Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
849bda8e8e | ||
|
|
57edb711d5 | ||
|
|
8235c64c93 | ||
|
|
750fb077f5 | ||
|
|
d781cb3199 | ||
|
|
f6bf635b0d | ||
|
|
56187f7a84 | ||
|
|
93c76f1c3f | ||
|
|
521b6d6135 | ||
|
|
94772bf4dc | ||
|
|
149f21e7e0 | ||
|
|
0a3ef3a7ab | ||
|
|
fef9935883 | ||
|
|
c7a5b706e1 | ||
|
|
f304bb069a | ||
|
|
ad6aa1af49 | ||
|
|
55b2c3fb4a | ||
|
|
83bcde00eb | ||
|
|
e8024b9337 | ||
|
|
8280e512bc | ||
|
|
b2234cb969 | ||
|
|
4bcb1f64ac | ||
|
|
4a64d59546 | ||
|
|
3fc43e8a12 | ||
|
|
c24a9229a6 | ||
|
|
90329d44d0 | ||
|
|
9a02bd3f1c | ||
|
|
adba11b462 | ||
|
|
29526b927f | ||
|
|
503e089efa | ||
|
|
c3495f462c | ||
|
|
6da6eeb2a4 | ||
|
|
0cf3e81bd7 | ||
|
|
f8cf1d364b | ||
|
|
6f344c6b5d | ||
|
|
133856afca | ||
|
|
33c543daaf | ||
|
|
c77e727d2b | ||
|
|
b94538711d | ||
|
|
b83479f45c | ||
|
|
001aad3abc | ||
|
|
f8db5e1ba0 | ||
|
|
f8bec0fb3d | ||
|
|
8bf23b7e9c | ||
|
|
3f10e045f4 | ||
|
|
71d1a0321b | ||
|
|
0f16396c8d | ||
|
|
2709d79f59 | ||
|
|
ab0eec5d4f | ||
|
|
4a044da38f | ||
|
|
fbbfbc6580 | ||
|
|
2714c2b084 | ||
|
|
9979bc8a16 | ||
|
|
a958a59ea2 | ||
|
|
2bf9e66da5 | ||
|
|
2980144e40 | ||
|
|
5387fe8632 | ||
|
|
1132ea3bbd | ||
|
|
9188e08a4f | ||
|
|
941ef7f9dd | ||
|
|
b1ffa2e219 | ||
|
|
91ea68dc53 | ||
|
|
cf79528d05 | ||
|
|
ef81f9da57 | ||
|
|
60d59cd211 | ||
|
|
19d38378f3 | ||
|
|
15a64232c0 | ||
|
|
28d3be0370 | ||
|
|
90ff9ccc5a | ||
|
|
33516422e7 | ||
|
|
eb31610a38 | ||
|
|
b0e1655018 | ||
|
|
b4cc5f87ec | ||
|
|
549ae558e7 | ||
|
|
5b06110e2d | ||
|
|
e3da2ee4b1 | ||
|
|
6b38d68b09 | ||
|
|
1dedc93a56 | ||
|
|
1427b55728 | ||
|
|
20521b5b3f | ||
|
|
532df49a06 | ||
|
|
fb65db633a | ||
|
|
279ef6b4f9 | ||
|
|
0def18db07 | ||
|
|
50bd860ced | ||
|
|
818574cedb | ||
|
|
3be0429a60 | ||
|
|
b8e72246b9 | ||
|
|
99e708a984 | ||
|
|
876f620ada | ||
|
|
b9550890da | ||
|
|
3732b955f5 | ||
|
|
253bfc0eff | ||
|
|
d4bcfc0428 | ||
|
|
7dec32df5e | ||
|
|
e09d574d5b | ||
|
|
944fc6dc95 | ||
|
|
91a97a1c68 |
@@ -0,0 +1 @@
|
|||||||
|
if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })
|
||||||
@@ -39,14 +39,19 @@
|
|||||||
"@cosmjs/proto-signing": "^0.31.0",
|
"@cosmjs/proto-signing": "^0.31.0",
|
||||||
"@cosmjs/stargate": "^0.31.0",
|
"@cosmjs/stargate": "^0.31.0",
|
||||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||||
"@dydxprotocol/v4-abacus": "^1.0.24",
|
"@dydxprotocol/v4-abacus": "^1.2.5",
|
||||||
"@dydxprotocol/v4-client-js": "^1.0.0",
|
"@dydxprotocol/v4-client-js": "^1.0.11",
|
||||||
"@dydxprotocol/v4-localization": "^1.0.5",
|
"@dydxprotocol/v4-localization": "^1.1.11",
|
||||||
"@ethersproject/providers": "^5.7.2",
|
"@ethersproject/providers": "^5.7.2",
|
||||||
"@js-joda/core": "^5.5.3",
|
"@js-joda/core": "^5.5.3",
|
||||||
|
"@privy-io/react-auth": "^1.53.1",
|
||||||
|
"@privy-io/wagmi-connector": "^0.1.12",
|
||||||
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-navigation-menu": "^1.1.3",
|
"@radix-ui/react-navigation-menu": "^1.1.3",
|
||||||
"@radix-ui/react-popover": "^1.0.6",
|
"@radix-ui/react-popover": "^1.0.6",
|
||||||
"@radix-ui/react-radio-group": "^1.1.3",
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
@@ -108,8 +113,8 @@
|
|||||||
"reselect": "^4.1.8",
|
"reselect": "^4.1.8",
|
||||||
"styled-components": "^5.3.11",
|
"styled-components": "^5.3.11",
|
||||||
"use-latest": "^1.2.1",
|
"use-latest": "^1.2.1",
|
||||||
"viem": "^1.12.2",
|
"viem": "^1.20.0",
|
||||||
"wagmi": "^1.3.7"
|
"wagmi": "^1.4.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.5",
|
"@babel/core": "^7.22.5",
|
||||||
@@ -148,6 +153,7 @@
|
|||||||
"url-polyfill": "^1.1.12",
|
"url-polyfill": "^1.1.12",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
"vite": "^4.3.9",
|
"vite": "^4.3.9",
|
||||||
|
"vite-plugin-pwa": "^0.17.4",
|
||||||
"vite-plugin-svgr": "^3.2.0",
|
"vite-plugin-svgr": "^3.2.0",
|
||||||
"vitest": "^0.32.2",
|
"vitest": "^0.32.2",
|
||||||
"w3name": "^1.0.8",
|
"w3name": "^1.0.8",
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"chainId": "1",
|
||||||
|
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||||
|
"name": "Ethereum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chainId": "5",
|
||||||
|
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
|
||||||
|
"name": "Ethereum Goerli"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"tradingRewardsFAQs": [
|
||||||
|
{
|
||||||
|
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_WHO_IS_ELIGIBLE_QUESTION",
|
||||||
|
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_WHO_IS_ELIGIBLE_ANSWER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_TRADING_REWARDS_WORK_QUESTION",
|
||||||
|
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_TRADING_REWARDS_WORK_ANSWER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_QUESTION",
|
||||||
|
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_ANSWER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -39,10 +39,10 @@
|
|||||||
"environments": {
|
"environments": {
|
||||||
"dydxprotocol-dev": {
|
"dydxprotocol-dev": {
|
||||||
"name": "v4 Dev",
|
"name": "v4 Dev",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydxprotocol-testnet",
|
"dydxChainId": "dydxprotocol-testnet",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
"https://validator.v4dev.dydx.exchange"
|
"https://validator.v4dev.dydx.exchange"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.0xsquid.com",
|
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
"faucet": "https://faucet.v4dev.dydx.exchange"
|
"faucet": "https://faucet.v4dev.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@@ -88,7 +89,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -112,10 +113,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-dev-2": {
|
"dydxprotocol-dev-2": {
|
||||||
"name": "v4 Dev 2",
|
"name": "v4 Dev 2",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydxprotocol-testnet",
|
"dydxChainId": "dydxprotocol-testnet",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -143,7 +144,8 @@
|
|||||||
"validators": [
|
"validators": [
|
||||||
"http://54.92.118.111"
|
"http://54.92.118.111"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.0xsquid.com"
|
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "https://dydx.exchange/v4-terms",
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
@@ -160,7 +162,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -184,10 +186,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-dev-4": {
|
"dydxprotocol-dev-4": {
|
||||||
"name": "v4 Dev 4",
|
"name": "v4 Dev 4",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydxprotocol-testnet",
|
"dydxChainId": "dydxprotocol-testnet",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -215,7 +217,8 @@
|
|||||||
"validators": [
|
"validators": [
|
||||||
"http://validator.v4dev4.dydx.exchange"
|
"http://validator.v4dev4.dydx.exchange"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.0xsquid.com"
|
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "https://dydx.exchange/v4-terms",
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
@@ -232,7 +235,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -256,10 +259,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-dev-5": {
|
"dydxprotocol-dev-5": {
|
||||||
"name": "v4 Dev 5",
|
"name": "v4 Dev 5",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydxprotocol-testnet",
|
"dydxChainId": "dydxprotocol-testnet",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -287,7 +290,8 @@
|
|||||||
"validators": [
|
"validators": [
|
||||||
"http://18.223.78.50"
|
"http://18.223.78.50"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.0xsquid.com"
|
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "https://dydx.exchange/v4-terms",
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
@@ -304,7 +308,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -328,10 +332,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-staging": {
|
"dydxprotocol-staging": {
|
||||||
"name": "v4 Staging",
|
"name": "v4 Staging",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydxprotocol-testnet",
|
"dydxChainId": "dydxprotocol-testnet",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -360,7 +364,8 @@
|
|||||||
"validators": [
|
"validators": [
|
||||||
"https://validator.v4staging.dydx.exchange"
|
"https://validator.v4staging.dydx.exchange"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com"
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "https://dydx.exchange/v4-terms",
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
@@ -378,7 +383,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -402,10 +407,11 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-staging-forced-update": {
|
"dydxprotocol-staging-forced-update": {
|
||||||
"name": "v4 Staging Forced Update",
|
"name": "v4 Staging Forced Update",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydxprotocol-testnet",
|
"dydxChainId": "dydxprotocol-testnet",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
"chain": {
|
"chain": {
|
||||||
@@ -433,12 +439,14 @@
|
|||||||
"validators": [
|
"validators": [
|
||||||
"https://validator.v4staging.dydx.exchange"
|
"https://validator.v4staging.dydx.exchange"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com"
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "https://dydx.exchange/v4-terms",
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
"privacy": "https://dydx.exchange/privacy",
|
"privacy": "https://dydx.exchange/privacy",
|
||||||
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
||||||
|
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
|
||||||
"documentation": "https://v4-teacher.vercel.app/",
|
"documentation": "https://v4-teacher.vercel.app/",
|
||||||
"community": "https://discord.com/invite/dydx",
|
"community": "https://discord.com/invite/dydx",
|
||||||
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
||||||
@@ -472,10 +480,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-staging-west": {
|
"dydxprotocol-staging-west": {
|
||||||
"name": "v4 Staging West",
|
"name": "v4 Staging West",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydxprotocol-testnet",
|
"dydxChainId": "dydxprotocol-testnet",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -504,7 +512,8 @@
|
|||||||
"validators": [
|
"validators": [
|
||||||
"https://validator-uswest1.v4staging.dydx.exchange"
|
"https://validator-uswest1.v4staging.dydx.exchange"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com"
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "https://dydx.exchange/v4-terms",
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
@@ -522,7 +531,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -546,10 +555,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-testnet": {
|
"dydxprotocol-testnet": {
|
||||||
"name": "v4 Public Testnet",
|
"name": "v4 Public Testnet",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydx-testnet-4",
|
"dydxChainId": "dydx-testnet-4",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -570,8 +579,8 @@
|
|||||||
"endpoints": {
|
"endpoints": {
|
||||||
"indexers": [
|
"indexers": [
|
||||||
{
|
{
|
||||||
"api": "https://dydx-testnet.imperator.co",
|
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||||
"socket": "wss://dydx-testnet.imperator.co"
|
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"validators": [
|
"validators": [
|
||||||
@@ -582,6 +591,7 @@
|
|||||||
"https://dydx-rpc.liquify.com/api=8878132/dydx"
|
"https://dydx-rpc.liquify.com/api=8878132/dydx"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@@ -600,7 +610,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -624,10 +634,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-testnet-dydx": {
|
"dydxprotocol-testnet-dydx": {
|
||||||
"name": "v4 Public Testnet/dYdX",
|
"name": "v4 Public Testnet/dYdX",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydx-testnet-4",
|
"dydxChainId": "dydx-testnet-4",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -656,6 +666,7 @@
|
|||||||
"https://validator.v4testnet.dydx.exchange"
|
"https://validator.v4testnet.dydx.exchange"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@@ -675,7 +686,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -699,10 +710,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-testnet-nodefleet": {
|
"dydxprotocol-testnet-nodefleet": {
|
||||||
"name": "v4 Public Testnet/nodefleet",
|
"name": "v4 Public Testnet/nodefleet",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydx-testnet-4",
|
"dydxChainId": "dydx-testnet-4",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -723,14 +734,15 @@
|
|||||||
"endpoints": {
|
"endpoints": {
|
||||||
"indexers": [
|
"indexers": [
|
||||||
{
|
{
|
||||||
"api": "https://dydx-testnet.imperator.co",
|
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||||
"socket": "wss://dydx-testnet.imperator.co"
|
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"validators": [
|
"validators": [
|
||||||
"https://dydx-testnet.nodefleet.org"
|
"https://dydx-testnet.nodefleet.org"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@@ -750,7 +762,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -774,10 +786,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-testnet-kingnodes": {
|
"dydxprotocol-testnet-kingnodes": {
|
||||||
"name": "v4 Public Testnet/KingNodes",
|
"name": "v4 Public Testnet/KingNodes",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydx-testnet-4",
|
"dydxChainId": "dydx-testnet-4",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -798,14 +810,15 @@
|
|||||||
"endpoints": {
|
"endpoints": {
|
||||||
"indexers": [
|
"indexers": [
|
||||||
{
|
{
|
||||||
"api": "https://dydx-testnet.imperator.co",
|
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||||
"socket": "wss://dydx-testnet.imperator.co"
|
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"validators": [
|
"validators": [
|
||||||
"https://test-dydx.kingnodes.com"
|
"https://test-dydx.kingnodes.com"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@@ -825,7 +838,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -849,10 +862,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-testnet-liquify": {
|
"dydxprotocol-testnet-liquify": {
|
||||||
"name": "v4 Public Testnet/Liquify",
|
"name": "v4 Public Testnet/Liquify",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydx-testnet-4",
|
"dydxChainId": "dydx-testnet-4",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -873,14 +886,15 @@
|
|||||||
"endpoints": {
|
"endpoints": {
|
||||||
"indexers": [
|
"indexers": [
|
||||||
{
|
{
|
||||||
"api": "https://dydx-testnet.imperator.co",
|
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||||
"socket": "wss://dydx-testnet.imperator.co"
|
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"validators": [
|
"validators": [
|
||||||
"https://dydx-rpc.liquify.com/api=8878132/dydx"
|
"https://dydx-rpc.liquify.com/api=8878132/dydx"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@@ -900,7 +914,7 @@
|
|||||||
"governanceLearnMore": "https://help.dydx.exchange",
|
"governanceLearnMore": "https://help.dydx.exchange",
|
||||||
"stakingLearnMore": "https://help.dydx.exchange",
|
"stakingLearnMore": "https://help.dydx.exchange",
|
||||||
"keplrDashboard": "https://testnet.keplr.app/",
|
"keplrDashboard": "https://testnet.keplr.app/",
|
||||||
"accountExportLearnMore": "https://help.dydx.exchange",
|
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||||
},
|
},
|
||||||
"wallets": {
|
"wallets": {
|
||||||
@@ -924,10 +938,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-testnet-polkachu": {
|
"dydxprotocol-testnet-polkachu": {
|
||||||
"name": "v4 Public Testnet/Polkahcu",
|
"name": "v4 Public Testnet/Polkahcu",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydx-testnet-4",
|
"dydxChainId": "dydx-testnet-4",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -948,14 +962,15 @@
|
|||||||
"endpoints": {
|
"endpoints": {
|
||||||
"indexers": [
|
"indexers": [
|
||||||
{
|
{
|
||||||
"api": "https://dydx-testnet.imperator.co",
|
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||||
"socket": "wss://dydx-testnet.imperator.co"
|
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"validators": [
|
"validators": [
|
||||||
"https://dydx-testnet-rpc.polkachu.com/"
|
"https://dydx-testnet-rpc.polkachu.com/"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@@ -990,10 +1005,10 @@
|
|||||||
},
|
},
|
||||||
"dydxprotocol-testnet-bware": {
|
"dydxprotocol-testnet-bware": {
|
||||||
"name": "v4 Public Testnet/BWare",
|
"name": "v4 Public Testnet/BWare",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "11155111",
|
||||||
"dydxChainId": "dydx-testnet-4",
|
"dydxChainId": "dydx-testnet-4",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "dYdX-api",
|
"squidIntegratorId": "dYdX-api",
|
||||||
"isMainNet": false,
|
"isMainNet": false,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -1014,14 +1029,15 @@
|
|||||||
"endpoints": {
|
"endpoints": {
|
||||||
"indexers": [
|
"indexers": [
|
||||||
{
|
{
|
||||||
"api": "https://dydx-testnet.imperator.co",
|
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||||
"socket": "wss://dydx-testnet.imperator.co"
|
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"validators": [
|
"validators": [
|
||||||
"https://dydx-testnet-full-rpc.public.blastapi.io/"
|
"https://dydx-testnet-full-rpc.public.blastapi.io/"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||||
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@@ -1068,7 +1084,7 @@
|
|||||||
"ethereumChainId": "1",
|
"ethereumChainId": "1",
|
||||||
"dydxChainId": "[mainnet chain id]",
|
"dydxChainId": "[mainnet chain id]",
|
||||||
"chainName": "dYdX Chain",
|
"chainName": "dYdX Chain",
|
||||||
"chainLogo": "dydx-chain.png",
|
"chainLogo": "/dydx-chain.png",
|
||||||
"squidIntegratorId": "[mainnet squid integrator id]",
|
"squidIntegratorId": "[mainnet squid integrator id]",
|
||||||
"isMainNet": true,
|
"isMainNet": true,
|
||||||
"tokens": {
|
"tokens": {
|
||||||
@@ -1098,7 +1114,8 @@
|
|||||||
"[Validator endpoint 1",
|
"[Validator endpoint 1",
|
||||||
"[Validator endpoint n]"
|
"[Validator endpoint n]"
|
||||||
],
|
],
|
||||||
"0xsquid": "[0xSquid endpoint for mainnet]"
|
"0xsquid": "[0xSquid endpoint for mainnet]",
|
||||||
|
"nobleValidator": "[noble validator endpoint for mainnet]"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "[HTTP link to TOS]",
|
"tos": "[HTTP link to TOS]",
|
||||||
@@ -1109,7 +1126,7 @@
|
|||||||
"blogs": "[HTTP link to blogs, can be null]",
|
"blogs": "[HTTP link to blogs, can be null]",
|
||||||
"foundation": "[HTTP link to foundation, can be null]",
|
"foundation": "[HTTP link to foundation, can be null]",
|
||||||
"initialMarginFractionLearnMore": "[HTTP link to initial margin fraction learn more, can be null]",
|
"initialMarginFractionLearnMore": "[HTTP link to initial margin fraction learn more, can be null]",
|
||||||
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
|
"reduceOnlyLearnMore": "[HTTP link to reduce-only learn more, can be null]",
|
||||||
"documentation": "[HTTP link to documentation, can be null]",
|
"documentation": "[HTTP link to documentation, can be null]",
|
||||||
"community": "[HTTP link to community, can be null]",
|
"community": "[HTTP link to community, can be null]",
|
||||||
"help": "[HTTP link to help page, can be null]",
|
"help": "[HTTP link to help page, can be null]",
|
||||||
|
|||||||
@@ -1,454 +1,360 @@
|
|||||||
{
|
{
|
||||||
"1INCH-USD":{
|
"1INCH-USD": {
|
||||||
"name":"1inch",
|
"name": "1inch",
|
||||||
"tags":[
|
"tags": ["Defi"],
|
||||||
"Defi"
|
"websiteLink": "https://1inch.io/",
|
||||||
],
|
"whitepaperLink": "https://github.com/1inch",
|
||||||
"websiteLink":"https://1inch.io/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/1inch/"
|
||||||
"whitepaperLink":"https://github.com/1inch",
|
},
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/1inch/"
|
"AAVE-USD": {
|
||||||
},
|
"name": "Aave",
|
||||||
"AAVE-USD":{
|
"tags": ["Defi"],
|
||||||
"name":"Aave",
|
"websiteLink": "https://aave.com/",
|
||||||
"tags":[
|
"whitepaperLink": "https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf",
|
||||||
"Defi"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/aave/"
|
||||||
],
|
},
|
||||||
"websiteLink":"https://aave.com/",
|
"ADA-USD": {
|
||||||
"whitepaperLink":"https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf",
|
"name": "Cardano",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aave/"
|
"tags": ["Layer 1"],
|
||||||
},
|
"websiteLink": "https://cardano.org/",
|
||||||
"ADA-USD":{
|
"whitepaperLink": "https://why.cardano.org/en/introduction/motivation/",
|
||||||
"name":"Cardano",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/cardano/"
|
||||||
"tags":[
|
},
|
||||||
"Layer 1"
|
"ALGO-USD": {
|
||||||
],
|
"name": "Algorand",
|
||||||
"websiteLink":"https://cardano.org/",
|
"tags": ["Layer 1"],
|
||||||
"whitepaperLink":"https://why.cardano.org/en/introduction/motivation/",
|
"websiteLink": "https://algorand.com/",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cardano/"
|
"whitepaperLink": "https://algorand.com/technology/white-papers",
|
||||||
},
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/algorand/"
|
||||||
"ALGO-USD":{
|
},
|
||||||
"name":"Algorand",
|
"APE-USD": {
|
||||||
"tags":[
|
"name": "ApeCoin",
|
||||||
"Layer 1"
|
"tags": [],
|
||||||
],
|
"websiteLink": "https://apecoin.com/",
|
||||||
"websiteLink":"https://algorand.com/",
|
"whitepaperLink": "https://apecoin.com/about",
|
||||||
"whitepaperLink":"https://algorand.com/technology/white-papers",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/apecoin-ape/"
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/algorand/"
|
},
|
||||||
},
|
"APT-USD": {
|
||||||
"APE-USD":{
|
"name": "Aptos",
|
||||||
"name":"ApeCoin",
|
"tags": ["Layer 1"],
|
||||||
"tags":[
|
"websiteLink": "https://aptoslabs.com/",
|
||||||
|
"whitepaperLink": "https://aptos.dev/aptos-white-paper/",
|
||||||
],
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/aptos/"
|
||||||
"websiteLink":"https://apecoin.com/",
|
},
|
||||||
"whitepaperLink":"https://apecoin.com/about",
|
"ARB-USD": {
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/apecoin-ape/"
|
"name": "Arbitrum",
|
||||||
},
|
"tags": [],
|
||||||
"APT-USD":{
|
"websiteLink": "https://arbitrum.io/",
|
||||||
"name":"Aptos",
|
"whitepaperLink": "https://github.com/OffchainLabs/nitro",
|
||||||
"tags":[
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/arbitrum/"
|
||||||
"Layer 1"
|
},
|
||||||
],
|
"ATOM-USD": {
|
||||||
"websiteLink":"https://aptoslabs.com/",
|
"name": "Cosmos",
|
||||||
"whitepaperLink":"https://aptos.dev/aptos-white-paper/",
|
"tags": ["Layer 1"],
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aptos/"
|
"websiteLink": "https://cosmos.network/",
|
||||||
},
|
"whitepaperLink": "https://v1.cosmos.network/resources/whitepaper",
|
||||||
"ARB-USD":{
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/cosmos/"
|
||||||
"name":"Arbitrum",
|
},
|
||||||
"tags":[
|
"AVAX-USD": {
|
||||||
|
"name": "Avalanche",
|
||||||
],
|
"tags": ["Layer 1"],
|
||||||
"websiteLink":"https://arbitrum.io/",
|
"websiteLink": "https://www.avalabs.org/",
|
||||||
"whitepaperLink":"https://github.com/OffchainLabs/nitro",
|
"whitepaperLink": "https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/arbitrum/"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/avalanche/"
|
||||||
},
|
},
|
||||||
"ATOM-USD":{
|
"BCH-USD": {
|
||||||
"name":"Cosmos",
|
"name": "Bitcoin Cash",
|
||||||
"tags":[
|
"tags": ["Layer 1"],
|
||||||
"Layer 1"
|
"websiteLink": "https://bitcoincash.org/",
|
||||||
],
|
"whitepaperLink": "https://bitcoincash.org/",
|
||||||
"websiteLink":"https://cosmos.network/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin-cash/"
|
||||||
"whitepaperLink":"https://v1.cosmos.network/resources/whitepaper",
|
},
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cosmos/"
|
"BLUR-USD": {
|
||||||
},
|
"name": "Blur",
|
||||||
"AVAX-USD":{
|
"tags": [],
|
||||||
"name":"Avalanche",
|
"websiteLink": "https://blur.io/",
|
||||||
"tags":[
|
"whitepaperLink": "https://docs.blur.foundation/",
|
||||||
"Layer 1"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/blur-token/"
|
||||||
],
|
},
|
||||||
"websiteLink":"https://www.avalabs.org/",
|
"CELO-USD": {
|
||||||
"whitepaperLink":"https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf",
|
"name": "Celo",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/avalanche/"
|
"tags": [],
|
||||||
},
|
"websiteLink": "https://celo.org",
|
||||||
"BCH-USD":{
|
"whitepaperLink": "https://docs.celo.org",
|
||||||
"name":"Bitcoin Cash",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/celo/"
|
||||||
"tags":[
|
},
|
||||||
"Layer 1"
|
"BTC-USD": {
|
||||||
],
|
"name": "Bitcoin",
|
||||||
"websiteLink":"https://bitcoincash.org/",
|
"tags": ["Layer 1"],
|
||||||
"whitepaperLink":"https://bitcoincash.org/",
|
"websiteLink": "https://bitcoin.org/",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin-cash/"
|
"whitepaperLink": "https://bitcoin.org/bitcoin.pdf",
|
||||||
},
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin/"
|
||||||
"BLUR-USD":{
|
},
|
||||||
"name":"Blur",
|
"COMP-USD": {
|
||||||
"tags":[
|
"name": "Compound",
|
||||||
|
"tags": ["Defi"],
|
||||||
],
|
"websiteLink": "https://compound.finance/",
|
||||||
"websiteLink":"https://blur.io/",
|
"whitepaperLink": "https://compound.finance/documents/Compound.Whitepaper.pdf",
|
||||||
"whitepaperLink":"https://docs.blur.foundation/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/compound/"
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/blur-token/"
|
},
|
||||||
},
|
"CRV-USD": {
|
||||||
"CELO-USD":{
|
"name": "Curve",
|
||||||
"name":"Celo",
|
"tags": ["Governance"],
|
||||||
"tags":[
|
"websiteLink": "https://curve.fi/",
|
||||||
|
"whitepaperLink": "https://curve.fi/whitepaper",
|
||||||
],
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/curve-dao-token/"
|
||||||
"websiteLink":"https://celo.org",
|
},
|
||||||
"whitepaperLink":"https://docs.celo.org",
|
"DOGE-USD": {
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/celo/"
|
"name": "Dogecoin",
|
||||||
},
|
"tags": ["Layer 1"],
|
||||||
"BTC-USD":{
|
"websiteLink": "https://dogecoin.com/",
|
||||||
"name":"Bitcoin",
|
"whitepaperLink": "https://github.com/dogecoin/dogecoin",
|
||||||
"tags":[
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/dogecoin/"
|
||||||
"Layer 1"
|
},
|
||||||
],
|
"DOT-USD": {
|
||||||
"websiteLink":"https://bitcoin.org/",
|
"name": "Polkadot",
|
||||||
"whitepaperLink":"https://bitcoin.org/bitcoin.pdf",
|
"tags": ["Layer 1"],
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin/"
|
"websiteLink": "https://polkadot.network/",
|
||||||
},
|
"whitepaperLink": "https://polkadot.network/PolkaDotPaper.pdf",
|
||||||
"COMP-USD":{
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/polkadot-new/"
|
||||||
"name":"Compound",
|
},
|
||||||
"tags":[
|
"ENJ-USD": {
|
||||||
"Defi"
|
"name": "Enjin",
|
||||||
],
|
"tags": [],
|
||||||
"websiteLink":"https://compound.finance/",
|
"websiteLink": "https://enjin.io/",
|
||||||
"whitepaperLink":"https://compound.finance/documents/Compound.Whitepaper.pdf",
|
"whitepaperLink": "https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/compound/"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/enjin-coin/"
|
||||||
},
|
},
|
||||||
"CRV-USD":{
|
"EOS-USD": {
|
||||||
"name":"Curve",
|
"name": "EOS",
|
||||||
"tags":[
|
"tags": ["Layer 1"],
|
||||||
"Governance"
|
"websiteLink": "https://eos.io/",
|
||||||
],
|
"whitepaperLink": "https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md",
|
||||||
"websiteLink":"https://curve.fi/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/eos/"
|
||||||
"whitepaperLink":"https://curve.fi/whitepaper",
|
},
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/curve-dao-token/"
|
"ETC-USD": {
|
||||||
},
|
"name": "Ethereum Classic",
|
||||||
"DOGE-USD":{
|
"tags": ["Layer 1"],
|
||||||
"name":"Dogecoin",
|
"websiteLink": "https://ethereumclassic.org/",
|
||||||
"tags":[
|
"whitepaperLink": "https://ethereumclassic.org/why-classic",
|
||||||
"Layer 1"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum-classic/"
|
||||||
],
|
},
|
||||||
"websiteLink":"https://dogecoin.com/",
|
"ETH-USD": {
|
||||||
"whitepaperLink":"https://github.com/dogecoin/dogecoin",
|
"name": "Ethereum",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/dogecoin/"
|
"tags": ["Layer 1"],
|
||||||
},
|
"websiteLink": "https://ethereum.org/",
|
||||||
"DOT-USD":{
|
"whitepaperLink": "https://ethereum.org/whitepaper/",
|
||||||
"name":"Polkadot",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum/",
|
||||||
"tags":[
|
"displayStepSize": "0.001",
|
||||||
"Layer 1"
|
"displayTickSize": "0.1"
|
||||||
],
|
},
|
||||||
"websiteLink":"https://polkadot.network/",
|
"FIL-USD": {
|
||||||
"whitepaperLink":"https://polkadot.network/PolkaDotPaper.pdf",
|
"name": "Filecoin",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polkadot-new/"
|
"tags": ["Layer 1"],
|
||||||
},
|
"websiteLink": "https://filecoin.io/",
|
||||||
"ENJ-USD":{
|
"whitepaperLink": "https://filecoin.io/filecoin.pdf",
|
||||||
"name":"Enjin",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/filecoin/"
|
||||||
"tags":[
|
},
|
||||||
|
"HNT-USD": {
|
||||||
],
|
"name": "Helium",
|
||||||
"websiteLink":"https://enjin.io/",
|
"tags": ["Layer 1"],
|
||||||
"whitepaperLink":"https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
|
"websiteLink": "https://www.helium.com",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/enjin-coin/"
|
"whitepaperLink": "http://whitepaper.helium.com",
|
||||||
},
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/helium/"
|
||||||
"EOS-USD":{
|
},
|
||||||
"name":"EOS",
|
"ICP-USD": {
|
||||||
"tags":[
|
"name": "Internet Computer",
|
||||||
"Layer 1"
|
"tags": ["Layer 1"],
|
||||||
],
|
"websiteLink": "https://dfinity.org",
|
||||||
"websiteLink":"https://eos.io/",
|
"whitepaperLink": "https://dfinity.org/whitepaper.pdf",
|
||||||
"whitepaperLink":"https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/internet-computer/"
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/eos/"
|
},
|
||||||
},
|
"LDO-USD": {
|
||||||
"ETC-USD":{
|
"name": "Lido DAO",
|
||||||
"name":"Ethereum Classic",
|
"tags": ["Defi"],
|
||||||
"tags":[
|
"websiteLink": "https://lido.fi/",
|
||||||
"Layer 1"
|
"whitepaperLink": "https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf",
|
||||||
],
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/lido-dao/"
|
||||||
"websiteLink":"https://ethereumclassic.org/",
|
},
|
||||||
"whitepaperLink":"https://ethereumclassic.org/why-classic",
|
"LINK-USD": {
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum-classic/"
|
"name": "Chainlink",
|
||||||
},
|
"tags": ["Defi"],
|
||||||
"ETH-USD":{
|
"websiteLink": "https://chain.link/",
|
||||||
"name":"Ethereum",
|
"whitepaperLink": "https://link.smartcontract.com/whitepaper",
|
||||||
"tags":[
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/chainlink/"
|
||||||
"Layer 1"
|
},
|
||||||
],
|
"LTC-USD": {
|
||||||
"websiteLink":"https://ethereum.org/",
|
"name": "Litecoin",
|
||||||
"whitepaperLink":"https://ethereum.org/whitepaper/",
|
"tags": ["Layer 1"],
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum/",
|
"websiteLink": "https://litecoin.org/",
|
||||||
"displayStepSize":"0.001",
|
"whitepaperLink": "https://litecoin.info/index.php/Main_Page",
|
||||||
"displayTickSize":"0.1"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/litecoin/"
|
||||||
},
|
},
|
||||||
"FIL-USD":{
|
"MATIC-USD": {
|
||||||
"name":"Filecoin",
|
"name": "Polygon",
|
||||||
"tags":[
|
"tags": ["Layer 2"],
|
||||||
"Layer 1"
|
"websiteLink": "https://polygon.technology/",
|
||||||
],
|
"whitepaperLink": "https://polygon.technology/lightpaper-polygon.pdf",
|
||||||
"websiteLink":"https://filecoin.io/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/polygon/"
|
||||||
"whitepaperLink":"https://filecoin.io/filecoin.pdf",
|
},
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/filecoin/"
|
"MKR-USD": {
|
||||||
},
|
"name": "Maker",
|
||||||
"HNT-USD":{
|
"tags": ["Governance"],
|
||||||
"name":"Helium",
|
"websiteLink": "https://makerdao.com",
|
||||||
"tags":[
|
"whitepaperLink": "https://makerdao.com/whitepaper",
|
||||||
"Layer 1"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/maker/"
|
||||||
],
|
},
|
||||||
"websiteLink":"https://www.helium.com",
|
"NEAR-USD": {
|
||||||
"whitepaperLink":"http://whitepaper.helium.com",
|
"name": "NEAR Protocol",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/helium/"
|
"tags": ["Layer 1"],
|
||||||
},
|
"websiteLink": "https://near.org",
|
||||||
"ICP-USD":{
|
"whitepaperLink": "https://near.org/papers/the-official-near-white-paper/",
|
||||||
"name":"Internet Computer",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/near-protocol/"
|
||||||
"tags":[
|
},
|
||||||
"Layer 1"
|
"OP-USD": {
|
||||||
],
|
"name": "Optimism",
|
||||||
"websiteLink":"https://dfinity.org",
|
"tags": [],
|
||||||
"whitepaperLink":"https://dfinity.org/whitepaper.pdf",
|
"websiteLink": "https://www.optimism.io/",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/internet-computer/"
|
"whitepaperLink": "https://github.com/ethereum-optimism",
|
||||||
},
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/optimism-ethereum/"
|
||||||
"LDO-USD":{
|
},
|
||||||
"name":"Lido DAO",
|
"PEPE-USD": {
|
||||||
"tags":[
|
"name": "Pepe",
|
||||||
"Defi"
|
"tags": [],
|
||||||
],
|
"websiteLink": "https://www.pepe.vip/",
|
||||||
"websiteLink":"https://lido.fi/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/pepe/"
|
||||||
"whitepaperLink":"https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf",
|
},
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/lido-dao/"
|
"RUNE-USD": {
|
||||||
},
|
"name": "THORChain",
|
||||||
"LINK-USD":{
|
"tags": ["Layer 1"],
|
||||||
"name":"Chainlink",
|
"websiteLink": "https://thorchain.org",
|
||||||
"tags":[
|
"whitepaperLink": "https://whitepaper.io/document/709/thorchain-whitepaper",
|
||||||
"Defi"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/thorchain/"
|
||||||
],
|
},
|
||||||
"websiteLink":"https://chain.link/",
|
"SEI-USD": {
|
||||||
"whitepaperLink":"https://link.smartcontract.com/whitepaper",
|
"name": "Sei",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/chainlink/"
|
"tags": ["Layer 1", "Defi"],
|
||||||
},
|
"websiteLink": "https://www.sei.io/",
|
||||||
"LTC-USD":{
|
"whitepaperLink": "https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf",
|
||||||
"name":"Litecoin",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sei/"
|
||||||
"tags":[
|
},
|
||||||
"Layer 1"
|
"SHIB-USD": {
|
||||||
],
|
"name": "Shiba Inu",
|
||||||
"websiteLink":"https://litecoin.org/",
|
"tags": [],
|
||||||
"whitepaperLink":"https://litecoin.info/index.php/Main_Page",
|
"websiteLink": "https://shibatoken.com/",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/litecoin/"
|
"whitepaperLink": "https://docs.shibatoken.com/",
|
||||||
},
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/shiba-inu/"
|
||||||
"MATIC-USD":{
|
},
|
||||||
"name":"Polygon",
|
"SNX-USD": {
|
||||||
"tags":[
|
"name": "Synthetix",
|
||||||
"Layer 2"
|
"tags": ["Defi"],
|
||||||
],
|
"websiteLink": "https://synthetix.io/",
|
||||||
"websiteLink":"https://polygon.technology/",
|
"whitepaperLink": "https://docs.synthetix.io/litepaper",
|
||||||
"whitepaperLink":"https://polygon.technology/lightpaper-polygon.pdf",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/synthetix-network-token/"
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polygon/"
|
},
|
||||||
},
|
"SOL-USD": {
|
||||||
"MKR-USD":{
|
"name": "Solana",
|
||||||
"name":"Maker",
|
"tags": ["Layer 1"],
|
||||||
"tags":[
|
"websiteLink": "https://solana.com/",
|
||||||
"Governance"
|
"whitepaperLink": "https://solana.com/solana-whitepaper.pdf",
|
||||||
],
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/solana/"
|
||||||
"websiteLink":"https://makerdao.com",
|
},
|
||||||
"whitepaperLink":"https://makerdao.com/whitepaper",
|
"SUI-USD": {
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/maker/"
|
"name": "Sui",
|
||||||
},
|
"tags": ["Layer 1"],
|
||||||
"NEAR-USD":{
|
"websiteLink": "https://sui.io/",
|
||||||
"name":"NEAR Protocol",
|
"whitepaperLink": "https://github.com/MystenLabs",
|
||||||
"tags":[
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sui/"
|
||||||
"Layer 1"
|
},
|
||||||
],
|
"SUSHI-USD": {
|
||||||
"websiteLink":"https://near.org",
|
"name": "Sushi",
|
||||||
"whitepaperLink":"https://near.org/papers/the-official-near-white-paper/",
|
"tags": ["Defi"],
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/near-protocol/"
|
"websiteLink": "https://sushi.com/",
|
||||||
},
|
"whitepaperLink": "https://docs.sushi.com/",
|
||||||
"OP-USD":{
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sushiswap/"
|
||||||
"name":"Optimism",
|
},
|
||||||
"tags":[
|
"TIA-USD": {
|
||||||
|
"name": "Celestia",
|
||||||
],
|
"tags": ["Layer 1"],
|
||||||
"websiteLink":"https://www.optimism.io/",
|
"websiteLink": "https://celestia.org/",
|
||||||
"whitepaperLink":"https://github.com/ethereum-optimism",
|
"whitepaperLink": "https://arxiv.org/pdf/1905.09274.pdf",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/optimism-ethereum/"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/celestia/"
|
||||||
},
|
},
|
||||||
"PEPE-USD":{
|
"TRX-USD": {
|
||||||
"name":"Pepe",
|
"name": "TRON",
|
||||||
"tags":[
|
"tags": ["Defi"],
|
||||||
|
"websiteLink": "https://tron.network/",
|
||||||
],
|
"whitepaperLink": "https://tron.network/static/doc/white_paper_v_2_0.pdf",
|
||||||
"websiteLink":"https://www.pepe.vip/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/tron/"
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/pepe/"
|
},
|
||||||
},
|
"UMA-USD": {
|
||||||
"RUNE-USD":{
|
"name": "UMA",
|
||||||
"name":"THORChain",
|
"tags": ["Defi"],
|
||||||
"tags":[
|
"websiteLink": "https://umaproject.org/",
|
||||||
"Layer 1"
|
"whitepaperLink": "https://github.com/UMAprotocol/whitepaper",
|
||||||
],
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/uma/"
|
||||||
"websiteLink":"https://thorchain.org",
|
},
|
||||||
"whitepaperLink":"https://whitepaper.io/document/709/thorchain-whitepaper",
|
"UNI-USD": {
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/thorchain/"
|
"name": "Uniswap",
|
||||||
},
|
"tags": ["Defi"],
|
||||||
"SEI-USD":{
|
"websiteLink": "https://uniswap.org/",
|
||||||
"name":"Sei",
|
"whitepaperLink": "https://uniswap.org/whitepaper-v3.pdf",
|
||||||
"tags":[
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/uniswap/"
|
||||||
"Layer 1",
|
},
|
||||||
"Defi"
|
"WLD-USD": {
|
||||||
],
|
"name": "Worldcoin",
|
||||||
"websiteLink":"https://www.sei.io/",
|
"tags": [],
|
||||||
"whitepaperLink":"https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf",
|
"websiteLink": "https://worldcoin.org/",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sei/"
|
"whitepaperLink": "https://whitepaper.worldcoin.org/",
|
||||||
},
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/worldcoin-org/"
|
||||||
"SHIB-USD":{
|
},
|
||||||
"name":"Shiba Inu",
|
"XLM-USD": {
|
||||||
"tags":[
|
"name": "Stellar",
|
||||||
|
"tags": ["Layer 1"],
|
||||||
],
|
"websiteLink": "https://www.stellar.org/",
|
||||||
"websiteLink":"https://shibatoken.com/",
|
"whitepaperLink": "https://www.stellar.org/papers/stellar-consensus-protocol",
|
||||||
"whitepaperLink":"https://docs.shibatoken.com/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/stellar/"
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/shiba-inu/"
|
},
|
||||||
},
|
"XMR-USD": {
|
||||||
"SNX-USD":{
|
"name": "Monero",
|
||||||
"name":"Synthetix",
|
"tags": ["Layer 1"],
|
||||||
"tags":[
|
"websiteLink": "https://www.getmonero.org/",
|
||||||
"Defi"
|
"whitepaperLink": "https://www.getmonero.org/resources/research-lab/",
|
||||||
],
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/monero/"
|
||||||
"websiteLink":"https://synthetix.io/",
|
},
|
||||||
"whitepaperLink":"https://docs.synthetix.io/litepaper",
|
"XRP-USD": {
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/synthetix-network-token/"
|
"name": "Ripple",
|
||||||
},
|
"tags": ["Layer 1"],
|
||||||
"SOL-USD":{
|
"websiteLink": "https://ripple.com/currency/",
|
||||||
"name":"Solana",
|
"whitepaperLink": "https://github.com/ripple",
|
||||||
"tags":[
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/xrp/"
|
||||||
"Layer 1"
|
},
|
||||||
],
|
"XTZ-USD": {
|
||||||
"websiteLink":"https://solana.com/",
|
"name": "Tezos",
|
||||||
"whitepaperLink":"https://solana.com/solana-whitepaper.pdf",
|
"tags": ["Layer 1"],
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/solana/"
|
"websiteLink": "https://tezos.com",
|
||||||
},
|
"whitepaperLink": "https://tezos.com/whitepaper.pdf",
|
||||||
"SUI-USD":{
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/tezos/"
|
||||||
"name":"Sui",
|
},
|
||||||
"tags":[
|
"YFI-USD": {
|
||||||
"Layer 1"
|
"name": "Yearn",
|
||||||
],
|
"tags": ["Defi"],
|
||||||
"websiteLink":"https://sui.io/",
|
"websiteLink": "https://yearn.finance/",
|
||||||
"whitepaperLink":"https://github.com/MystenLabs",
|
"whitepaperLink": "https://docs.yearn.finance/",
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sui/"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/yearn-finance/"
|
||||||
},
|
},
|
||||||
"SUSHI-USD":{
|
"ZEC-USD": {
|
||||||
"name":"Sushi",
|
"name": "Zcash",
|
||||||
"tags":[
|
"tags": ["Layer 1"],
|
||||||
"Defi"
|
"websiteLink": "https://z.cash/",
|
||||||
],
|
"whitepaperLink": "https://z.cash/technology/",
|
||||||
"websiteLink":"https://sushi.com/",
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/zcash/"
|
||||||
"whitepaperLink":"https://docs.sushi.com/",
|
},
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sushiswap/"
|
"ZRX-USD": {
|
||||||
},
|
"name": "0x",
|
||||||
"TRX-USD":{
|
"tags": ["Defi"],
|
||||||
"name":"TRON",
|
"websiteLink": "https://0x.org/",
|
||||||
"tags":[
|
"whitepaperLink": "https://0x.org/pdfs/0x_white_paper.pdf",
|
||||||
"Defi"
|
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/0x/"
|
||||||
],
|
}
|
||||||
"websiteLink":"https://tron.network/",
|
}
|
||||||
"whitepaperLink":"https://tron.network/static/doc/white_paper_v_2_0.pdf",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/tron/"
|
|
||||||
},
|
|
||||||
"UMA-USD":{
|
|
||||||
"name":"UMA",
|
|
||||||
"tags":[
|
|
||||||
"Defi"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://umaproject.org/",
|
|
||||||
"whitepaperLink":"https://github.com/UMAprotocol/whitepaper",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/uma/"
|
|
||||||
},
|
|
||||||
"UNI-USD":{
|
|
||||||
"name":"Uniswap",
|
|
||||||
"tags":[
|
|
||||||
"Defi"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://uniswap.org/",
|
|
||||||
"whitepaperLink":"https://uniswap.org/whitepaper-v3.pdf",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/uniswap/"
|
|
||||||
},
|
|
||||||
"WLD-USD":{
|
|
||||||
"name":"Worldcoin",
|
|
||||||
"tags":[
|
|
||||||
|
|
||||||
],
|
|
||||||
"websiteLink":"https://worldcoin.org/",
|
|
||||||
"whitepaperLink":"https://whitepaper.worldcoin.org/",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/worldcoin-org/"
|
|
||||||
},
|
|
||||||
"XLM-USD":{
|
|
||||||
"name":"Stellar",
|
|
||||||
"tags":[
|
|
||||||
"Layer 1"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://www.stellar.org/",
|
|
||||||
"whitepaperLink":"https://www.stellar.org/papers/stellar-consensus-protocol",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/stellar/"
|
|
||||||
},
|
|
||||||
"XMR-USD":{
|
|
||||||
"name":"Monero",
|
|
||||||
"tags":[
|
|
||||||
"Layer 1"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://www.getmonero.org/",
|
|
||||||
"whitepaperLink":"https://www.getmonero.org/resources/research-lab/",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/monero/"
|
|
||||||
},
|
|
||||||
"XRP-USD":{
|
|
||||||
"name":"Ripple",
|
|
||||||
"tags":[
|
|
||||||
"Layer 1"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://ripple.com/currency/",
|
|
||||||
"whitepaperLink":"https://github.com/ripple",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/xrp/"
|
|
||||||
},
|
|
||||||
"XTZ-USD":{
|
|
||||||
"name":"Tezos",
|
|
||||||
"tags":[
|
|
||||||
"Layer 1"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://tezos.com",
|
|
||||||
"whitepaperLink":"https://tezos.com/whitepaper.pdf",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/tezos/"
|
|
||||||
},
|
|
||||||
"YFI-USD":{
|
|
||||||
"name":"Yearn",
|
|
||||||
"tags":[
|
|
||||||
"Defi"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://yearn.finance/",
|
|
||||||
"whitepaperLink":"https://docs.yearn.finance/",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/yearn-finance/"
|
|
||||||
},
|
|
||||||
"ZEC-USD":{
|
|
||||||
"name":"Zcash",
|
|
||||||
"tags":[
|
|
||||||
"Layer 1"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://z.cash/",
|
|
||||||
"whitepaperLink":"https://z.cash/technology/",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/zcash/"
|
|
||||||
},
|
|
||||||
"ZRX-USD":{
|
|
||||||
"name":"0x",
|
|
||||||
"tags":[
|
|
||||||
"Defi"
|
|
||||||
],
|
|
||||||
"websiteLink":"https://0x.org/",
|
|
||||||
"whitepaperLink":"https://0x.org/pdfs/0x_white_paper.pdf",
|
|
||||||
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/0x/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -199,7 +199,7 @@
|
|||||||
},
|
},
|
||||||
"mobile": {
|
"mobile": {
|
||||||
"native": "trust:",
|
"native": "trust:",
|
||||||
"universal": "https://link.trustwallet.com"
|
"universal": ""
|
||||||
},
|
},
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"native": "",
|
"native": "",
|
||||||
|
|||||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,230 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 481 KiB |
|
Before Width: | Height: | Size: 817 B After Width: | Height: | Size: 297 B |
@@ -1,7 +1,9 @@
|
|||||||
import { Suspense } from 'react';
|
import { lazy, Suspense } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||||
import { WagmiConfig } from 'wagmi';
|
import { WagmiConfig } from 'wagmi';
|
||||||
|
import { PrivyProvider } from '@privy-io/react-auth';
|
||||||
|
import { PrivyWagmiConnector } from '@privy-io/wagmi-connector';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
import { GrazProvider } from 'graz';
|
import { GrazProvider } from 'graz';
|
||||||
|
|
||||||
@@ -22,21 +24,9 @@ import { NotificationsProvider } from '@/hooks/useNotifications';
|
|||||||
import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
|
import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
|
||||||
import { RestrictionProvider } from '@/hooks/useRestrictions';
|
import { RestrictionProvider } from '@/hooks/useRestrictions';
|
||||||
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
||||||
import { SquidProvider } from '@/hooks/useSquid';
|
|
||||||
import { TestFlagsProvider } from '@/hooks/useTestFlags';
|
|
||||||
|
|
||||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||||
|
|
||||||
import MarketsPage from '@/pages/markets/Markets';
|
|
||||||
import PortfolioPage from '@/pages/portfolio/Portfolio';
|
|
||||||
import { AlertsPage } from '@/pages/AlertsPage';
|
|
||||||
import ProfilePage from '@/pages/Profile';
|
|
||||||
import { SettingsPage } from '@/pages/settings/Settings';
|
|
||||||
import TradePage from '@/pages/trade/Trade';
|
|
||||||
import { RewardsPage } from '@/pages/rewards/RewardsPage';
|
|
||||||
import { TermsOfUsePage } from '@/pages/TermsOfUsePage';
|
|
||||||
import { PrivacyPolicyPage } from '@/pages/PrivacyPolicyPage';
|
|
||||||
|
|
||||||
import { HeaderDesktop } from '@/layout/Header/HeaderDesktop';
|
import { HeaderDesktop } from '@/layout/Header/HeaderDesktop';
|
||||||
import { FooterDesktop } from '@/layout/Footer/FooterDesktop';
|
import { FooterDesktop } from '@/layout/Footer/FooterDesktop';
|
||||||
import { FooterMobile } from '@/layout/Footer/FooterMobile';
|
import { FooterMobile } from '@/layout/Footer/FooterMobile';
|
||||||
@@ -44,15 +34,26 @@ import { NotificationsToastArea } from '@/layout/NotificationsToastArea';
|
|||||||
import { DialogManager } from '@/layout/DialogManager';
|
import { DialogManager } from '@/layout/DialogManager';
|
||||||
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
|
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
|
||||||
|
|
||||||
import { config } from '@/lib/wagmi';
|
import { config, privyConfig } from '@/lib/wagmi';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
import { LoadingSpace } from './components/Loading/LoadingSpinner';
|
||||||
|
|
||||||
import '@/styles/constants.css';
|
import '@/styles/constants.css';
|
||||||
import '@/styles/fonts.css';
|
import '@/styles/fonts.css';
|
||||||
import '@/styles/web3modal.css';
|
import '@/styles/web3modal.css';
|
||||||
|
|
||||||
|
const MarketsPage = lazy(() => import('@/pages/markets/Markets'));
|
||||||
|
const PortfolioPage = lazy(() => import('@/pages/portfolio/Portfolio'));
|
||||||
|
const AlertsPage = lazy(() => import('@/pages/AlertsPage'));
|
||||||
|
const ProfilePage = lazy(() => import('@/pages/Profile'));
|
||||||
|
const SettingsPage = lazy(() => import('@/pages/settings/Settings'));
|
||||||
|
const TradePage = lazy(() => import('@/pages/trade/Trade'));
|
||||||
|
const RewardsPage = lazy(() => import('@/pages/rewards/RewardsPage'));
|
||||||
|
const TermsOfUsePage = lazy(() => import('@/pages/TermsOfUsePage'));
|
||||||
|
const PrivacyPolicyPage = lazy(() => import('@/pages/PrivacyPolicyPage'));
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const Content = () => {
|
const Content = () => {
|
||||||
@@ -71,7 +72,7 @@ const Content = () => {
|
|||||||
{isNotTablet && <HeaderDesktop />}
|
{isNotTablet && <HeaderDesktop />}
|
||||||
|
|
||||||
<Styled.Main>
|
<Styled.Main>
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={<LoadingSpace id="main" />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={AppRoute.Trade}>
|
<Route path={AppRoute.Trade}>
|
||||||
<Route path=":market" element={<TradePage />} />
|
<Route path=":market" element={<TradePage />} />
|
||||||
@@ -121,16 +122,19 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const providers = [
|
const providers = [
|
||||||
|
wrapProvider(PrivyProvider, {
|
||||||
|
appId: import.meta.env.VITE_PRIVY_APP_ID,
|
||||||
|
config: privyConfig,
|
||||||
|
}),
|
||||||
wrapProvider(QueryClientProvider, { client: queryClient }),
|
wrapProvider(QueryClientProvider, { client: queryClient }),
|
||||||
wrapProvider(GrazProvider),
|
wrapProvider(GrazProvider),
|
||||||
|
wrapProvider(PrivyWagmiConnector, { wagmiChainsConfig: config }),
|
||||||
wrapProvider(WagmiConfig, { config }),
|
wrapProvider(WagmiConfig, { config }),
|
||||||
wrapProvider(TestFlagsProvider),
|
|
||||||
wrapProvider(LocaleProvider),
|
wrapProvider(LocaleProvider),
|
||||||
wrapProvider(RestrictionProvider),
|
wrapProvider(RestrictionProvider),
|
||||||
wrapProvider(DydxProvider),
|
wrapProvider(DydxProvider),
|
||||||
wrapProvider(AccountsProvider),
|
wrapProvider(AccountsProvider),
|
||||||
wrapProvider(SubaccountProvider),
|
wrapProvider(SubaccountProvider),
|
||||||
wrapProvider(SquidProvider),
|
|
||||||
wrapProvider(LocalNotificationsProvider),
|
wrapProvider(LocalNotificationsProvider),
|
||||||
wrapProvider(NotificationsProvider),
|
wrapProvider(NotificationsProvider),
|
||||||
wrapProvider(DialogAreaProvider),
|
wrapProvider(DialogAreaProvider),
|
||||||
|
|||||||
@@ -0,0 +1,390 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "name",
|
||||||
|
"outputs": [{ "name": "", "type": "string" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [{ "name": "_upgradedAddress", "type": "address" }],
|
||||||
|
"name": "deprecate",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "name": "_spender", "type": "address" },
|
||||||
|
{ "name": "_value", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "approve",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "deprecated",
|
||||||
|
"outputs": [{ "name": "", "type": "bool" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [{ "name": "_evilUser", "type": "address" }],
|
||||||
|
"name": "addBlackList",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "totalSupply",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "name": "_from", "type": "address" },
|
||||||
|
{ "name": "_to", "type": "address" },
|
||||||
|
{ "name": "_value", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "transferFrom",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "upgradedAddress",
|
||||||
|
"outputs": [{ "name": "", "type": "address" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [{ "name": "", "type": "address" }],
|
||||||
|
"name": "balances",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "decimals",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "maximumFee",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "_totalSupply",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "unpause",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [{ "name": "_maker", "type": "address" }],
|
||||||
|
"name": "getBlackListStatus",
|
||||||
|
"outputs": [{ "name": "", "type": "bool" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{ "name": "", "type": "address" },
|
||||||
|
{ "name": "", "type": "address" }
|
||||||
|
],
|
||||||
|
"name": "allowed",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "paused",
|
||||||
|
"outputs": [{ "name": "", "type": "bool" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [{ "name": "who", "type": "address" }],
|
||||||
|
"name": "balanceOf",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "pause",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "getOwner",
|
||||||
|
"outputs": [{ "name": "", "type": "address" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "owner",
|
||||||
|
"outputs": [{ "name": "", "type": "address" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "symbol",
|
||||||
|
"outputs": [{ "name": "", "type": "string" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "name": "_to", "type": "address" },
|
||||||
|
{ "name": "_value", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "transfer",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "name": "newBasisPoints", "type": "uint256" },
|
||||||
|
{ "name": "newMaxFee", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "setParams",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [{ "name": "amount", "type": "uint256" }],
|
||||||
|
"name": "issue",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [{ "name": "amount", "type": "uint256" }],
|
||||||
|
"name": "redeem",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{ "name": "_owner", "type": "address" },
|
||||||
|
{ "name": "_spender", "type": "address" }
|
||||||
|
],
|
||||||
|
"name": "allowance",
|
||||||
|
"outputs": [{ "name": "remaining", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "basisPointsRate",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [{ "name": "", "type": "address" }],
|
||||||
|
"name": "isBlackListed",
|
||||||
|
"outputs": [{ "name": "", "type": "bool" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [{ "name": "_clearedUser", "type": "address" }],
|
||||||
|
"name": "removeBlackList",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "MAX_UINT",
|
||||||
|
"outputs": [{ "name": "", "type": "uint256" }],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [{ "name": "newOwner", "type": "address" }],
|
||||||
|
"name": "transferOwnership",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [{ "name": "_blackListedUser", "type": "address" }],
|
||||||
|
"name": "destroyBlackFunds",
|
||||||
|
"outputs": [],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{ "name": "_initialSupply", "type": "uint256" },
|
||||||
|
{ "name": "_name", "type": "string" },
|
||||||
|
{ "name": "_symbol", "type": "string" },
|
||||||
|
{ "name": "_decimals", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"payable": false,
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "constructor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }],
|
||||||
|
"name": "Issue",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }],
|
||||||
|
"name": "Redeem",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{ "indexed": false, "name": "newAddress", "type": "address" }],
|
||||||
|
"name": "Deprecate",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": false, "name": "feeBasisPoints", "type": "uint256" },
|
||||||
|
{ "indexed": false, "name": "maxFee", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "Params",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": false, "name": "_blackListedUser", "type": "address" },
|
||||||
|
{ "indexed": false, "name": "_balance", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "DestroyedBlackFunds",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{ "indexed": false, "name": "_user", "type": "address" }],
|
||||||
|
"name": "AddedBlackList",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{ "indexed": false, "name": "_user", "type": "address" }],
|
||||||
|
"name": "RemovedBlackList",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": true, "name": "owner", "type": "address" },
|
||||||
|
{ "indexed": true, "name": "spender", "type": "address" },
|
||||||
|
{ "indexed": false, "name": "value", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "Approval",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": true, "name": "from", "type": "address" },
|
||||||
|
{ "indexed": true, "name": "to", "type": "address" },
|
||||||
|
{ "indexed": false, "name": "value", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "Transfer",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{ "anonymous": false, "inputs": [], "name": "Pause", "type": "event" },
|
||||||
|
{ "anonymous": false, "inputs": [], "name": "Unpause", "type": "event" }
|
||||||
|
]
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
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 = {
|
||||||
|
header: React.ReactNode;
|
||||||
|
content: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccordionProps = {
|
||||||
|
items: AccordionItem[];
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Accordion = ({ items, className }: AccordionProps) => (
|
||||||
|
<Styled.Root className={className} type="single" collapsible>
|
||||||
|
{items.map(({ header, content }, idx) => (
|
||||||
|
<Styled.Item key={idx} value={idx.toString()}>
|
||||||
|
<Header>
|
||||||
|
<Styled.Trigger>
|
||||||
|
{header}
|
||||||
|
<Styled.Icon>
|
||||||
|
<PlusIcon />
|
||||||
|
</Styled.Icon>
|
||||||
|
</Styled.Trigger>
|
||||||
|
</Header>
|
||||||
|
<Styled.Content>{content}</Styled.Content>
|
||||||
|
</Styled.Item>
|
||||||
|
))}
|
||||||
|
</Styled.Root>
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Item = styled(Item)``;
|
||||||
|
|
||||||
|
Styled.Icon = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
|
||||||
|
--color-border: var(--color-layer-6);
|
||||||
|
color: var(--color-text-1);
|
||||||
|
background-color: var(--color-layer-5);
|
||||||
|
border: solid var(--border-width) var(--color-border);
|
||||||
|
border-radius: 50%;
|
||||||
|
font: var(--font-small-book);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 1.125em;
|
||||||
|
width: 1.125em;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Trigger = styled(Trigger)`
|
||||||
|
${layoutMixins.spacedRow}
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--accordion-paddingY) var(--accordion-paddingX);
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
color: var(--color-text-1);
|
||||||
|
text-align: start;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
${Styled.Icon} {
|
||||||
|
color: var(--color-text-2);
|
||||||
|
filter: brightness(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: var(--color-text-0);
|
||||||
|
transition: transform 0.3s var(--ease-out-expo);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-state='open'] svg {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Content = styled(Content)`
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY);
|
||||||
|
|
||||||
|
color: var(--color-text-0);
|
||||||
|
|
||||||
|
&[data-state='open'] {
|
||||||
|
animation: ${keyframes`
|
||||||
|
from {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
`} 0.3s var(--ease-out-expo);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-state='closed'] {
|
||||||
|
animation: ${keyframes`
|
||||||
|
from {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
`} 0.1s var(--ease-in-expo);
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -45,6 +45,7 @@ const assetIcons = {
|
|||||||
SOL: '/currencies/sol.png',
|
SOL: '/currencies/sol.png',
|
||||||
SUI: '/currencies/sui.png',
|
SUI: '/currencies/sui.png',
|
||||||
SUSHI: '/currencies/sushi.png',
|
SUSHI: '/currencies/sushi.png',
|
||||||
|
TIA: '/currencies/tia.png',
|
||||||
TRX: '/currencies/trx.png',
|
TRX: '/currencies/trx.png',
|
||||||
UMA: '/currencies/uma.png',
|
UMA: '/currencies/uma.png',
|
||||||
UNI: '/currencies/uni.png',
|
UNI: '/currencies/uni.png',
|
||||||
@@ -71,8 +72,13 @@ export const AssetIcon = ({
|
|||||||
}: {
|
}: {
|
||||||
symbol?: Nullable<string>;
|
symbol?: Nullable<string>;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) =>
|
}) => (
|
||||||
isAssetSymbol(symbol) ? <Styled.Img src={assetIcons[symbol]} className={className} /> : null;
|
<Styled.Img
|
||||||
|
src={isAssetSymbol(symbol) ? assetIcons[symbol] : '/currencies/unavailable.png'}
|
||||||
|
className={className}
|
||||||
|
alt={symbol}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledBaseButton
|
<StyledBaseButton
|
||||||
disabled={state[ButtonState.Disabled]}
|
disabled={state[ButtonState.Disabled] || state[ButtonState.Loading]}
|
||||||
{...{ ref, action, size, shape, state, ...otherProps }}
|
{...{ ref, action, size, shape, state, ...otherProps }}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ export const Checkboxes: Story<CheckboxProps> = (args) => {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
{...args}
|
{...args}
|
||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onClick={(e: React.ChangeEvent) => setIsChecked((e.target as HTMLInputElement).checked)}
|
onCheckedChange={setIsChecked}
|
||||||
|
id="story-checkbox"
|
||||||
|
label="example label"
|
||||||
/>
|
/>
|
||||||
</StoryWrapper>
|
</StoryWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import styled, { 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';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
onClick: (e: React.ChangeEvent) => void;
|
onCheckedChange: (checked: boolean) => void;
|
||||||
|
id?: string;
|
||||||
|
label?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
@@ -13,85 +17,54 @@ type StyleProps = {
|
|||||||
|
|
||||||
export type CheckboxProps = ElementProps & StyleProps;
|
export type CheckboxProps = ElementProps & StyleProps;
|
||||||
|
|
||||||
export const Checkbox: React.FC<CheckboxProps> = ({ checked, className, onClick }) => {
|
export const Checkbox: React.FC<CheckboxProps> = ({
|
||||||
return (
|
checked,
|
||||||
<Styled.CheckboxWrapper className={className}>
|
className,
|
||||||
<Styled.Checkbox
|
onCheckedChange,
|
||||||
type="checkbox"
|
id,
|
||||||
checked={checked}
|
label,
|
||||||
onChange={onClick}
|
}) => (
|
||||||
/>
|
<Styled.Container>
|
||||||
<Styled.CustomCheckbox />
|
<Styled.Root className={className} checked={checked} onCheckedChange={onCheckedChange} id={id}>
|
||||||
</Styled.CheckboxWrapper>
|
<Styled.Indicator>
|
||||||
);
|
<CheckIcon />
|
||||||
};
|
</Styled.Indicator>
|
||||||
|
</Styled.Root>
|
||||||
|
{label && <label htmlFor={id}>{label}</label>}
|
||||||
|
</Styled.Container>
|
||||||
|
);
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
Styled.CustomCheckbox = styled.span`
|
Styled.Container = styled.div`
|
||||||
width: 1.25em;
|
${layoutMixins.row}
|
||||||
height: 1.25em;
|
gap: 1ch;
|
||||||
|
font: var(--font-small-book);
|
||||||
|
|
||||||
position: absolute;
|
label {
|
||||||
left: 0;
|
cursor: pointer;
|
||||||
right: 0;
|
}
|
||||||
top: 0;
|
`;
|
||||||
bottom: 0;
|
|
||||||
|
Styled.Root = styled(Root)`
|
||||||
|
--checkbox-backgroundColor: var(--color-layer-0);
|
||||||
|
|
||||||
|
min-width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: var(--border-width) solid var(--color-border);
|
||||||
background-color: var(--checkbox-backgroundColor);
|
background-color: var(--checkbox-backgroundColor);
|
||||||
|
|
||||||
&::after {
|
&[data-state='checked'] {
|
||||||
position: absolute;
|
--checkbox-backgroundColor: var(--color-accent);
|
||||||
content: '';
|
|
||||||
top: 0.25em;
|
|
||||||
left: 0.4375em;
|
|
||||||
width: 0.3125em;
|
|
||||||
height: 0.5em;
|
|
||||||
border: solid var(--checkbox-checkColor);
|
|
||||||
border-width: 0 0.125em 0.125em 0;
|
|
||||||
border-radius: 0.0625em;
|
|
||||||
opacity: 0;
|
|
||||||
transform: rotate(0deg) scale(0);
|
|
||||||
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media ${breakpoints.tablet} {
|
|
||||||
width: 1.5em;
|
|
||||||
height: 1.5em;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
top: 0.25em;
|
|
||||||
left: 0.4375em;
|
|
||||||
width: 0.4375em;
|
|
||||||
height: 0.625em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.CheckboxWrapper = styled.div`
|
Styled.Indicator = styled(Indicator)`
|
||||||
--checkbox-backgroundColor: var(--color-layer-1);
|
|
||||||
--checkbox-checkColor: var(--color-text-1);
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 0.25em;
|
align-items: center;
|
||||||
overflow: hidden;
|
justify-content: center;
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
> input:checked ~ ${Styled.CustomCheckbox}::after {
|
color: var(--color-text-2);
|
||||||
opacity: 1;
|
|
||||||
transform: rotate(40deg) scale(1);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Checkbox = styled.input`
|
|
||||||
width: 1.25em;
|
|
||||||
height: 1.25em;
|
|
||||||
z-index: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
@media ${breakpoints.tablet} {
|
|
||||||
width: 1.5em;
|
|
||||||
height: 1.5em;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import type { Story } from '@ladle/react';
|
import type { Story } from '@ladle/react';
|
||||||
|
|
||||||
import { Collapsible } from '@/components/Collapsible';
|
import { Collapsible, type CollapsibleProps } from '@/components/Collapsible';
|
||||||
|
|
||||||
import { StoryWrapper } from '.ladle/components';
|
import { StoryWrapper } from '.ladle/components';
|
||||||
import { IconName } from './Icon';
|
import { IconName } from './Icon';
|
||||||
|
|
||||||
export const CollapsibleStory: Story<Parameters<typeof Collapsible>> = (args) => (
|
export const CollapsibleStory: Story<CollapsibleProps> = (args) => (
|
||||||
<StoryWrapper>
|
<StoryWrapper>
|
||||||
<Collapsible
|
<Collapsible {...args}>
|
||||||
label="Collapsible List of Items"
|
|
||||||
{...args}
|
|
||||||
>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Collapsible Item 1</li>
|
<li>Collapsible Item 1</li>
|
||||||
<li>Collapsible Item 2</li>
|
<li>Collapsible Item 2</li>
|
||||||
@@ -23,6 +20,8 @@ export const CollapsibleStory: Story<Parameters<typeof Collapsible>> = (args) =>
|
|||||||
|
|
||||||
CollapsibleStory.args = {
|
CollapsibleStory.args = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
withTrigger: true,
|
||||||
|
label: 'Collapsible List of Items',
|
||||||
};
|
};
|
||||||
|
|
||||||
CollapsibleStory.argTypes = {
|
CollapsibleStory.argTypes = {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type ElementProps = {
|
|||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
triggerIcon?: IconName;
|
triggerIcon?: IconName;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
withTrigger?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
@@ -23,6 +24,8 @@ type StyleProps = {
|
|||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CollapsibleProps = ElementProps & StyleProps;
|
||||||
|
|
||||||
export const Collapsible = ({
|
export const Collapsible = ({
|
||||||
defaultOpen = false,
|
defaultOpen = false,
|
||||||
disabled,
|
disabled,
|
||||||
@@ -35,26 +38,29 @@ export const Collapsible = ({
|
|||||||
triggerIconSide = 'left',
|
triggerIconSide = 'left',
|
||||||
fullWidth,
|
fullWidth,
|
||||||
className,
|
className,
|
||||||
}: ElementProps & StyleProps) => (
|
withTrigger = true,
|
||||||
|
}: CollapsibleProps) => (
|
||||||
<Styled.Root defaultOpen={defaultOpen} open={open} onOpenChange={onOpenChange}>
|
<Styled.Root defaultOpen={defaultOpen} open={open} onOpenChange={onOpenChange}>
|
||||||
<Styled.Trigger className={className} disabled={disabled}>
|
{withTrigger && (
|
||||||
{triggerIconSide === 'right' && (
|
<Styled.Trigger className={className} disabled={disabled}>
|
||||||
<>
|
{triggerIconSide === 'right' && (
|
||||||
{label}
|
<>
|
||||||
{fullWidth && <HorizontalSeparatorFiller />}
|
{label}
|
||||||
</>
|
{fullWidth && <HorizontalSeparatorFiller />}
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Styled.TriggerIcon>
|
<Styled.TriggerIcon>
|
||||||
<Icon iconName={triggerIcon} />
|
<Icon iconName={triggerIcon} />
|
||||||
</Styled.TriggerIcon>
|
</Styled.TriggerIcon>
|
||||||
{triggerIconSide === 'left' && (
|
{triggerIconSide === 'left' && (
|
||||||
<>
|
<>
|
||||||
{fullWidth && <HorizontalSeparatorFiller />}
|
{fullWidth && <HorizontalSeparatorFiller />}
|
||||||
{label}
|
{label}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Styled.Trigger>
|
</Styled.Trigger>
|
||||||
|
)}
|
||||||
<Styled.Content $transitionDuration={transitionDuration}>{children}</Styled.Content>
|
<Styled.Content $transitionDuration={transitionDuration}>{children}</Styled.Content>
|
||||||
</Styled.Root>
|
</Styled.Root>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ type ElementProps<TabItemsValue> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
fullWidthTabs?: boolean;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
fullWidthTabs?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CollapsibleTabsProps<TabItemsValue> = ElementProps<TabItemsValue> & StyleProps;
|
||||||
|
|
||||||
export const CollapsibleTabs = <TabItemsValue extends string>({
|
export const CollapsibleTabs = <TabItemsValue extends string>({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
items,
|
items,
|
||||||
@@ -43,8 +45,9 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
|
|
||||||
fullWidthTabs,
|
fullWidthTabs,
|
||||||
|
|
||||||
className,
|
className,
|
||||||
}: ElementProps<TabItemsValue> & StyleProps) => {
|
}: CollapsibleTabsProps<TabItemsValue>) => {
|
||||||
const [value, setValue] = useState(defaultValue);
|
const [value, setValue] = useState(defaultValue);
|
||||||
|
|
||||||
const currentItem = items.find((item) => item.value === value);
|
const currentItem = items.find((item) => item.value === value);
|
||||||
@@ -86,8 +89,8 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
|
|||||||
</Styled.Header>
|
</Styled.Header>
|
||||||
|
|
||||||
<Styled.CollapsibleContent>
|
<Styled.CollapsibleContent>
|
||||||
{items.map(({ value, content }) => (
|
{items.map(({ asChild, value, content }) => (
|
||||||
<Styled.TabsContent key={value} value={value}>
|
<Styled.TabsContent key={value} asChild={asChild} value={value}>
|
||||||
{content}
|
{content}
|
||||||
</Styled.TabsContent>
|
</Styled.TabsContent>
|
||||||
))}
|
))}
|
||||||
@@ -219,7 +222,6 @@ Styled.Header = styled.header`
|
|||||||
|
|
||||||
Styled.CollapsibleContent = styled(CollapsibleContent)`
|
Styled.CollapsibleContent = styled(CollapsibleContent)`
|
||||||
${layoutMixins.stack}
|
${layoutMixins.stack}
|
||||||
${layoutMixins.perspectiveArea}
|
|
||||||
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -22,7 +22,12 @@ type PickComboxMenuProps<
|
|||||||
MenuGroupValue extends string | number
|
MenuGroupValue extends string | number
|
||||||
> = Pick<
|
> = Pick<
|
||||||
ComboboxMenuProps<MenuItemValue, MenuGroupValue>,
|
ComboboxMenuProps<MenuItemValue, MenuGroupValue>,
|
||||||
'inputPlaceholder' | 'onItemSelected' | 'slotEmpty' | 'withSearch' | 'withStickyLayout'
|
| 'inputPlaceholder'
|
||||||
|
| 'onItemSelected'
|
||||||
|
| 'slotEmpty'
|
||||||
|
| 'withItemBorders'
|
||||||
|
| 'withSearch'
|
||||||
|
| 'withStickyLayout'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type PickDialogProps = Pick<
|
type PickDialogProps = Pick<
|
||||||
@@ -53,6 +58,7 @@ export const ComboboxDialogMenu = <
|
|||||||
onItemSelected,
|
onItemSelected,
|
||||||
inputPlaceholder,
|
inputPlaceholder,
|
||||||
slotEmpty,
|
slotEmpty,
|
||||||
|
withItemBorders,
|
||||||
withSearch,
|
withSearch,
|
||||||
withStickyLayout = true,
|
withStickyLayout = true,
|
||||||
children,
|
children,
|
||||||
@@ -83,6 +89,7 @@ export const ComboboxDialogMenu = <
|
|||||||
title={title}
|
title={title}
|
||||||
inputPlaceholder={inputPlaceholder}
|
inputPlaceholder={inputPlaceholder}
|
||||||
slotEmpty={slotEmpty}
|
slotEmpty={slotEmpty}
|
||||||
|
withItemBorders={withItemBorders}
|
||||||
withSearch={withSearch}
|
withSearch={withSearch}
|
||||||
withStickyLayout={withStickyLayout}
|
withStickyLayout={withStickyLayout}
|
||||||
/>
|
/>
|
||||||
@@ -95,6 +102,8 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
Styled.Dialog = styled(Dialog)`
|
Styled.Dialog = styled(Dialog)`
|
||||||
/* Params */
|
/* Params */
|
||||||
--comboboxDialogMenu-backgroundColor: var(--color-layer-2);
|
--comboboxDialogMenu-backgroundColor: var(--color-layer-2);
|
||||||
|
--comboboxDialogMenu-item-gap: 0.5rem;
|
||||||
|
--comboboxDialogMenu-item-padding: 0.5rem 1rem;
|
||||||
|
|
||||||
/* Overrides */
|
/* Overrides */
|
||||||
& {
|
& {
|
||||||
@@ -113,10 +122,12 @@ Styled.Dialog = styled(Dialog)`
|
|||||||
|
|
||||||
/* Net 0 sticky top inset (let stickyArea1 header stick to top) */
|
/* Net 0 sticky top inset (let stickyArea1 header stick to top) */
|
||||||
--stickyArea0-topGap: calc(-1 * var(--stickyArea0-topHeight));
|
--stickyArea0-topGap: calc(-1 * var(--stickyArea0-topHeight));
|
||||||
|
overflow-x: clip;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ComboboxMenu = styled(ComboboxMenu)`
|
Styled.ComboboxMenu = styled(ComboboxMenu)`
|
||||||
--comboboxMenu-backgroundColor: var(--comboboxDialogMenu-backgroundColor);
|
--comboboxMenu-backgroundColor: var(--comboboxDialogMenu-backgroundColor);
|
||||||
--comboboxMenu-item-gap: var(--comboxDialogMenu-item-gap, 0.5rem);
|
--comboboxMenu-item-gap: var(--comboboxDialogMenu-item-gap);
|
||||||
|
--comboboxMenu-item-padding: var(--comboboxDialogMenu-item-padding);
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Fragment, type ReactNode, useState } from 'react';
|
|||||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
|
|
||||||
import { type MenuConfig } from '@/constants/menus';
|
import { MenuItem, type MenuConfig } from '@/constants/menus';
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ type ElementProps<MenuItemValue extends string | number, MenuGroupValue extends
|
|||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
withItemBorders?: boolean;
|
||||||
withStickyLayout?: boolean;
|
withStickyLayout?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,17 +39,11 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
withSearch = true,
|
withSearch = true,
|
||||||
|
|
||||||
className,
|
className,
|
||||||
|
withItemBorders,
|
||||||
withStickyLayout,
|
withStickyLayout,
|
||||||
}: ComboboxMenuProps<MenuItemValue, MenuGroupValue>) => {
|
}: ComboboxMenuProps<MenuItemValue, MenuGroupValue>) => {
|
||||||
const [highlightedCommand, setHighlightedCommand] = useState<MenuItemValue>();
|
const [highlightedCommand, setHighlightedCommand] = useState<MenuItemValue>();
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
// const inputRef = useRef<HTMLInputElement | null>(null);
|
|
||||||
|
|
||||||
// console.log({ commandValue: highlightedCommand });
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// inputRef?.current?.focus();
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Command
|
<Styled.Command
|
||||||
@@ -65,11 +60,14 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
{withSearch && (
|
{withSearch && (
|
||||||
<Styled.Header $withStickyLayout={withStickyLayout}>
|
<Styled.Header $withStickyLayout={withStickyLayout}>
|
||||||
<Styled.Input
|
<Styled.Input
|
||||||
// ref={inputRef}
|
/**
|
||||||
|
* Mobile Issue: Search Input will always trigger mobile keyboard drawer. There is no fix.
|
||||||
|
* https://github.com/pacocoursey/cmdk/issues/127
|
||||||
|
*/
|
||||||
|
autoFocus
|
||||||
type="search"
|
type="search"
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onValueChange={setSearchValue}
|
onValueChange={setSearchValue}
|
||||||
autoFocus
|
|
||||||
placeholder={inputPlaceholder}
|
placeholder={inputPlaceholder}
|
||||||
/>
|
/>
|
||||||
</Styled.Header>
|
</Styled.Header>
|
||||||
@@ -80,6 +78,7 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
<Styled.Group
|
<Styled.Group
|
||||||
key={group.group}
|
key={group.group}
|
||||||
heading={group.groupLabel}
|
heading={group.groupLabel}
|
||||||
|
$withItemBorders={withItemBorders}
|
||||||
$withStickyLayout={withStickyLayout}
|
$withStickyLayout={withStickyLayout}
|
||||||
>
|
>
|
||||||
{group.items.map((item) => (
|
{group.items.map((item) => (
|
||||||
@@ -99,22 +98,31 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
|
$withItemBorders={withItemBorders}
|
||||||
>
|
>
|
||||||
{item.slotBefore}
|
{
|
||||||
<Styled.ItemLabel>
|
<>
|
||||||
<span>
|
{item.slotBefore}
|
||||||
{`${item.label}${item.subitems?.length ? '…' : ''}`}
|
{item.slotCustomContent ?? (
|
||||||
{item.tag && (
|
<Styled.ItemLabel>
|
||||||
<>
|
<span>
|
||||||
{' '}
|
{typeof item.label === 'string'
|
||||||
<Tag>{item.tag}</Tag>
|
? `${item.label}${item.subitems?.length ? '…' : ''}`
|
||||||
</>
|
: item.label}
|
||||||
|
{item.tag && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
<Tag>{item.tag}</Tag>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{item.description && <span>{item.description}</span>}
|
||||||
|
</Styled.ItemLabel>
|
||||||
)}
|
)}
|
||||||
</span>
|
{item.slotAfter}
|
||||||
{item.description && <span>{item.description}</span>}
|
{item.subitems && '→'}
|
||||||
</Styled.ItemLabel>
|
</>
|
||||||
{item.slotAfter}
|
}
|
||||||
{item.subitems && '→'}
|
|
||||||
</Styled.Item>
|
</Styled.Item>
|
||||||
|
|
||||||
{searchValue &&
|
{searchValue &&
|
||||||
@@ -137,6 +145,7 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
onItemSelected?.();
|
onItemSelected?.();
|
||||||
}}
|
}}
|
||||||
disabled={subitem.disabled}
|
disabled={subitem.disabled}
|
||||||
|
$withItemBorders={withItemBorders}
|
||||||
>
|
>
|
||||||
{subitem.slotBefore}
|
{subitem.slotBefore}
|
||||||
<Styled.ItemLabel>
|
<Styled.ItemLabel>
|
||||||
@@ -169,14 +178,17 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
|
|
||||||
Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
|
Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
|
||||||
--comboboxMenu-backgroundColor: var(--color-layer-2);
|
--comboboxMenu-backgroundColor: var(--color-layer-2);
|
||||||
|
|
||||||
--comboboxMenu-input-backgroundColor: var(--color-layer-3);
|
--comboboxMenu-input-backgroundColor: var(--color-layer-3);
|
||||||
--comboboxMenu-input-height: 2.5rem;
|
--comboboxMenu-input-height: 2.5rem;
|
||||||
|
|
||||||
--comboboxMenu-item-checked-backgroundColor: ;
|
--comboboxMenu-item-checked-backgroundColor: ;
|
||||||
--comboboxMenu-item-checked-textColor: ;
|
--comboboxMenu-item-checked-textColor: ;
|
||||||
--comboboxMenu-item-highlighted-backgroundColor: var(--color-layer-3);
|
--comboboxMenu-item-highlighted-backgroundColor: var(--color-layer-3);
|
||||||
--comboboxMenu-item-highlighted-textColor: var(--color-text-1);
|
--comboboxMenu-item-highlighted-textColor: var(--color-text-1);
|
||||||
--comboboxMenu-item-backgroundColor: ;
|
--comboboxMenu-item-backgroundColor: ;
|
||||||
--comboboxMenu-item-gap: 0.5rem;
|
--comboboxMenu-item-gap: 0.5rem;
|
||||||
|
--comboboxMenu-item-padding: 0.5em 1em;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
@@ -225,7 +237,7 @@ Styled.Input = styled(Command.Input)`
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Group = styled(Command.Group)<{ $withStickyLayout?: boolean }>`
|
Styled.Group = styled(Command.Group)<{ $withItemBorders?: boolean; $withStickyLayout?: boolean }>`
|
||||||
color: var(--color-text-0);
|
color: var(--color-text-0);
|
||||||
|
|
||||||
> [cmdk-group-heading] {
|
> [cmdk-group-heading] {
|
||||||
@@ -242,12 +254,21 @@ Styled.Group = styled(Command.Group)<{ $withStickyLayout?: boolean }>`
|
|||||||
|
|
||||||
> [cmdk-group-heading] {
|
> [cmdk-group-heading] {
|
||||||
${layoutMixins.stickyHeader}
|
${layoutMixins.stickyHeader}
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
> [cmdk-group-items] {
|
> [cmdk-group-items] {
|
||||||
${layoutMixins.stickyArea3}
|
${layoutMixins.stickyArea3}
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
|
||||||
|
${({ $withItemBorders }) =>
|
||||||
|
$withItemBorders &&
|
||||||
|
css`
|
||||||
|
> [cmdk-group-items] {
|
||||||
|
padding: var(--border-width) 0;
|
||||||
|
}
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
|
Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
|
||||||
@@ -274,13 +295,14 @@ Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
|
|||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Item = styled(Command.Item)`
|
Styled.Item = styled(Command.Item)<{ $withItemBorders?: boolean }>`
|
||||||
${layoutMixins.scrollSnapItem}
|
${layoutMixins.scrollSnapItem}
|
||||||
${popoverMixins.item}
|
${popoverMixins.item}
|
||||||
--item-checked-backgroundColor: var(--comboboxMenu-item-checked-backgroundColor);
|
--item-checked-backgroundColor: var(--comboboxMenu-item-checked-backgroundColor);
|
||||||
--item-checked-textColor: var(--comboboxMenu-item-checked-textColor);
|
--item-checked-textColor: var(--comboboxMenu-item-checked-textColor);
|
||||||
--item-highlighted-textColor: var(--comboboxMenu-item-highlighted-textColor);
|
--item-highlighted-textColor: var(--comboboxMenu-item-highlighted-textColor);
|
||||||
--item-gap: var(--comboboxMenu-item-gap);
|
--item-gap: var(--comboboxMenu-item-gap);
|
||||||
|
--item-padding: var(--comboboxMenu-item-padding);
|
||||||
|
|
||||||
background-color: var(--comboboxMenu-backgroundColor, inherit);
|
background-color: var(--comboboxMenu-backgroundColor, inherit);
|
||||||
|
|
||||||
@@ -291,6 +313,12 @@ Styled.Item = styled(Command.Item)`
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${({ $withItemBorders }) =>
|
||||||
|
$withItemBorders &&
|
||||||
|
css`
|
||||||
|
${layoutMixins.withOuterBorder}
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ItemLabel = styled.div`
|
Styled.ItemLabel = styled.div`
|
||||||
@@ -309,6 +337,8 @@ Styled.ItemLabel = styled.div`
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Empty = styled(Command.Empty)`
|
Styled.Empty = styled(Command.Empty)`
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ CopyButtonStory.args = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
CopyButtonStory.argTypes = {
|
CopyButtonStory.argTypes = {
|
||||||
shownAsText: {
|
buttonType: {
|
||||||
options: [true, false],
|
options: ["text", "icon", "default"],
|
||||||
control: { type: 'select' },
|
control: { type: 'select' },
|
||||||
defaultValue: false,
|
defaultValue: "default",
|
||||||
},
|
},
|
||||||
children: {
|
children: {
|
||||||
options: ['some text to copy'],
|
options: ['some text to copy'],
|
||||||
|
|||||||
@@ -9,14 +9,21 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
|||||||
|
|
||||||
import { Button, ButtonProps } from './Button';
|
import { Button, ButtonProps } from './Button';
|
||||||
import { Icon, IconName } from './Icon';
|
import { Icon, IconName } from './Icon';
|
||||||
|
import { IconButton } from './IconButton';
|
||||||
|
import { WithTooltip } from './WithTooltip';
|
||||||
|
|
||||||
export type CopyButtonProps = {
|
export type CopyButtonProps = {
|
||||||
value?: string;
|
value?: string;
|
||||||
shownAsText?: boolean;
|
buttonType?: 'text' | 'icon' | 'default';
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
} & ButtonProps;
|
} & ButtonProps;
|
||||||
|
|
||||||
export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: CopyButtonProps) => {
|
export const CopyButton = ({
|
||||||
|
value,
|
||||||
|
buttonType = 'default',
|
||||||
|
children,
|
||||||
|
...buttonProps
|
||||||
|
}: CopyButtonProps) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
@@ -28,11 +35,23 @@ export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: Cop
|
|||||||
setTimeout(() => setCopied(false), 500);
|
setTimeout(() => setCopied(false), 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
return shownAsText ? (
|
return buttonType === 'text' ? (
|
||||||
<Styled.InlineRow onClick={onCopy} copied={copied}>
|
<Styled.InlineRow onClick={onCopy} copied={copied}>
|
||||||
{children}
|
{children}
|
||||||
<Icon iconName={IconName.Copy} />
|
<Styled.Icon copied={copied} iconName={copied ? IconName.Check : IconName.Copy} />
|
||||||
</Styled.InlineRow>
|
</Styled.InlineRow>
|
||||||
|
) : buttonType === 'icon' ? (
|
||||||
|
<WithTooltip
|
||||||
|
tooltipString={stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })}
|
||||||
|
>
|
||||||
|
<Styled.IconButton
|
||||||
|
{...buttonProps}
|
||||||
|
copied={copied}
|
||||||
|
action={ButtonAction.Base}
|
||||||
|
iconName={copied ? IconName.Check : IconName.Copy}
|
||||||
|
onClick={onCopy}
|
||||||
|
/>
|
||||||
|
</WithTooltip>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
@@ -63,3 +82,21 @@ Styled.InlineRow = styled.div<{ copied: boolean }>`
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.Icon = styled(Icon)<{ copied: boolean }>`
|
||||||
|
${({ copied }) =>
|
||||||
|
copied &&
|
||||||
|
css`
|
||||||
|
color: var(--color-positive);
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.IconButton = styled(IconButton)<{ copied: boolean }>`
|
||||||
|
${({ copied }) =>
|
||||||
|
copied &&
|
||||||
|
css`
|
||||||
|
svg {
|
||||||
|
color: var(--color-positive);
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type ElementProps = {
|
|||||||
slotTrigger?: React.ReactNode;
|
slotTrigger?: React.ReactNode;
|
||||||
slotHeaderInner?: React.ReactNode;
|
slotHeaderInner?: React.ReactNode;
|
||||||
slotFooter?: React.ReactNode;
|
slotFooter?: React.ReactNode;
|
||||||
|
withClose?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
@@ -46,6 +47,7 @@ type StyleProps = {
|
|||||||
hasHeaderBorder?: boolean;
|
hasHeaderBorder?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
withAnimation?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DialogProps = ElementProps & StyleProps;
|
export type DialogProps = ElementProps & StyleProps;
|
||||||
@@ -79,9 +81,11 @@ export const Dialog = ({
|
|||||||
slotTrigger,
|
slotTrigger,
|
||||||
slotHeaderInner,
|
slotHeaderInner,
|
||||||
slotFooter,
|
slotFooter,
|
||||||
|
withClose = true,
|
||||||
placement = DialogPlacement.Default,
|
placement = DialogPlacement.Default,
|
||||||
portalContainer,
|
portalContainer,
|
||||||
hasHeaderBorder = false,
|
hasHeaderBorder = false,
|
||||||
|
withAnimation = false,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
}: DialogProps) => {
|
}: DialogProps) => {
|
||||||
@@ -105,6 +109,7 @@ export const Dialog = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
$withAnimation={withAnimation}
|
||||||
>
|
>
|
||||||
<Styled.Header $withBorder={hasHeaderBorder}>
|
<Styled.Header $withBorder={hasHeaderBorder}>
|
||||||
<Styled.HeaderTopRow>
|
<Styled.HeaderTopRow>
|
||||||
@@ -114,7 +119,7 @@ export const Dialog = ({
|
|||||||
|
|
||||||
{title && <Styled.Title>{title}</Styled.Title>}
|
{title && <Styled.Title>{title}</Styled.Title>}
|
||||||
|
|
||||||
{!preventClose && (
|
{!preventClose && withClose && (
|
||||||
<Styled.Close ref={closeButtonRef}>
|
<Styled.Close ref={closeButtonRef}>
|
||||||
<Icon iconName={IconName.Close} />
|
<Icon iconName={IconName.Close} />
|
||||||
</Styled.Close>
|
</Styled.Close>
|
||||||
@@ -168,7 +173,7 @@ Styled.Overlay = styled(Overlay)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
|
Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?: boolean }>`
|
||||||
/* Params */
|
/* Params */
|
||||||
--dialog-inset: 1rem;
|
--dialog-inset: 1rem;
|
||||||
--dialog-width: 30rem;
|
--dialog-width: 30rem;
|
||||||
@@ -224,7 +229,7 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
|
|||||||
|
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
${({ placement }) =>
|
${({ placement, $withAnimation }) =>
|
||||||
({
|
({
|
||||||
[DialogPlacement.Default]: css`
|
[DialogPlacement.Default]: css`
|
||||||
inset: var(--dialog-inset);
|
inset: var(--dialog-inset);
|
||||||
@@ -260,9 +265,11 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
|
|||||||
padding-bottom: var(--dialog-radius); */
|
padding-bottom: var(--dialog-radius); */
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
${$withAnimation &&
|
||||||
&[data-state='open'] {
|
css`
|
||||||
animation: ${keyframes`
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
&[data-state='open'] {
|
||||||
|
animation: ${keyframes`
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@@ -270,18 +277,19 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
|
|||||||
max-height: 0;
|
max-height: 0;
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state='closed'] {
|
&[data-state='closed'] {
|
||||||
animation: ${keyframes`
|
animation: ${keyframes`
|
||||||
to {
|
to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
scale: 0.9;
|
scale: 0.9;
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
}
|
}
|
||||||
`} 0.15s;
|
`} 0.15s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`}
|
||||||
`,
|
`,
|
||||||
[DialogPlacement.Sidebar]: css`
|
[DialogPlacement.Sidebar]: css`
|
||||||
--dialog-width: var(--sidebar-width);
|
--dialog-width: var(--sidebar-width);
|
||||||
@@ -291,50 +299,52 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
${$withAnimation &&
|
||||||
&[data-state='open'] {
|
css`
|
||||||
animation: ${keyframes`
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
&[data-state='open'] {
|
||||||
|
animation: ${keyframes`
|
||||||
from {
|
from {
|
||||||
translate: 100% 0;
|
translate: 100% 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state='closed'] {
|
&[data-state='closed'] {
|
||||||
animation: ${keyframes`
|
animation: ${keyframes`
|
||||||
to {
|
to {
|
||||||
translate: 100% 0;
|
translate: 100% 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`}
|
||||||
`,
|
`,
|
||||||
[DialogPlacement.Inline]: css`
|
[DialogPlacement.Inline]: css`
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
${$withAnimation &&
|
||||||
&[data-state='open'] {
|
css`
|
||||||
animation: ${keyframes`
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
&[data-state='open'] {
|
||||||
|
animation: ${keyframes`
|
||||||
from {
|
from {
|
||||||
scale: 0.99;
|
scale: 0.99;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
/* filter: blur(2px); */
|
|
||||||
/* backdrop-filter: none; */
|
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state='closed'] {
|
&[data-state='closed'] {
|
||||||
animation: ${keyframes`
|
animation: ${keyframes`
|
||||||
to {
|
to {
|
||||||
scale: 0.99;
|
scale: 0.99;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
/* filter: blur(2px); */
|
|
||||||
/* backdrop-filter: none; */
|
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`}
|
||||||
`,
|
`,
|
||||||
[DialogPlacement.FullScreen]: css`
|
[DialogPlacement.FullScreen]: css`
|
||||||
--dialog-width: 100vw;
|
--dialog-width: 100vw;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
RadioItem,
|
RadioItem,
|
||||||
Portal,
|
Portal,
|
||||||
} from '@radix-ui/react-dropdown-menu';
|
} from '@radix-ui/react-dropdown-menu';
|
||||||
|
import { CheckIcon } from '@radix-ui/react-icons';
|
||||||
|
|
||||||
import { type MenuItem } from '@/constants/menus';
|
import { type MenuItem } from '@/constants/menus';
|
||||||
|
|
||||||
@@ -90,7 +91,9 @@ export const DropdownSelectMenu = <MenuItemValue extends string>({
|
|||||||
|
|
||||||
{slotAfter}
|
{slotAfter}
|
||||||
|
|
||||||
<Styled.ItemIndicator>✔{/* <CheckIcon /> */}</Styled.ItemIndicator>
|
<Styled.ItemIndicator>
|
||||||
|
<CheckIcon />
|
||||||
|
</Styled.ItemIndicator>
|
||||||
</Styled.RadioItem>
|
</Styled.RadioItem>
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|||||||
@@ -1,43 +1,16 @@
|
|||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
|
||||||
|
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
|
|
||||||
export const GreenCheckCircle = ({ className }: { className?: string }) => (
|
export const GreenCheckCircle = ({ className }: { className?: string }) => (
|
||||||
<Styled.GreenCheckCircle className={className}>
|
<Styled.Icon className={className} iconName={IconName.CheckCircle} />
|
||||||
<Icon iconName={IconName.Check} />
|
|
||||||
</Styled.GreenCheckCircle>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
Styled.GreenCheckCircle = styled.div`
|
Styled.Icon = styled(Icon)`
|
||||||
${layoutMixins.stack}
|
|
||||||
|
|
||||||
--icon-size: 1.25rem;
|
--icon-size: 1.25rem;
|
||||||
--icon-border-width: 3px;
|
|
||||||
|
|
||||||
width: var(--icon-size);
|
width: var(--icon-size);
|
||||||
height: var(--icon-size);
|
height: var(--icon-size);
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
width: calc(var(--icon-size) - var(--icon-border-width) * 2);
|
|
||||||
height: calc(var(--icon-size) - var(--icon-border-width) * 2);
|
|
||||||
border: solid var(--icon-border-width) var(--color-positive);
|
|
||||||
border-radius: 50%;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: calc(var(--icon-size) / 3);
|
|
||||||
height: calc(var(--icon-size) / 3);
|
|
||||||
|
|
||||||
color: var(--color-positive);
|
|
||||||
justify-self: center;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
CautionCircleIcon,
|
CautionCircleIcon,
|
||||||
ChatIcon,
|
ChatIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
@@ -23,7 +24,6 @@ import {
|
|||||||
CoinsIcon,
|
CoinsIcon,
|
||||||
CommentIcon,
|
CommentIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
CubeIcon,
|
|
||||||
DepositIcon,
|
DepositIcon,
|
||||||
DepthChartIcon,
|
DepthChartIcon,
|
||||||
DiscordIcon,
|
DiscordIcon,
|
||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
HelpCircleIcon,
|
HelpCircleIcon,
|
||||||
HideIcon,
|
HideIcon,
|
||||||
HistoryIcon,
|
HistoryIcon,
|
||||||
|
LeaderboardIcon,
|
||||||
LinkOutIcon,
|
LinkOutIcon,
|
||||||
LockIcon,
|
LockIcon,
|
||||||
LogoShortIcon,
|
LogoShortIcon,
|
||||||
@@ -52,10 +53,13 @@ import {
|
|||||||
OrderUntriggeredIcon,
|
OrderUntriggeredIcon,
|
||||||
OverviewIcon,
|
OverviewIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
|
PlayIcon,
|
||||||
|
PlusIcon,
|
||||||
PositionsIcon,
|
PositionsIcon,
|
||||||
PriceChartIcon,
|
PriceChartIcon,
|
||||||
PrivacyIcon,
|
PrivacyIcon,
|
||||||
QrIcon,
|
QrIcon,
|
||||||
|
RewardStarIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
SendIcon,
|
SendIcon,
|
||||||
ShareIcon,
|
ShareIcon,
|
||||||
@@ -86,6 +90,7 @@ export enum IconName {
|
|||||||
CautionCircleStroked = 'CautionCircleStroked',
|
CautionCircleStroked = 'CautionCircleStroked',
|
||||||
Chat = 'Chat',
|
Chat = 'Chat',
|
||||||
Check = 'Check',
|
Check = 'Check',
|
||||||
|
CheckCircle = 'CheckCircle',
|
||||||
ChevronLeft = 'ChevronLeft',
|
ChevronLeft = 'ChevronLeft',
|
||||||
ChevronRight = 'ChevronRight',
|
ChevronRight = 'ChevronRight',
|
||||||
Clock = 'Clock',
|
Clock = 'Clock',
|
||||||
@@ -94,7 +99,6 @@ export enum IconName {
|
|||||||
Coins = 'Coins',
|
Coins = 'Coins',
|
||||||
Comment = 'Comment',
|
Comment = 'Comment',
|
||||||
Copy = 'Copy',
|
Copy = 'Copy',
|
||||||
Cube = 'Cube',
|
|
||||||
Deposit = 'Deposit',
|
Deposit = 'Deposit',
|
||||||
DepthChart = 'DepthChart',
|
DepthChart = 'DepthChart',
|
||||||
Discord = 'Discord',
|
Discord = 'Discord',
|
||||||
@@ -108,6 +112,7 @@ export enum IconName {
|
|||||||
HelpCircle = 'HelpCircle',
|
HelpCircle = 'HelpCircle',
|
||||||
Hide = 'Hide',
|
Hide = 'Hide',
|
||||||
History = 'History',
|
History = 'History',
|
||||||
|
Leaderboard = 'Leaderboard',
|
||||||
LinkOut = 'LinkOut',
|
LinkOut = 'LinkOut',
|
||||||
Lock = 'Lock',
|
Lock = 'Lock',
|
||||||
LogoShort = 'LogoShort',
|
LogoShort = 'LogoShort',
|
||||||
@@ -124,10 +129,13 @@ export enum IconName {
|
|||||||
OrderUntriggered = 'OrderUntriggered',
|
OrderUntriggered = 'OrderUntriggered',
|
||||||
Overview = 'Overview',
|
Overview = 'Overview',
|
||||||
Pencil = 'Pencil',
|
Pencil = 'Pencil',
|
||||||
|
Play = 'Play',
|
||||||
|
Plus = 'Plus',
|
||||||
Positions = 'Positions',
|
Positions = 'Positions',
|
||||||
PriceChart = 'PriceChart',
|
PriceChart = 'PriceChart',
|
||||||
Privacy = 'Privacy',
|
Privacy = 'Privacy',
|
||||||
Qr = 'Qr',
|
Qr = 'Qr',
|
||||||
|
RewardStar = 'RewardStar',
|
||||||
Search = 'Search',
|
Search = 'Search',
|
||||||
Send = 'Send',
|
Send = 'Send',
|
||||||
Share = 'Share',
|
Share = 'Share',
|
||||||
@@ -158,6 +166,7 @@ const icons = {
|
|||||||
[IconName.CautionCircleStroked]: CautionCircleStrokeIcon,
|
[IconName.CautionCircleStroked]: CautionCircleStrokeIcon,
|
||||||
[IconName.Chat]: ChatIcon,
|
[IconName.Chat]: ChatIcon,
|
||||||
[IconName.Check]: CheckIcon,
|
[IconName.Check]: CheckIcon,
|
||||||
|
[IconName.CheckCircle]: CheckCircleIcon,
|
||||||
[IconName.ChevronLeft]: ChevronLeftIcon,
|
[IconName.ChevronLeft]: ChevronLeftIcon,
|
||||||
[IconName.ChevronRight]: ChevronRightIcon,
|
[IconName.ChevronRight]: ChevronRightIcon,
|
||||||
[IconName.Clock]: ClockIcon,
|
[IconName.Clock]: ClockIcon,
|
||||||
@@ -166,7 +175,6 @@ const icons = {
|
|||||||
[IconName.Coins]: CoinsIcon,
|
[IconName.Coins]: CoinsIcon,
|
||||||
[IconName.Comment]: CommentIcon,
|
[IconName.Comment]: CommentIcon,
|
||||||
[IconName.Copy]: CopyIcon,
|
[IconName.Copy]: CopyIcon,
|
||||||
[IconName.Cube]: CubeIcon,
|
|
||||||
[IconName.Deposit]: DepositIcon,
|
[IconName.Deposit]: DepositIcon,
|
||||||
[IconName.DepthChart]: DepthChartIcon,
|
[IconName.DepthChart]: DepthChartIcon,
|
||||||
[IconName.Discord]: DiscordIcon,
|
[IconName.Discord]: DiscordIcon,
|
||||||
@@ -180,6 +188,7 @@ const icons = {
|
|||||||
[IconName.HelpCircle]: HelpCircleIcon,
|
[IconName.HelpCircle]: HelpCircleIcon,
|
||||||
[IconName.Hide]: HideIcon,
|
[IconName.Hide]: HideIcon,
|
||||||
[IconName.History]: HistoryIcon,
|
[IconName.History]: HistoryIcon,
|
||||||
|
[IconName.Leaderboard]: LeaderboardIcon,
|
||||||
[IconName.LinkOut]: LinkOutIcon,
|
[IconName.LinkOut]: LinkOutIcon,
|
||||||
[IconName.Lock]: LockIcon,
|
[IconName.Lock]: LockIcon,
|
||||||
[IconName.LogoShort]: LogoShortIcon,
|
[IconName.LogoShort]: LogoShortIcon,
|
||||||
@@ -195,10 +204,13 @@ const icons = {
|
|||||||
[IconName.OrderUntriggered]: OrderUntriggeredIcon,
|
[IconName.OrderUntriggered]: OrderUntriggeredIcon,
|
||||||
[IconName.Overview]: OverviewIcon,
|
[IconName.Overview]: OverviewIcon,
|
||||||
[IconName.Pencil]: PencilIcon,
|
[IconName.Pencil]: PencilIcon,
|
||||||
|
[IconName.Play]: PlayIcon,
|
||||||
|
[IconName.Plus]: PlusIcon,
|
||||||
[IconName.Positions]: PositionsIcon,
|
[IconName.Positions]: PositionsIcon,
|
||||||
[IconName.PriceChart]: PriceChartIcon,
|
[IconName.PriceChart]: PriceChartIcon,
|
||||||
[IconName.Privacy]: PrivacyIcon,
|
[IconName.Privacy]: PrivacyIcon,
|
||||||
[IconName.Qr]: QrIcon,
|
[IconName.Qr]: QrIcon,
|
||||||
|
[IconName.RewardStar]: RewardStarIcon,
|
||||||
[IconName.Search]: SearchIcon,
|
[IconName.Search]: SearchIcon,
|
||||||
[IconName.Send]: SendIcon,
|
[IconName.Send]: SendIcon,
|
||||||
[IconName.Share]: ShareIcon,
|
[IconName.Share]: ShareIcon,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
USD_DECIMALS,
|
USD_DECIMALS,
|
||||||
} from '@/constants/numbers';
|
} from '@/constants/numbers';
|
||||||
|
|
||||||
import { BIG_NUMBERS, MustBigNumber } from '@/lib/numbers';
|
import { BIG_NUMBERS } from '@/lib/numbers';
|
||||||
import { useLocaleSeparators } from '@/hooks';
|
import { useLocaleSeparators } from '@/hooks';
|
||||||
|
|
||||||
export enum InputType {
|
export enum InputType {
|
||||||
@@ -29,31 +29,38 @@ type StyleProps = {
|
|||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
type?: InputType;
|
type?: InputType;
|
||||||
value?: string | number | null;
|
value?: string | number | null;
|
||||||
|
|
||||||
allowNegative?: boolean;
|
|
||||||
decimals?: number;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
max?: number;
|
|
||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
onChange?:
|
|
||||||
| Dispatch<SetStateAction<string>>
|
|
||||||
| React.ReactEventHandler<HTMLInputElement>
|
|
||||||
| ((values: NumberFormatValues, e: SourceInfo) => void);
|
|
||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
onInput?: ({
|
|
||||||
value,
|
|
||||||
floatValue,
|
|
||||||
formattedValue,
|
|
||||||
}: {
|
|
||||||
value: string;
|
|
||||||
floatValue?: number;
|
|
||||||
formattedValue: string;
|
|
||||||
}) => void;
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InputProps = ElementProps & StyleProps;
|
type ConditionalProps =
|
||||||
|
| {
|
||||||
|
allowNegative?: boolean;
|
||||||
|
decimals?: number;
|
||||||
|
max?: number;
|
||||||
|
onChange?: (values: NumberFormatValues, e: SourceInfo) => void;
|
||||||
|
onInput?: ({
|
||||||
|
value,
|
||||||
|
floatValue,
|
||||||
|
formattedValue,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
floatValue?: number;
|
||||||
|
formattedValue: string;
|
||||||
|
}) => void;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
allowNegative?: never;
|
||||||
|
decimals?: never;
|
||||||
|
max?: never;
|
||||||
|
onChange?: Dispatch<SetStateAction<string>> | React.ReactEventHandler<HTMLInputElement>;
|
||||||
|
onInput?: (e: SyntheticInputEvent) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InputProps = ElementProps & StyleProps & ConditionalProps;
|
||||||
|
|
||||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||||
(
|
(
|
||||||
@@ -129,6 +136,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
// Other
|
// Other
|
||||||
|
data-1p-ignore // prevent 1Password fill
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -137,6 +145,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
ref={ref as React.Ref<typeof NumericFormat<unknown>>}
|
ref={ref as React.Ref<typeof NumericFormat<unknown>>}
|
||||||
id={id}
|
id={id}
|
||||||
// NumericFormat
|
// NumericFormat
|
||||||
|
valueIsNumericString
|
||||||
allowNegative={allowNegative}
|
allowNegative={allowNegative}
|
||||||
decimalScale={decimals}
|
decimalScale={decimals}
|
||||||
decimalSeparator={LOCALE_DECIMAL_SEPARATOR}
|
decimalSeparator={LOCALE_DECIMAL_SEPARATOR}
|
||||||
@@ -160,7 +169,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(formattedValue.replace(',', '.'));
|
: Number(formattedValue.replace(',', '.'));
|
||||||
|
|
||||||
onInput?.({ value, floatValue, formattedValue });
|
onInput?.({ value, floatValue, formattedValue, ...e });
|
||||||
}}
|
}}
|
||||||
// Native
|
// Native
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -168,6 +177,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
value={formattedValue}
|
value={formattedValue}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
|
data-1p-ignore // prevent 1Password fill
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -188,7 +198,6 @@ Styled.InputContainer = styled.div`
|
|||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
user-select: all;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, Ref, useEffect, useRef, useState } from 'react';
|
import { forwardRef, Ref } from 'react';
|
||||||
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
||||||
import { NavLink, matchPath, useLocation } from 'react-router-dom';
|
import { NavLink, matchPath, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -21,8 +21,6 @@ import {
|
|||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { isTruthy } from '@/lib/isTruthy';
|
|
||||||
|
|
||||||
import { Tag } from './Tag';
|
import { Tag } from './Tag';
|
||||||
import { Icon, IconName } from './Icon';
|
import { Icon, IconName } from './Icon';
|
||||||
|
|
||||||
@@ -61,7 +59,6 @@ const NavItem = forwardRef(
|
|||||||
<>
|
<>
|
||||||
{slotBefore}
|
{slotBefore}
|
||||||
<span>
|
<span>
|
||||||
{/* {`${label}${subitems?.length ? ' ' : ''}`} */}
|
|
||||||
{label}
|
{label}
|
||||||
{tag && (
|
{tag && (
|
||||||
<>
|
<>
|
||||||
@@ -100,8 +97,6 @@ const NavItem = forwardRef(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
type TriggerRef = HTMLAnchorElement | HTMLDivElement | HTMLButtonElement | null;
|
|
||||||
|
|
||||||
export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue extends string>({
|
export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue extends string>({
|
||||||
onSelectItem,
|
onSelectItem,
|
||||||
onSelectGroup,
|
onSelectGroup,
|
||||||
@@ -112,33 +107,6 @@ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue exte
|
|||||||
dir = 'ltr',
|
dir = 'ltr',
|
||||||
className,
|
className,
|
||||||
}: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => {
|
}: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => {
|
||||||
// Disable click (close) in the first 500ms after hover (open)
|
|
||||||
// https://github.com/radix-ui/primitives/issues/1630#issuecomment-1545995075
|
|
||||||
const [clickIsDisabled, setClickIsDisabled] = useState(false);
|
|
||||||
const triggerRefs = useRef({} as { [value: string]: TriggerRef });
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const observer = new MutationObserver((mutationsList) => {
|
|
||||||
for (const mutation of mutationsList) {
|
|
||||||
if (
|
|
||||||
mutation.type === 'attributes' &&
|
|
||||||
mutation.attributeName === 'data-state' &&
|
|
||||||
(mutation.target as unknown as HTMLOrSVGElement).dataset.state === 'open' &&
|
|
||||||
mutation.target !== document.activeElement
|
|
||||||
) {
|
|
||||||
setClickIsDisabled(true);
|
|
||||||
setTimeout(() => setClickIsDisabled(false), 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const element of Object.values(triggerRefs.current).filter(isTruthy)) {
|
|
||||||
observer.observe(element, { attributes: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const renderSubitems = ({
|
const renderSubitems = ({
|
||||||
item,
|
item,
|
||||||
depth = 0,
|
depth = 0,
|
||||||
@@ -148,18 +116,18 @@ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue exte
|
|||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
<Styled.SubMenuTrigger
|
<Styled.SubMenuTrigger
|
||||||
asChild={depth > 0}
|
asChild
|
||||||
ref={(ref: TriggerRef) => (triggerRefs.current[item.value] = ref)}
|
onPointerMove={(e: MouseEvent) => e.preventDefault()}
|
||||||
onClick={(e: MouseEvent) => {
|
onPointerLeave={(e: MouseEvent) => e.preventDefault()}
|
||||||
if (clickIsDisabled) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Styled.NavItem onSelect={onSelectGroup} orientation={itemOrientation} {...item} />
|
<Styled.NavItem onSelect={onSelectGroup} orientation={itemOrientation} {...item} />
|
||||||
</Styled.SubMenuTrigger>
|
</Styled.SubMenuTrigger>
|
||||||
|
|
||||||
<Styled.Content data-placement={submenuPlacement}>
|
<Styled.Content
|
||||||
|
onPointerEnter={(e: MouseEvent) => e.preventDefault()}
|
||||||
|
onPointerLeave={(e: MouseEvent) => e.preventDefault()}
|
||||||
|
data-placement={submenuPlacement}
|
||||||
|
>
|
||||||
<Styled.Sub data-placement={submenuPlacement}>
|
<Styled.Sub data-placement={submenuPlacement}>
|
||||||
<Styled.List
|
<Styled.List
|
||||||
data-orientation={depth > 0 ? 'menu' : orientation === 'vertical' ? 'vertical' : 'menu'}
|
data-orientation={depth > 0 ? 'menu' : orientation === 'vertical' ? 'vertical' : 'menu'}
|
||||||
@@ -410,25 +378,12 @@ Styled.List = styled(List)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ListItem = styled(Item)`
|
Styled.ListItem = styled(Item)`
|
||||||
/* display: contents; */
|
|
||||||
display: grid;
|
display: grid;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
${Styled.List}[data-orientation="horizontal"] > & {
|
${Styled.List}[data-orientation="horizontal"] > & {
|
||||||
gap: var(--submenu-side-offset);
|
gap: var(--submenu-side-offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ${Styled.List}[data-orientation="menu"] > & {
|
|
||||||
grid-template-columns: 1fr 0;
|
|
||||||
align-items: start;
|
|
||||||
gap: 2rem;
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* &:has([data-state="open"]) {
|
|
||||||
position: sticky;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
} */
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.SubMenuTrigger = styled(Trigger)`
|
Styled.SubMenuTrigger = styled(Trigger)`
|
||||||
@@ -436,11 +391,6 @@ Styled.SubMenuTrigger = styled(Trigger)`
|
|||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
|
|
||||||
&[data-state='open'] {
|
&[data-state='open'] {
|
||||||
div {
|
|
||||||
background-color: var(--navigationMenu-item-checked-backgroundColor);
|
|
||||||
color: var(--navigationMenu-item-checked-textColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
rotate: 0.5turn;
|
rotate: 0.5turn;
|
||||||
}
|
}
|
||||||
@@ -452,11 +402,10 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
|
|||||||
subitems?.length
|
subitems?.length
|
||||||
? css`
|
? css`
|
||||||
${popoverMixins.trigger}
|
${popoverMixins.trigger}
|
||||||
--trigger-backgroundColor: transparent;
|
--trigger-open-backgroundColor: var(--navigationMenu-item-checked-backgroundColor);
|
||||||
--trigger-open-backgroundColor: var(--color-layer-3);
|
|
||||||
`
|
`
|
||||||
: css`
|
: css`
|
||||||
&:hover:not(.active) {
|
&:hover:not(:active) {
|
||||||
background-color: var(--navigationMenu-item-highlighted-backgroundColor);
|
background-color: var(--navigationMenu-item-highlighted-backgroundColor);
|
||||||
color: var(--navigationMenu-item-highlighted-textColor);
|
color: var(--navigationMenu-item-highlighted-textColor);
|
||||||
}
|
}
|
||||||
@@ -470,8 +419,6 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
|
|||||||
--item-radius: var(--navigationMenu-item-radius);
|
--item-radius: var(--navigationMenu-item-radius);
|
||||||
--item-padding: var(--navigationMenu-item-padding);
|
--item-padding: var(--navigationMenu-item-padding);
|
||||||
|
|
||||||
/* ${popoverMixins.backdropOverlay} */
|
|
||||||
|
|
||||||
${layoutMixins.scrollSnapItem}
|
${layoutMixins.scrollSnapItem}
|
||||||
|
|
||||||
min-height: var(--navigationMenu-item-height);
|
min-height: var(--navigationMenu-item-height);
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
import React, { type MouseEvent } from 'react';
|
||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
NotificationStatus,
|
||||||
|
type Notification as NotificationDataType,
|
||||||
|
} from '@/constants/notifications';
|
||||||
|
|
||||||
|
import { useNotifications } from '@/hooks/useNotifications';
|
||||||
|
|
||||||
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
|
import { IconButton } from '@/components/IconButton';
|
||||||
|
import { IconName } from '@/components/Icon';
|
||||||
|
import { Output, OutputType } from '@/components/Output';
|
||||||
|
|
||||||
|
type ElementProps = {
|
||||||
|
notification: NotificationDataType;
|
||||||
|
onClick?: () => void;
|
||||||
|
slotIcon?: React.ReactNode;
|
||||||
|
slotTitle?: React.ReactNode;
|
||||||
|
slotTitleLeft?: React.ReactNode;
|
||||||
|
slotTitleRight?: React.ReactNode;
|
||||||
|
slotDescription?: React.ReactNode;
|
||||||
|
slotCustomContent?: React.ReactNode;
|
||||||
|
slotAction?: React.ReactNode;
|
||||||
|
withClose?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StyleProps = {
|
||||||
|
className?: string;
|
||||||
|
isToast?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NotificationProps = ElementProps & StyleProps;
|
||||||
|
|
||||||
|
export const Notification = ({
|
||||||
|
className,
|
||||||
|
isToast,
|
||||||
|
notification,
|
||||||
|
onClick,
|
||||||
|
slotIcon,
|
||||||
|
slotTitle,
|
||||||
|
slotTitleLeft,
|
||||||
|
slotTitleRight,
|
||||||
|
slotDescription,
|
||||||
|
slotCustomContent,
|
||||||
|
slotAction,
|
||||||
|
withClose = !isToast,
|
||||||
|
}: NotificationProps) => {
|
||||||
|
const { markCleared, markSeen } = useNotifications();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<$Container className={className} isToast={isToast} onClick={onClick}>
|
||||||
|
<$Header>
|
||||||
|
{slotTitleLeft ?? (slotIcon && <$Icon>{slotIcon}</$Icon>)}
|
||||||
|
<$Title>{slotTitle}</$Title>
|
||||||
|
{slotTitleRight}
|
||||||
|
{withClose && !isToast && (
|
||||||
|
<$ActionItems>
|
||||||
|
<$Output
|
||||||
|
type={OutputType.RelativeTime}
|
||||||
|
value={notification.timestamps[NotificationStatus.Triggered]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{notification.status < NotificationStatus.Seen ? <$UnreadIndicator /> : null}
|
||||||
|
|
||||||
|
{notification.status < NotificationStatus.Cleared ? (
|
||||||
|
<$IconButton
|
||||||
|
iconName={IconName.Close}
|
||||||
|
shape={ButtonShape.Square}
|
||||||
|
size={ButtonSize.XSmall}
|
||||||
|
onClick={(e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (notification.status < NotificationStatus.Seen) {
|
||||||
|
markSeen(notification);
|
||||||
|
} else if (notification.status < NotificationStatus.Cleared) {
|
||||||
|
markCleared(notification);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</$ActionItems>
|
||||||
|
)}
|
||||||
|
</$Header>
|
||||||
|
<$Description>{slotCustomContent ?? slotDescription}</$Description>
|
||||||
|
{slotAction && <$Action>{slotAction}</$Action>}
|
||||||
|
</$Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const $Container = styled.div<{ isToast?: boolean }>`
|
||||||
|
// Params
|
||||||
|
--toast-icon-size: 1.75em;
|
||||||
|
|
||||||
|
// Rules
|
||||||
|
${popoverMixins.popover}
|
||||||
|
overflow: visible;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
|
||||||
|
${({ isToast }) =>
|
||||||
|
isToast
|
||||||
|
? css`
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
||||||
|
// border
|
||||||
|
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
flex: 1;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
backdrop-filter: none;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $Header = styled.header`
|
||||||
|
${layoutMixins.row}
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $Icon = styled.div`
|
||||||
|
${layoutMixins.row}
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
> svg,
|
||||||
|
img {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $Title = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
font: var(--font-base-medium);
|
||||||
|
color: var(--color-text-2);
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $Description = styled.div`
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
color: var(--color-text-0);
|
||||||
|
font: var(--font-small-book);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $Action = styled.div`
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font: var(--font-small-book);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $ActionItems = styled.div`
|
||||||
|
min-width: fit-content;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
height: 100%;
|
||||||
|
${layoutMixins.row};
|
||||||
|
justify-content: end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
background-color: var(--color-layer-2);
|
||||||
|
font: var(--font-mini-book);
|
||||||
|
|
||||||
|
display: none;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
${$Container}:hover & {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $UnreadIndicator = styled.div`
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
border: 1px solid var(--color-layer-2);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $Output = styled(Output)`
|
||||||
|
color: var(--color-text-0);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $IconButton = styled(IconButton)`
|
||||||
|
--button-border: none;
|
||||||
|
--button-textColor: var(--color-text-0);
|
||||||
|
--button-hover-textColor: var(--color-text-1);
|
||||||
|
`;
|
||||||
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
import { breakpoints } from '@/styles';
|
||||||
|
|
||||||
type PanelProps = {
|
type PanelProps = {
|
||||||
slotHeaderContent?: string;
|
slotHeaderContent?: string;
|
||||||
@@ -67,6 +68,12 @@ Styled.Panel = styled.section<{ onClick?: () => void }>`
|
|||||||
--panel-content-paddingY: var(--panel-paddingY);
|
--panel-content-paddingY: var(--panel-paddingY);
|
||||||
--panel-content-paddingX: var(--panel-paddingX);
|
--panel-content-paddingX: var(--panel-paddingX);
|
||||||
|
|
||||||
|
@media ${breakpoints.notTablet} {
|
||||||
|
--panel-paddingX: 1.5rem;
|
||||||
|
--panel-paddingY: 1.25rem;
|
||||||
|
--panel-content-paddingY: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
${layoutMixins.row}
|
${layoutMixins.row}
|
||||||
|
|
||||||
background-color: var(--color-layer-3);
|
background-color: var(--color-layer-3);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
@@ -22,6 +22,11 @@ export const SearchInput = ({
|
|||||||
}: SearchInputProps) => {
|
}: SearchInputProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) inputRef?.current?.focus();
|
||||||
|
}, [inputRef, isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Search>
|
<Styled.Search>
|
||||||
@@ -41,6 +46,7 @@ export const SearchInput = ({
|
|||||||
/>
|
/>
|
||||||
<Styled.Input
|
<Styled.Input
|
||||||
autoFocus
|
autoFocus
|
||||||
|
ref={inputRef}
|
||||||
value={value}
|
value={value}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
type={type}
|
type={type}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
|
|||||||
import { WithLabel } from '@/components/WithLabel';
|
import { WithLabel } from '@/components/WithLabel';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
import { formMixins } from '@/styles/formMixins';
|
||||||
|
import breakpoints from '@/styles/breakpoints';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
@@ -84,27 +86,30 @@ export const SearchSelectMenu = ({
|
|||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.SearchSelectMenu = styled.div`
|
||||||
|
${layoutMixins.column}
|
||||||
|
`;
|
||||||
|
|
||||||
Styled.MenuTrigger = styled.div`
|
Styled.MenuTrigger = styled.div`
|
||||||
height: var(--form-input-height);
|
height: var(--form-input-height);
|
||||||
|
|
||||||
${layoutMixins.spacedRow}
|
${layoutMixins.spacedRow}
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 1rem;
|
padding: var(--form-input-paddingY) var(--form-input-paddingX);
|
||||||
|
|
||||||
|
@media ${breakpoints.tablet} {
|
||||||
|
height: var(--form-input-height-mobile);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.WithLabel = styled(WithLabel)`
|
Styled.WithLabel = styled(WithLabel)`
|
||||||
--label-textColor: var(--color-text-0);
|
${formMixins.inputLabel}
|
||||||
gap: 0.25rem;
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font: var(--font-mini-book);
|
font: var(--font-mini-book);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.SearchSelectMenu = styled.div`
|
|
||||||
${layoutMixins.column}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
||||||
--withReceipt-backgroundColor: var(--color-layer-2);
|
--withReceipt-backgroundColor: var(--color-layer-2);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { css, type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Root,
|
Root,
|
||||||
@@ -13,11 +13,13 @@ import {
|
|||||||
Portal,
|
Portal,
|
||||||
Icon as SelectIcon,
|
Icon as SelectIcon,
|
||||||
} from '@radix-ui/react-select';
|
} from '@radix-ui/react-select';
|
||||||
|
import { CheckIcon } from '@radix-ui/react-icons';
|
||||||
|
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
import { formMixins } from '@/styles/formMixins';
|
import { formMixins } from '@/styles/formMixins';
|
||||||
|
|
||||||
import { WithLabel } from '@/components/WithLabel';
|
import { WithLabel } from '@/components/WithLabel';
|
||||||
|
import { Icon, IconName } from './Icon';
|
||||||
|
|
||||||
export const SelectMenu = <T extends string>({
|
export const SelectMenu = <T extends string>({
|
||||||
children,
|
children,
|
||||||
@@ -44,7 +46,9 @@ export const SelectMenu = <T extends string>({
|
|||||||
) : (
|
) : (
|
||||||
<Value />
|
<Value />
|
||||||
)}
|
)}
|
||||||
{React.Children.toArray(children).length > 1 && <Styled.DropdownIcon />}
|
{React.Children.toArray(children).length > 1 && (
|
||||||
|
<Styled.TriggerIcon iconName={IconName.Triangle} />
|
||||||
|
)}
|
||||||
</Styled.Trigger>
|
</Styled.Trigger>
|
||||||
<Portal>
|
<Portal>
|
||||||
<Styled.Content className={className}>
|
<Styled.Content className={className}>
|
||||||
@@ -68,7 +72,9 @@ export const SelectItem = <T extends string>({
|
|||||||
}) => (
|
}) => (
|
||||||
<Styled.Item className={className} value={value}>
|
<Styled.Item className={className} value={value}>
|
||||||
<ItemText>{label}</ItemText>
|
<ItemText>{label}</ItemText>
|
||||||
<Styled.ItemIndicator>✔{/* <CheckIcon /> */}</Styled.ItemIndicator>
|
<Styled.ItemIndicator>
|
||||||
|
<CheckIcon />
|
||||||
|
</Styled.ItemIndicator>
|
||||||
</Styled.Item>
|
</Styled.Item>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -88,12 +94,6 @@ Styled.Trigger = styled(Trigger)<{ $withBlur?: boolean }>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.DropdownIcon = styled(SelectIcon)`
|
|
||||||
font-size: 0.675em;
|
|
||||||
color: var(--color-text-1);
|
|
||||||
margin-left: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Content = styled(Content)`
|
Styled.Content = styled(Content)`
|
||||||
--select-menu-content-maxWidth: ;
|
--select-menu-content-maxWidth: ;
|
||||||
max-width: var(--select-menu-content-maxWidth);
|
max-width: var(--select-menu-content-maxWidth);
|
||||||
@@ -116,3 +116,9 @@ Styled.WithLabel = styled(WithLabel)`
|
|||||||
${formMixins.inputLabel}
|
${formMixins.inputLabel}
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.TriggerIcon = styled(Icon)`
|
||||||
|
width: 0.625rem;
|
||||||
|
height: 0.375rem;
|
||||||
|
color: var(--color-text-0);
|
||||||
|
`;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
|||||||
|
|
||||||
import { Icon, IconName } from './Icon';
|
import { Icon, IconName } from './Icon';
|
||||||
import { Tag } from './Tag';
|
import { Tag } from './Tag';
|
||||||
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
|
||||||
export { TableCell } from './Table/TableCell';
|
export { TableCell } from './Table/TableCell';
|
||||||
export { TableColumnHeader } from './Table/TableColumnHeader';
|
export { TableColumnHeader } from './Table/TableColumnHeader';
|
||||||
@@ -161,10 +162,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
? // String
|
? // String
|
||||||
collator.compare(first as string, second as string)
|
collator.compare(first as string, second as string)
|
||||||
: // Number
|
: // Number
|
||||||
Math.sign(
|
MustBigNumber(first).comparedTo(MustBigNumber(second))) *
|
||||||
(parseInt(first as string) || (first as number)) -
|
|
||||||
(parseInt(second as string) || (second as number))
|
|
||||||
)) *
|
|
||||||
// Flip the direction if descending order is specified.
|
// Flip the direction if descending order is specified.
|
||||||
(sortDirection === 'descending' ? -1 : 1)
|
(sortDirection === 'descending' ? -1 : 1)
|
||||||
);
|
);
|
||||||
@@ -831,6 +829,7 @@ Styled.SortArrow = styled.span<{ sortDirection: 'ascending' | 'descending' }>`
|
|||||||
Styled.Thead = styled.thead<StyleProps>`
|
Styled.Thead = styled.thead<StyleProps>`
|
||||||
${layoutMixins.stickyHeader}
|
${layoutMixins.stickyHeader}
|
||||||
scroll-snap-align: none;
|
scroll-snap-align: none;
|
||||||
|
font: var(--font-mini-book);
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
height: var(--stickyArea-topHeight);
|
height: var(--stickyArea-topHeight);
|
||||||
@@ -839,18 +838,17 @@ Styled.Thead = styled.thead<StyleProps>`
|
|||||||
color: var(--tableHeader-textColor);
|
color: var(--tableHeader-textColor);
|
||||||
background-color: var(--tableHeader-backgroundColor);
|
background-color: var(--tableHeader-backgroundColor);
|
||||||
|
|
||||||
@media ${breakpoints.notTablet} {
|
${({ withInnerBorders, withGradientCardRows }) =>
|
||||||
${({ withInnerBorders, withGradientCardRows }) =>
|
withInnerBorders &&
|
||||||
withInnerBorders &&
|
!withGradientCardRows &&
|
||||||
!withGradientCardRows &&
|
css`
|
||||||
css`
|
${layoutMixins.withInnerHorizontalBorders}
|
||||||
${layoutMixins.withInnerHorizontalBorders}
|
`}
|
||||||
`}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Tbody = styled.tbody<StyleProps>`
|
Styled.Tbody = styled.tbody<StyleProps>`
|
||||||
${layoutMixins.stickyArea2}
|
${layoutMixins.stickyArea2}
|
||||||
|
font: var(--font-small-book);
|
||||||
|
|
||||||
// If <table> height is fixed with not enough rows to overflow, vertically center the rows
|
// If <table> height is fixed with not enough rows to overflow, vertically center the rows
|
||||||
&:before,
|
&:before,
|
||||||
@@ -869,6 +867,10 @@ Styled.Tbody = styled.tbody<StyleProps>`
|
|||||||
--stickyArea2-paddingBottom: var(--border-width);
|
--stickyArea2-paddingBottom: var(--border-width);
|
||||||
--stickyArea2-paddingLeft: var(--border-width);
|
--stickyArea2-paddingLeft: var(--border-width);
|
||||||
--stickyArea2-paddingRight: var(--border-width);
|
--stickyArea2-paddingRight: var(--border-width);
|
||||||
|
|
||||||
|
tr:first-of-type {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${({ withGradientCardRows }) =>
|
${({ withGradientCardRows }) =>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export type TabItem<TabItemsValue> = {
|
|||||||
content?: React.ReactNode;
|
content?: React.ReactNode;
|
||||||
subitems?: TabItem<TabItemsValue>[];
|
subitems?: TabItem<TabItemsValue>[];
|
||||||
customTrigger?: ReactNode;
|
customTrigger?: ReactNode;
|
||||||
|
asChild?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ElementProps<TabItemsValue> = {
|
type ElementProps<TabItemsValue> = {
|
||||||
@@ -41,6 +42,8 @@ type StyleProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TabsProps<TabItemsValue> = ElementProps<TabItemsValue> & StyleProps;
|
||||||
|
|
||||||
export const Tabs = <TabItemsValue extends string>({
|
export const Tabs = <TabItemsValue extends string>({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
value,
|
value,
|
||||||
@@ -107,9 +110,10 @@ export const Tabs = <TabItemsValue extends string>({
|
|||||||
sharedContent
|
sharedContent
|
||||||
) : (
|
) : (
|
||||||
<Styled.Stack>
|
<Styled.Stack>
|
||||||
{items.map(({ value, content, forceMount }) => (
|
{items.map(({ asChild, value, content, forceMount }) => (
|
||||||
<Styled.Content
|
<Styled.Content
|
||||||
key={value}
|
key={value}
|
||||||
|
asChild={asChild}
|
||||||
value={value}
|
value={value}
|
||||||
forceMount={forceMount}
|
forceMount={forceMount}
|
||||||
$hide={forceMount && currentItem?.value !== value}
|
$hide={forceMount && currentItem?.value !== value}
|
||||||
@@ -242,7 +246,6 @@ Styled.Trigger = styled(Trigger)<{ $withBorders?: boolean }>`
|
|||||||
|
|
||||||
Styled.Stack = styled.div`
|
Styled.Stack = styled.div`
|
||||||
${layoutMixins.stack}
|
${layoutMixins.stack}
|
||||||
${layoutMixins.perspectiveArea}
|
|
||||||
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import { type MouseEvent, useEffect, useRef, useState } from 'react';
|
||||||
|
import styled, { keyframes } from 'styled-components';
|
||||||
import { Root, Title, Description, Action, Close } from '@radix-ui/react-toast';
|
import { Root, Action, Close } from '@radix-ui/react-toast';
|
||||||
|
|
||||||
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||||
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
|
import { breakpoints } from '@/styles';
|
||||||
|
|
||||||
|
import { Notification, type NotificationProps } from '@/components/Notification';
|
||||||
|
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
import { CloseIcon } from '@/icons';
|
import { IconName } from './Icon';
|
||||||
|
|
||||||
import styled, { keyframes } from 'styled-components';
|
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
setIsOpen?: (isOpen: boolean, isClosedFromTimeout?: boolean) => void;
|
setIsOpen?: (isOpen: boolean, isClosedFromTimeout?: boolean) => void;
|
||||||
slotIcon?: React.ReactNode;
|
|
||||||
slotTitle?: React.ReactNode;
|
|
||||||
slotDescription?: React.ReactNode;
|
|
||||||
slotCustomContent?: React.ReactNode;
|
|
||||||
slotAction?: React.ReactNode;
|
|
||||||
actionDescription?: string;
|
actionDescription?: string;
|
||||||
actionAltText?: string;
|
actionAltText?: string;
|
||||||
sensitivity?: 'foreground' | 'background';
|
sensitivity?: 'foreground' | 'background';
|
||||||
@@ -26,13 +21,22 @@ type ElementProps = {
|
|||||||
lastUpdated?: number;
|
lastUpdated?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {};
|
type StyleProps = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ToastProps = NotificationProps & ElementProps & StyleProps;
|
||||||
|
|
||||||
export const Toast = ({
|
export const Toast = ({
|
||||||
|
className,
|
||||||
isOpen = true,
|
isOpen = true,
|
||||||
|
notification,
|
||||||
|
onClick,
|
||||||
setIsOpen,
|
setIsOpen,
|
||||||
slotIcon,
|
slotIcon,
|
||||||
slotTitle,
|
slotTitle,
|
||||||
|
slotTitleLeft,
|
||||||
|
slotTitleRight,
|
||||||
slotDescription,
|
slotDescription,
|
||||||
slotCustomContent,
|
slotCustomContent,
|
||||||
slotAction,
|
slotAction,
|
||||||
@@ -41,7 +45,7 @@ export const Toast = ({
|
|||||||
sensitivity = 'background',
|
sensitivity = 'background',
|
||||||
duration = Infinity,
|
duration = Infinity,
|
||||||
lastUpdated,
|
lastUpdated,
|
||||||
}: ElementProps & StyleProps) => {
|
}: ToastProps) => {
|
||||||
// Timeout
|
// Timeout
|
||||||
const timeout = useRef<number>();
|
const timeout = useRef<number>();
|
||||||
const [isPaused, setIsPaused] = useState(false);
|
const [isPaused, setIsPaused] = useState(false);
|
||||||
@@ -58,37 +62,45 @@ export const Toast = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<$Root
|
<$Root
|
||||||
|
className={className}
|
||||||
type={sensitivity}
|
type={sensitivity}
|
||||||
duration={Infinity}
|
duration={Infinity}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onOpenChange={setIsOpen}
|
onOpenChange={setIsOpen}
|
||||||
onPause={() => setIsPaused(true)}
|
onPause={() => setIsPaused(true)}
|
||||||
onResume={() => setIsPaused(false)}
|
onResume={() => setIsPaused(false)}
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen?.(false, false);
|
||||||
|
onClick?.();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<Close asChild>
|
||||||
<$Container>
|
<$CloseButton
|
||||||
<$Header>
|
iconName={IconName.Close}
|
||||||
{slotIcon && <$Icon>{slotIcon}</$Icon>}
|
shape={ButtonShape.Circle}
|
||||||
|
size={ButtonSize.XSmall}
|
||||||
|
onClick={(e: MouseEvent) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</Close>
|
||||||
|
|
||||||
<Close asChild>
|
{slotCustomContent ?? (
|
||||||
<$CloseButton
|
<$Notification
|
||||||
iconComponent={CloseIcon}
|
isToast
|
||||||
shape={ButtonShape.Square}
|
notification={notification}
|
||||||
size={ButtonSize.XSmall}
|
slotIcon={slotIcon}
|
||||||
/>
|
slotTitle={slotTitle}
|
||||||
</Close>
|
slotTitleLeft={slotTitleLeft}
|
||||||
|
slotTitleRight={slotTitleRight}
|
||||||
<$Title>{slotTitle}</$Title>
|
slotDescription={slotDescription}
|
||||||
</$Header>
|
slotAction={
|
||||||
{!slotCustomContent && <$Description>{slotDescription}</$Description>}
|
actionDescription && (
|
||||||
{slotCustomContent}
|
<$Action asChild altText={actionAltText}>
|
||||||
{actionDescription && (
|
{slotAction}
|
||||||
<$Action asChild altText={actionAltText}>
|
</$Action>
|
||||||
{slotAction}
|
)
|
||||||
</$Action>
|
}
|
||||||
)}
|
/>
|
||||||
</$Container>
|
)}
|
||||||
</div>
|
|
||||||
</$Root>
|
</$Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -114,7 +126,7 @@ const $Root = styled(Root)`
|
|||||||
translate: var(--x) var(--y);
|
translate: var(--x) var(--y);
|
||||||
will-change: translate, margin-top;
|
will-change: translate, margin-top;
|
||||||
|
|
||||||
margin-bottom: var(--toasts-gap);
|
/* margin-bottom: var(--toasts-gap); */
|
||||||
|
|
||||||
&[data-swipe-direction='right'] {
|
&[data-swipe-direction='right'] {
|
||||||
&[data-state='open'] {
|
&[data-state='open'] {
|
||||||
@@ -163,6 +175,10 @@ const $Root = styled(Root)`
|
|||||||
cursor: n-resize;
|
cursor: n-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus:not([data-swipe='end']) & {
|
||||||
|
outline: var(--color-accent) 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
&[data-swipe='move'] {
|
&[data-swipe='move'] {
|
||||||
transition-property: opacity;
|
transition-property: opacity;
|
||||||
opacity: 0.98;
|
opacity: 0.98;
|
||||||
@@ -171,6 +187,10 @@ const $Root = styled(Root)`
|
|||||||
* {
|
* {
|
||||||
cursor: inherit;
|
cursor: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state='closed'],
|
&[data-state='closed'],
|
||||||
@@ -198,69 +218,48 @@ const $Root = styled(Root)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const $Container = styled.div`
|
const $Notification = styled(Notification)`
|
||||||
// Params
|
// Params
|
||||||
--toast-icon-size: 1.75em;
|
--toast-icon-size: 1.75em;
|
||||||
|
|
||||||
// Rules
|
// Rules
|
||||||
${popoverMixins.popover}
|
${popoverMixins.popover}
|
||||||
|
overflow: visible;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
||||||
// border
|
// border
|
||||||
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
||||||
|
|
||||||
${$Root}:focus:not([data-swipe='end']) & {
|
|
||||||
outline: var(--color-accent) 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
${$Root}[data-swipe='move'] & > * {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $Header = styled.header`
|
|
||||||
display: block;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $Icon = styled.div`
|
|
||||||
${layoutMixins.row}
|
|
||||||
|
|
||||||
float: left;
|
|
||||||
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
|
|
||||||
margin-right: 0.4em;
|
|
||||||
|
|
||||||
line-height: 1;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const $CloseButton = styled(IconButton)`
|
const $CloseButton = styled(IconButton)`
|
||||||
--button-textColor: var(--color-text-0);
|
--button-textColor: var(--color-text-0);
|
||||||
--button-border: none;
|
--button-border: none;
|
||||||
--button-icon-size: 0.85em;
|
--button-icon-size: 0.85em;
|
||||||
|
display: none;
|
||||||
|
|
||||||
float: right;
|
/* float: right; */
|
||||||
margin: -0.42rem -0.42rem -0.42rem 0.42rem;
|
margin: -0.42rem -0.42rem -0.42rem 0.42rem;
|
||||||
`;
|
|
||||||
|
|
||||||
const $Title = styled(Title)`
|
// Absolute
|
||||||
flex: 1;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
font: var(--font-base-medium);
|
border: solid var(--border-width) var(--color-border);
|
||||||
color: var(--color-text-2);
|
|
||||||
|
|
||||||
overflow: hidden;
|
${$Root}:hover & {
|
||||||
text-overflow: ellipsis;
|
display: block;
|
||||||
`;
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
const $Description = styled(Description)`
|
@media ${breakpoints.mobile} {
|
||||||
margin-top: 0.5rem;
|
display: block;
|
||||||
color: var(--color-text-0);
|
z-index: 2;
|
||||||
font: var(--font-small-book);
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const $Action = styled(Action)`
|
const $Action = styled(Action)`
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { Provider, Viewport } from '@radix-ui/react-toast';
|
import { Provider, Viewport } from '@radix-ui/react-toast';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
@@ -12,7 +13,9 @@ type StyleProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ToastArea = ({ swipeDirection, children, className }: ElementProps & StyleProps) => (
|
type ToastAreaProps = ElementProps & StyleProps;
|
||||||
|
|
||||||
|
export const ToastArea = ({ swipeDirection, children, className }: ToastAreaProps) => (
|
||||||
<$ToastArea className={className}>
|
<$ToastArea className={className}>
|
||||||
<Provider swipeDirection={swipeDirection}>
|
<Provider swipeDirection={swipeDirection}>
|
||||||
{children}
|
{children}
|
||||||
@@ -26,7 +29,6 @@ const $ToastArea = styled.aside`
|
|||||||
--toasts-gap: 0.5rem;
|
--toasts-gap: 0.5rem;
|
||||||
|
|
||||||
// Rules
|
// Rules
|
||||||
${layoutMixins.scrollArea}
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -34,6 +36,7 @@ const $ToastArea = styled.aside`
|
|||||||
> * {
|
> * {
|
||||||
display: contents;
|
display: contents;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
> ol {
|
> ol {
|
||||||
/* display: block; */
|
/* display: block; */
|
||||||
@@ -43,6 +46,7 @@ const $ToastArea = styled.aside`
|
|||||||
// Desired display order: latest to earliest (¯\_(ツ)_/¯)
|
// Desired display order: latest to earliest (¯\_(ツ)_/¯)
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
|||||||
@@ -52,15 +52,19 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
Styled.BaseButton = styled(BaseButton)`
|
Styled.BaseButton = styled(BaseButton)`
|
||||||
--button-toggle-off-backgroundColor: var(--color-layer-3);
|
--button-toggle-off-backgroundColor: var(--color-layer-3);
|
||||||
--button-toggle-off-textColor: var(--color-text-0);
|
--button-toggle-off-textColor: var(--color-text-0);
|
||||||
|
--button-toggle-off-border: solid var(--border-width) var(--border-color);
|
||||||
--button-toggle-on-backgroundColor: var(--color-layer-1);
|
--button-toggle-on-backgroundColor: var(--color-layer-1);
|
||||||
--button-toggle-on-textColor: var(--color-text-2);
|
--button-toggle-on-textColor: var(--color-text-2);
|
||||||
|
--button-toggle-on-border: solid var(--border-width) var(--border-color);
|
||||||
|
|
||||||
--button-backgroundColor: var(--button-toggle-off-backgroundColor);
|
--button-backgroundColor: var(--button-toggle-off-backgroundColor);
|
||||||
--button-textColor: var(--button-toggle-off-textColor);
|
--button-textColor: var(--button-toggle-off-textColor);
|
||||||
|
--button-border: var(--button-toggle-off-border);
|
||||||
|
|
||||||
&[data-state='on'],
|
&[data-state='on'],
|
||||||
&[data-state='active'] {
|
&[data-state='active'] {
|
||||||
--button-backgroundColor: var(--button-toggle-on-backgroundColor);
|
--button-backgroundColor: var(--button-toggle-on-backgroundColor);
|
||||||
--button-textColor: var(--button-toggle-on-textColor);
|
--button-textColor: var(--button-toggle-on-textColor);
|
||||||
|
--button-border: var(--button-toggle-on-border);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { PropsWithChildren, useContext, useState } from 'react';
|
||||||
|
|
||||||
import { Point } from '@visx/point';
|
import { Point } from '@visx/point';
|
||||||
import { localPoint } from '@visx/event';
|
import { localPoint } from '@visx/event';
|
||||||
import { XYChart, DataContext } from '@visx/xychart';
|
import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart';
|
||||||
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
|
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
|
||||||
|
|
||||||
export const XYChartWithPointerEvents = ({
|
export const XYChartWithPointerEvents = ({
|
||||||
onPointerMove, onPointerUp, onPointerPressedChange, ...props
|
onPointerMove,
|
||||||
|
onPointerUp,
|
||||||
|
onPointerPressedChange,
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
onPointerMove?: (point: Point) => void;
|
onPointerMove?: (point: Point | EventHandlerParams<object>) => void;
|
||||||
onPointerUp?: (point: Point) => void;
|
onPointerUp?: (point: Point | EventHandlerParams<object>) => void;
|
||||||
onPointerPressedChange?: (isPointerPressed: boolean) => void;
|
onPointerPressedChange?: (isPointerPressed: boolean) => void;
|
||||||
} & React.PropsWithChildren<Parameters<typeof XYChart>>) => {
|
} & PropsWithChildren<Parameters<typeof XYChart>[0]>) => {
|
||||||
const { xScale, yScale } = useContext(DataContext);
|
const { xScale, yScale } = useContext(DataContext);
|
||||||
|
|
||||||
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
|
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
|
||||||
|
|
||||||
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
|
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
|
||||||
|
|
||||||
const pointerChartPosition = xScale && yScale && pointerContainerPosition &&
|
const pointerChartPosition =
|
||||||
|
xScale &&
|
||||||
|
yScale &&
|
||||||
|
pointerContainerPosition &&
|
||||||
new Point({
|
new Point({
|
||||||
|
// @ts-expect-error invert supposedly doesn't exist on AxisScale
|
||||||
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2),
|
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),
|
y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -29,15 +36,13 @@ export const XYChartWithPointerEvents = ({
|
|||||||
{...props}
|
{...props}
|
||||||
onPointerMove={({ event }) => {
|
onPointerMove={({ event }) => {
|
||||||
setLastPointerMoveEvent(event as React.PointerEvent);
|
setLastPointerMoveEvent(event as React.PointerEvent);
|
||||||
if (pointerChartPosition)
|
if (pointerChartPosition) onPointerMove?.(pointerChartPosition);
|
||||||
onPointerMove?.(pointerChartPosition);
|
|
||||||
}}
|
}}
|
||||||
onPointerOut={() => setLastPointerMoveEvent(undefined)}
|
onPointerOut={() => setLastPointerMoveEvent(undefined)}
|
||||||
onPointerDown={() => onPointerPressedChange?.(true)}
|
onPointerDown={() => onPointerPressedChange?.(true)}
|
||||||
onPointerUp={() => {
|
onPointerUp={() => {
|
||||||
onPointerPressedChange?.(false);
|
onPointerPressedChange?.(false);
|
||||||
if (pointerChartPosition)
|
if (pointerChartPosition) onPointerUp?.(pointerChartPosition);
|
||||||
onPointerUp?.(pointerChartPosition);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPo
|
|||||||
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
|
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
|
||||||
export type OrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
export type OrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
||||||
export const AbacusOrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
export const AbacusOrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
||||||
|
const abacusOrderStatuses = [...AbacusOrderStatus.values()] as const;
|
||||||
|
export type AbacusOrderStatuses = (typeof abacusOrderStatuses)[number];
|
||||||
export type SubaccountFills = Abacus.exchange.dydx.abacus.output.SubaccountFill[];
|
export type SubaccountFills = Abacus.exchange.dydx.abacus.output.SubaccountFill[];
|
||||||
export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
|
export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
|
||||||
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
|
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
|
||||||
@@ -140,7 +142,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
|
|||||||
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
|
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
|
||||||
|
|
||||||
// ------ Transfer Items ------ //
|
// ------ Transfer Items ------ //
|
||||||
export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField;
|
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField;
|
||||||
const transferInputFields = [...TransferInputField.values()] as const;
|
const transferInputFields = [...TransferInputField.values()] as const;
|
||||||
export type TransferInputFields = (typeof transferInputFields)[number];
|
export type TransferInputFields = (typeof transferInputFields)[number];
|
||||||
|
|
||||||
@@ -149,7 +151,7 @@ const transferTypes = [...TransferType.values()] as const;
|
|||||||
export type TransferTypes = (typeof transferTypes)[number];
|
export type TransferTypes = (typeof transferTypes)[number];
|
||||||
|
|
||||||
// ------ Trade Items ------ //
|
// ------ Trade Items ------ //
|
||||||
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
|
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField;
|
||||||
const tradeInputFields = [...TradeInputField.values()] as const;
|
const tradeInputFields = [...TradeInputField.values()] as const;
|
||||||
export type TradeInputFields = (typeof tradeInputFields)[number];
|
export type TradeInputFields = (typeof tradeInputFields)[number];
|
||||||
|
|
||||||
@@ -160,7 +162,7 @@ export type TradeState<T> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ClosePositionInputField =
|
export const ClosePositionInputField =
|
||||||
Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField;
|
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
|
||||||
|
|
||||||
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
|
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
|
||||||
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
|
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
|
||||||
@@ -209,11 +211,13 @@ type ReadonlyKeysOf<T> = {
|
|||||||
[K in keyof T]: IfEquals<{ [_ in K]: T[K] }, { readonly [_ in K]: T[K] }, K, never>;
|
[K in keyof T]: IfEquals<{ [_ in K]: T[K] }, { readonly [_ in K]: T[K] }, K, never>;
|
||||||
}[keyof T];
|
}[keyof T];
|
||||||
|
|
||||||
type KotlinIrEnumValues<T> = Exclude<ReadonlyKeysOf<T>, 'Companion' | '$serializer'>;
|
export type KotlinIrEnumValues<T> = Exclude<ReadonlyKeysOf<T>, 'Companion' | '$serializer'>;
|
||||||
|
|
||||||
export const ORDER_SIDES: Record<KotlinIrEnumValues<typeof AbacusOrderSide>, OrderSide> = {
|
export const ORDER_SIDES: Record<KotlinIrEnumValues<typeof AbacusOrderSide>, OrderSide> = {
|
||||||
[AbacusOrderSide.buy.name]: OrderSide.BUY,
|
[AbacusOrderSide.buy.name]: OrderSide.BUY,
|
||||||
[AbacusOrderSide.sell.name]: OrderSide.SELL,
|
[AbacusOrderSide.sell.name]: OrderSide.SELL,
|
||||||
|
[AbacusOrderSide.buy.rawValue]: OrderSide.BUY,
|
||||||
|
[AbacusOrderSide.sell.rawValue]: OrderSide.SELL,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POSITION_SIDES: Record<KotlinIrEnumValues<typeof AbacusPositionSide>, PositionSide> = {
|
export const POSITION_SIDES: Record<KotlinIrEnumValues<typeof AbacusPositionSide>, PositionSide> = {
|
||||||
@@ -234,12 +238,25 @@ export const HISTORICAL_PNL_PERIODS: Record<
|
|||||||
|
|
||||||
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
|
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
|
||||||
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
|
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
|
||||||
|
[AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS,
|
||||||
|
|
||||||
[AbacusOrderStatus.partiallyFilled.name]: STRING_KEYS.PARTIALLY_FILLED,
|
[AbacusOrderStatus.partiallyFilled.name]: STRING_KEYS.PARTIALLY_FILLED,
|
||||||
|
[AbacusOrderStatus.partiallyFilled.rawValue]: STRING_KEYS.PARTIALLY_FILLED,
|
||||||
|
|
||||||
[AbacusOrderStatus.filled.name]: STRING_KEYS.ORDER_FILLED,
|
[AbacusOrderStatus.filled.name]: STRING_KEYS.ORDER_FILLED,
|
||||||
|
[AbacusOrderStatus.filled.rawValue]: STRING_KEYS.ORDER_FILLED,
|
||||||
|
|
||||||
[AbacusOrderStatus.cancelled.name]: STRING_KEYS.CANCELED,
|
[AbacusOrderStatus.cancelled.name]: STRING_KEYS.CANCELED,
|
||||||
|
[AbacusOrderStatus.cancelled.rawValue]: STRING_KEYS.CANCELED,
|
||||||
|
|
||||||
[AbacusOrderStatus.canceling.name]: STRING_KEYS.CANCELING,
|
[AbacusOrderStatus.canceling.name]: STRING_KEYS.CANCELING,
|
||||||
|
[AbacusOrderStatus.canceling.rawValue]: STRING_KEYS.CANCELING,
|
||||||
|
|
||||||
[AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING,
|
[AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING,
|
||||||
|
[AbacusOrderStatus.pending.rawValue]: STRING_KEYS.PENDING,
|
||||||
|
|
||||||
[AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED,
|
[AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED,
|
||||||
|
[AbacusOrderStatus.untriggered.rawValue]: STRING_KEYS.UNTRIGGERED,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TRADE_TYPES: Record<
|
export const TRADE_TYPES: Record<
|
||||||
@@ -247,15 +264,31 @@ export const TRADE_TYPES: Record<
|
|||||||
Nullable<TradeTypes>
|
Nullable<TradeTypes>
|
||||||
> = {
|
> = {
|
||||||
[AbacusOrderType.limit.name]: TradeTypes.LIMIT,
|
[AbacusOrderType.limit.name]: TradeTypes.LIMIT,
|
||||||
|
[AbacusOrderType.limit.rawValue]: TradeTypes.LIMIT,
|
||||||
|
|
||||||
[AbacusOrderType.market.name]: TradeTypes.MARKET,
|
[AbacusOrderType.market.name]: TradeTypes.MARKET,
|
||||||
|
[AbacusOrderType.market.rawValue]: TradeTypes.MARKET,
|
||||||
|
|
||||||
[AbacusOrderType.stopLimit.name]: TradeTypes.STOP_LIMIT,
|
[AbacusOrderType.stopLimit.name]: TradeTypes.STOP_LIMIT,
|
||||||
|
[AbacusOrderType.stopLimit.rawValue]: TradeTypes.STOP_LIMIT,
|
||||||
|
|
||||||
[AbacusOrderType.stopMarket.name]: TradeTypes.STOP_MARKET,
|
[AbacusOrderType.stopMarket.name]: TradeTypes.STOP_MARKET,
|
||||||
|
[AbacusOrderType.stopMarket.rawValue]: TradeTypes.STOP_MARKET,
|
||||||
|
|
||||||
[AbacusOrderType.takeProfitLimit.name]: TradeTypes.TAKE_PROFIT,
|
[AbacusOrderType.takeProfitLimit.name]: TradeTypes.TAKE_PROFIT,
|
||||||
|
[AbacusOrderType.takeProfitLimit.rawValue]: TradeTypes.TAKE_PROFIT,
|
||||||
|
|
||||||
[AbacusOrderType.takeProfitMarket.name]: TradeTypes.TAKE_PROFIT_MARKET,
|
[AbacusOrderType.takeProfitMarket.name]: TradeTypes.TAKE_PROFIT_MARKET,
|
||||||
|
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
|
||||||
|
|
||||||
[AbacusOrderType.liquidated.name]: null,
|
[AbacusOrderType.liquidated.name]: null,
|
||||||
[AbacusOrderType.liquidation.name]: null,
|
[AbacusOrderType.liquidation.name]: null,
|
||||||
|
|
||||||
|
[AbacusOrderType.liquidation.name]: null,
|
||||||
|
[AbacusOrderType.liquidation.rawValue]: null,
|
||||||
|
|
||||||
[AbacusOrderType.trailingStop.name]: null,
|
[AbacusOrderType.trailingStop.name]: null,
|
||||||
|
[AbacusOrderType.trailingStop.rawValue]: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom types involving Abacus
|
// Custom types involving Abacus
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
|||||||
elapsedTime?: number;
|
elapsedTime?: number;
|
||||||
blockHeight?: number;
|
blockHeight?: number;
|
||||||
indexerBlockHeight?: number;
|
indexerBlockHeight?: number;
|
||||||
|
trailingBlocks?: number;
|
||||||
}
|
}
|
||||||
: // Navigation
|
: // Navigation
|
||||||
T extends AnalyticsEvent.NavigatePage
|
T extends AnalyticsEvent.NavigatePage
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ export enum DialogTypes {
|
|||||||
Deposit = 'Deposit',
|
Deposit = 'Deposit',
|
||||||
DisconnectWallet = 'DisconnectWallet',
|
DisconnectWallet = 'DisconnectWallet',
|
||||||
ExchangeOffline = 'ExchangeOffline',
|
ExchangeOffline = 'ExchangeOffline',
|
||||||
|
ExternalLink = 'ExternalLink',
|
||||||
FillDetails = 'FillDetails',
|
FillDetails = 'FillDetails',
|
||||||
Help = 'Help',
|
Help = 'Help',
|
||||||
ExternalNavKeplr = 'ExternalNavKeplr',
|
ExternalNavKeplr = 'ExternalNavKeplr',
|
||||||
@@ -10,13 +11,14 @@ export enum DialogTypes {
|
|||||||
MobileSignIn = 'MobileSignIn',
|
MobileSignIn = 'MobileSignIn',
|
||||||
Onboarding = 'Onboarding',
|
Onboarding = 'Onboarding',
|
||||||
OrderDetails = 'OrderDetails',
|
OrderDetails = 'OrderDetails',
|
||||||
|
Preferences = 'Preferences',
|
||||||
RateLimit = 'RateLimit',
|
RateLimit = 'RateLimit',
|
||||||
Receive = 'Receive',
|
|
||||||
RestrictedGeo = 'RestrictedGeo',
|
RestrictedGeo = 'RestrictedGeo',
|
||||||
RestrictedWallet = 'RestrictedWallet',
|
RestrictedWallet = 'RestrictedWallet',
|
||||||
Trade = 'Trade',
|
Trade = 'Trade',
|
||||||
Transfer = 'Transfer',
|
Transfer = 'Transfer',
|
||||||
Withdraw = 'Withdraw',
|
Withdraw = 'Withdraw',
|
||||||
|
ManageFunds = 'ManageFunds',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TradeBoxDialogTypes {
|
export enum TradeBoxDialogTypes {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export enum LocalStorageKey {
|
|||||||
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
|
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
|
||||||
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
|
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
|
||||||
TransferNotifications = 'dydx.TransferNotifications',
|
TransferNotifications = 'dydx.TransferNotifications',
|
||||||
|
NotificationPreferences = 'dydx.NotificationPreferences',
|
||||||
|
|
||||||
// UI State
|
// UI State
|
||||||
LastViewedMarket = 'dydx.LastViewedMarket',
|
LastViewedMarket = 'dydx.LastViewedMarket',
|
||||||
@@ -21,9 +22,13 @@ export enum LocalStorageKey {
|
|||||||
SelectedTheme = 'dydx.SelectedTheme',
|
SelectedTheme = 'dydx.SelectedTheme',
|
||||||
SelectedTradeLayout = 'dydx.SelectedTradeLayout',
|
SelectedTradeLayout = 'dydx.SelectedTradeLayout',
|
||||||
TradingViewChartConfig = 'dydx.TradingViewChartConfig',
|
TradingViewChartConfig = 'dydx.TradingViewChartConfig',
|
||||||
|
HasSeenLaunchIncentives = 'dydx.HasSeenLaunchIncentives',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LOCAL_STORAGE_VERSIONS = {
|
export const LOCAL_STORAGE_VERSIONS = {
|
||||||
[LocalStorageKey.EvmDerivedAddresses]: 'v2',
|
[LocalStorageKey.EvmDerivedAddresses]: 'v2',
|
||||||
|
[LocalStorageKey.NotificationPreferences]: 'v1',
|
||||||
|
[LocalStorageKey.TransferNotifications]: 'v1',
|
||||||
|
[LocalStorageKey.Notifications]: 'v1',
|
||||||
// TODO: version all localStorage keys
|
// TODO: version all localStorage keys
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
LOCALE_DATA,
|
LOCALE_DATA,
|
||||||
TOOLTIPS,
|
TOOLTIPS,
|
||||||
WARNINGS_STRING_KEYS,
|
WARNINGS_STRING_KEYS,
|
||||||
|
NOTIFICATIONS_STRING_KEYS,
|
||||||
|
NOTIFICATIONS,
|
||||||
} from '@dydxprotocol/v4-localization';
|
} from '@dydxprotocol/v4-localization';
|
||||||
|
|
||||||
import { type LinksConfigs } from '@/hooks/useURLConfigs';
|
import { type LinksConfigs } from '@/hooks/useURLConfigs';
|
||||||
@@ -27,6 +29,7 @@ export enum SupportedLocales {
|
|||||||
|
|
||||||
export const EN_LOCALE_DATA = {
|
export const EN_LOCALE_DATA = {
|
||||||
...LOCALE_DATA[SupportedLocales.EN],
|
...LOCALE_DATA[SupportedLocales.EN],
|
||||||
|
...NOTIFICATIONS[SupportedLocales.EN],
|
||||||
TOOLTIPS: TOOLTIPS[SupportedLocales.EN],
|
TOOLTIPS: TOOLTIPS[SupportedLocales.EN],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,8 +37,13 @@ export const STRING_KEYS = {
|
|||||||
...APP_STRING_KEYS,
|
...APP_STRING_KEYS,
|
||||||
...ERRORS_STRING_KEYS,
|
...ERRORS_STRING_KEYS,
|
||||||
...WARNINGS_STRING_KEYS,
|
...WARNINGS_STRING_KEYS,
|
||||||
|
...NOTIFICATIONS_STRING_KEYS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const STRING_KEY_VALUES = Object.fromEntries(
|
||||||
|
Object.values(STRING_KEYS).map((key) => [key, key])
|
||||||
|
);
|
||||||
|
|
||||||
export type StringKey = keyof typeof STRING_KEYS;
|
export type StringKey = keyof typeof STRING_KEYS;
|
||||||
|
|
||||||
export type LocaleData = typeof EN_LOCALE_DATA;
|
export type LocaleData = typeof EN_LOCALE_DATA;
|
||||||
|
|||||||
@@ -25,4 +25,5 @@ export const DEFAULT_MARKETID = 'ETH-USD';
|
|||||||
export enum FundingDirection {
|
export enum FundingDirection {
|
||||||
ToShort = 'ToShort',
|
ToShort = 'ToShort',
|
||||||
ToLong = 'ToLong',
|
ToLong = 'ToLong',
|
||||||
|
None = 'None',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type MenuItem<MenuItemValue, MenuItemTypes = string> = {
|
|||||||
tag?: React.ReactNode;
|
tag?: React.ReactNode;
|
||||||
slotAfter?: React.ReactNode;
|
slotAfter?: React.ReactNode;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
slotCustomContent?: React.ReactNode;
|
||||||
|
|
||||||
href?: string;
|
href?: string;
|
||||||
onSelect?: (key: MenuItemValue) => void;
|
onSelect?: (key: MenuItemValue) => void;
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { StatusResponse } from '@0xsquid/sdk';
|
|||||||
|
|
||||||
/** implemented in useNotificationTypes */
|
/** implemented in useNotificationTypes */
|
||||||
export enum NotificationType {
|
export enum NotificationType {
|
||||||
OrderStatusChanged = 'OrderStatusChanged',
|
AbacusGenerated = 'AbacusGenerated',
|
||||||
SquidTransfer = 'SquidTransfer',
|
SquidTransfer = 'SquidTransfer',
|
||||||
|
ReleaseUpdates = 'ReleaseUpdates',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NotificationComponentType {}
|
export enum NotificationComponentType {}
|
||||||
@@ -59,7 +60,7 @@ export enum NotificationStatus {
|
|||||||
/** Toast or NotificationsMenu item interacted with or dismissed. "Seen" in NotificationsMenu. */
|
/** Toast or NotificationsMenu item interacted with or dismissed. "Seen" in NotificationsMenu. */
|
||||||
Seen,
|
Seen,
|
||||||
|
|
||||||
/** Notification marked for deletion. "Archived" in NotificationsMenu. */
|
/** Notification marked for deletion. Hidden in NotificationsMenu. */
|
||||||
Cleared,
|
Cleared,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,14 +81,22 @@ export type Notifications = Record<NotificationId, Notification<any>>;
|
|||||||
/** Notification display data derived from app state at runtime. */
|
/** Notification display data derived from app state at runtime. */
|
||||||
export type NotificationDisplayData = {
|
export type NotificationDisplayData = {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
|
title: string; // Title for Toast, Notification, and Push Notification
|
||||||
|
body?: string; // Description body for Toast, Notification, and Push Notification
|
||||||
|
|
||||||
title?: string;
|
slotTitleLeft?: React.ReactNode;
|
||||||
|
slotTitleRight?: React.ReactNode;
|
||||||
|
|
||||||
description?: React.ReactNode;
|
groupKey: string; // Grouping key toast notification stacking
|
||||||
|
|
||||||
customContent?: React.ReactNode;
|
// Overrides title/body for Notification in NotificationMenu
|
||||||
|
renderCustomBody?: ({
|
||||||
customMenuContent?: React.ReactNode;
|
isToast,
|
||||||
|
notification,
|
||||||
|
}: {
|
||||||
|
isToast?: boolean;
|
||||||
|
notification: Notification;
|
||||||
|
}) => React.ReactNode; // Custom Notification
|
||||||
|
|
||||||
actionDescription?: string;
|
actionDescription?: string;
|
||||||
|
|
||||||
@@ -115,12 +124,29 @@ export type NotificationDisplayData = {
|
|||||||
toastDuration?: number;
|
toastDuration?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum TransferNotificationTypes {
|
||||||
|
Withdrawal = 'withdrawal',
|
||||||
|
Deposit = 'deposit',
|
||||||
|
}
|
||||||
|
|
||||||
// Notification types
|
// Notification types
|
||||||
export type TransferNotifcation = {
|
export type TransferNotifcation = {
|
||||||
txHash: string;
|
txHash: string;
|
||||||
|
type?: TransferNotificationTypes;
|
||||||
toChainId?: string;
|
toChainId?: string;
|
||||||
fromChainId?: string;
|
fromChainId?: string;
|
||||||
toAmount?: number;
|
toAmount?: number;
|
||||||
triggeredAt?: number;
|
triggeredAt?: number;
|
||||||
|
isCctp?: boolean;
|
||||||
|
errorCount?: number;
|
||||||
status?: StatusResponse;
|
status?: StatusResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Struct to store whether a NotificationType should be triggered
|
||||||
|
*/
|
||||||
|
export type NotificationPreferences = {
|
||||||
|
[key in NotificationType]: boolean;
|
||||||
|
} & { version: string };
|
||||||
|
|
||||||
|
export const DEFAULT_TOAST_AUTO_CLOSE_MS = 5000;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const LEVERAGE_DECIMALS = 2;
|
|||||||
export const TOKEN_DECIMALS = 4;
|
export const TOKEN_DECIMALS = 4;
|
||||||
export const LARGE_TOKEN_DECIMALS = 2;
|
export const LARGE_TOKEN_DECIMALS = 2;
|
||||||
export const FEE_DECIMALS = 3;
|
export const FEE_DECIMALS = 3;
|
||||||
|
export const FUNDING_DECIMALS = 6;
|
||||||
|
|
||||||
export const QUANTUM_MULTIPLIER = 1_000_000;
|
export const QUANTUM_MULTIPLIER = 1_000_000;
|
||||||
|
|
||||||
@@ -18,3 +19,7 @@ export enum NumberSign {
|
|||||||
Negative = 'Negative',
|
Negative = 'Negative',
|
||||||
Neutral = 'Neutral',
|
Neutral = 'Neutral',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deposit/Withdraw
|
||||||
|
export const MAX_CCTP_TRANSFER_AMOUNT = 1_000_000;
|
||||||
|
export const MAX_PRICE_IMPACT = 0.02; // 2%
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { type TooltipStrings, TOOLTIP_STRING_KEYS } from '@/constants/localization';
|
import { type TooltipStrings, TOOLTIP_STRING_KEYS } from '@/constants/localization';
|
||||||
|
|
||||||
export const depositTooltips: TooltipStrings = {
|
export const depositTooltips: TooltipStrings = {
|
||||||
|
'minimum-deposit-amount': ({ stringGetter }) => ({
|
||||||
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_DEPOSIT_AMOUNT_TITLE }),
|
||||||
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_DEPOSIT_AMOUNT_BODY }),
|
||||||
|
}),
|
||||||
swap: ({ stringGetter }) => ({
|
swap: ({ stringGetter }) => ({
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_BODY }),
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ export const withdrawTooltips: TooltipStrings = {
|
|||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_BODY }),
|
||||||
}),
|
}),
|
||||||
|
'minimum-amount-received': ({ stringGetter }) => ({
|
||||||
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_AMOUNT_RECEIVED_TITLE }),
|
||||||
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_AMOUNT_RECEIVED_BODY }),
|
||||||
|
}),
|
||||||
'withdraw-types': ({ stringGetter }) => ({
|
'withdraw-types': ({ stringGetter }) => ({
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_BODY }),
|
||||||
|
|||||||
@@ -116,3 +116,15 @@ export enum MobilePlaceOrderSteps {
|
|||||||
PlacingOrder = 'PlacingOrder',
|
PlacingOrder = 'PlacingOrder',
|
||||||
Confirmation = 'Confirmation',
|
Confirmation = 'Confirmation',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CLEARED_TRADE_INPUTS = {
|
||||||
|
limitPriceInput: '',
|
||||||
|
triggerPriceInput: '',
|
||||||
|
trailingPercentInput: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CLEARED_SIZE_INPUTS = {
|
||||||
|
amountInput: '',
|
||||||
|
usdAmountInput: '',
|
||||||
|
leverageInput: '',
|
||||||
|
};
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { DydxNetwork, ENVIRONMENT_CONFIG_MAP } from './networks';
|
|||||||
export enum WalletConnectionType {
|
export enum WalletConnectionType {
|
||||||
CoinbaseWalletSdk = 'coinbaseWalletSdk',
|
CoinbaseWalletSdk = 'coinbaseWalletSdk',
|
||||||
CosmosSigner = 'CosmosSigner',
|
CosmosSigner = 'CosmosSigner',
|
||||||
|
Privy = 'Privy',
|
||||||
InjectedEip1193 = 'injectedEip1193',
|
InjectedEip1193 = 'injectedEip1193',
|
||||||
WalletConnect2 = 'walletConnect2',
|
WalletConnect2 = 'walletConnect2',
|
||||||
}
|
}
|
||||||
@@ -68,6 +69,9 @@ export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectio
|
|||||||
[WalletConnectionType.CosmosSigner]: {
|
[WalletConnectionType.CosmosSigner]: {
|
||||||
name: 'CosmosSigner',
|
name: 'CosmosSigner',
|
||||||
},
|
},
|
||||||
|
[WalletConnectionType.Privy]: {
|
||||||
|
name: 'Privy',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wallets
|
// Wallets
|
||||||
@@ -90,6 +94,7 @@ export enum WalletType {
|
|||||||
WalletConnect2 = 'WALLETCONNECT_2',
|
WalletConnect2 = 'WALLETCONNECT_2',
|
||||||
// TestWallet = 'TEST_WALLET',
|
// TestWallet = 'TEST_WALLET',
|
||||||
OtherWallet = 'OTHER_WALLET',
|
OtherWallet = 'OTHER_WALLET',
|
||||||
|
Privy = 'PRIVY',
|
||||||
}
|
}
|
||||||
|
|
||||||
const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
|
const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
|
||||||
@@ -243,6 +248,12 @@ export const wallets: Record<WalletType, WalletConfig> = {
|
|||||||
icon: KeplrIcon,
|
icon: KeplrIcon,
|
||||||
connectionTypes: [WalletConnectionType.CosmosSigner],
|
connectionTypes: [WalletConnectionType.CosmosSigner],
|
||||||
},
|
},
|
||||||
|
[WalletType.Privy]: {
|
||||||
|
type: WalletType.Privy,
|
||||||
|
stringKey: STRING_KEYS.KEPLR,
|
||||||
|
icon: GenericWalletIcon,
|
||||||
|
connectionTypes: [WalletConnectionType.Privy],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Injected EIP-1193 Providers
|
// Injected EIP-1193 Providers
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { useShouldShowFooter } from './useShouldShowFooter';
|
|||||||
import { useSelectedNetwork } from './useSelectedNetwork';
|
import { useSelectedNetwork } from './useSelectedNetwork';
|
||||||
import { useStringGetter } from './useStringGetter';
|
import { useStringGetter } from './useStringGetter';
|
||||||
import { useSubaccount } from './useSubaccount';
|
import { useSubaccount } from './useSubaccount';
|
||||||
|
import { useTradeFormInputs } from './useTradeFormInputs';
|
||||||
import { useURLConfigs } from './useURLConfigs';
|
import { useURLConfigs } from './useURLConfigs';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -49,5 +50,6 @@ export {
|
|||||||
useSelectedNetwork,
|
useSelectedNetwork,
|
||||||
useStringGetter,
|
useStringGetter,
|
||||||
useSubaccount,
|
useSubaccount,
|
||||||
|
useTradeFormInputs,
|
||||||
useURLConfigs,
|
useURLConfigs,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react';
|
import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react';
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { AES, enc } from 'crypto-js';
|
import { AES, enc } from 'crypto-js';
|
||||||
import { LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
|
import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
||||||
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets';
|
import {
|
||||||
|
DydxAddress,
|
||||||
|
EvmAddress,
|
||||||
|
PrivateInformation,
|
||||||
|
WalletConnectionType,
|
||||||
|
getSignTypedData,
|
||||||
|
} from '@/constants/wallets';
|
||||||
|
|
||||||
import { setOnboardingState, setOnboardingGuard } from '@/state/account';
|
import { setOnboardingState, setOnboardingGuard } from '@/state/account';
|
||||||
import { forceOpenDialog } from '@/state/dialogs';
|
import { forceOpenDialog } from '@/state/dialogs';
|
||||||
@@ -20,6 +26,11 @@ import { useLocalStorage } from './useLocalStorage';
|
|||||||
import { useRestrictions } from './useRestrictions';
|
import { useRestrictions } from './useRestrictions';
|
||||||
import { useWalletConnection } from './useWalletConnection';
|
import { useWalletConnection } from './useWalletConnection';
|
||||||
|
|
||||||
|
import { useSignTypedData } from 'wagmi';
|
||||||
|
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||||
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
|
import { usePrivy } from '@privy-io/react-auth';
|
||||||
|
|
||||||
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
|
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
|
||||||
|
|
||||||
AccountsContext.displayName = 'Accounts';
|
AccountsContext.displayName = 'Accounts';
|
||||||
@@ -136,8 +147,8 @@ const useAccountsContext = () => {
|
|||||||
getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => {
|
getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => {
|
||||||
try {
|
try {
|
||||||
const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress);
|
const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress);
|
||||||
setDydxSubaccounts(response.subaccounts);
|
setDydxSubaccounts(response?.subaccounts);
|
||||||
return response.subaccounts;
|
return response?.subaccounts ?? [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 404 is expected if the user has no subaccounts
|
// 404 is expected if the user has no subaccounts
|
||||||
if (error.status === 404) {
|
if (error.status === 404) {
|
||||||
@@ -153,6 +164,7 @@ const useAccountsContext = () => {
|
|||||||
|
|
||||||
// dYdX wallet / onboarding state
|
// dYdX wallet / onboarding state
|
||||||
const [localDydxWallet, setLocalDydxWallet] = useState<LocalWallet>();
|
const [localDydxWallet, setLocalDydxWallet] = useState<LocalWallet>();
|
||||||
|
const [localNobleWallet, setLocalNobleWallet] = useState<LocalWallet>();
|
||||||
const [hdKey, setHdKey] = useState<PrivateInformation>();
|
const [hdKey, setHdKey] = useState<PrivateInformation>();
|
||||||
|
|
||||||
const dydxAccounts = useMemo(() => localDydxWallet?.accounts, [localDydxWallet]);
|
const dydxAccounts = useMemo(() => localDydxWallet?.accounts, [localDydxWallet]);
|
||||||
@@ -162,6 +174,11 @@ const useAccountsContext = () => {
|
|||||||
[localDydxWallet]
|
[localDydxWallet]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const nobleAddress = useMemo(
|
||||||
|
() => localNobleWallet?.address,
|
||||||
|
[localNobleWallet]
|
||||||
|
);
|
||||||
|
|
||||||
const setWalletFromEvmSignature = async (signature: string) => {
|
const setWalletFromEvmSignature = async (signature: string) => {
|
||||||
const { wallet, mnemonic, privateKey, publicKey } = await getWalletFromEvmSignature({
|
const { wallet, mnemonic, privateKey, publicKey } = await getWalletFromEvmSignature({
|
||||||
signature,
|
signature,
|
||||||
@@ -176,6 +193,20 @@ const useAccountsContext = () => {
|
|||||||
}
|
}
|
||||||
}, [evmAddress, dydxAddress]);
|
}, [evmAddress, dydxAddress]);
|
||||||
|
|
||||||
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
|
|
||||||
|
const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
|
||||||
|
|
||||||
|
const signTypedData = getSignTypedData(selectedNetwork);
|
||||||
|
const { signTypedDataAsync } = useSignTypedData({
|
||||||
|
...signTypedData,
|
||||||
|
domain: {
|
||||||
|
...signTypedData.domain,
|
||||||
|
chainId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { ready, authenticated } = usePrivy();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (connectedDydxAddress && signerGraz) {
|
if (connectedDydxAddress && signerGraz) {
|
||||||
@@ -192,7 +223,17 @@ const useAccountsContext = () => {
|
|||||||
|
|
||||||
const evmDerivedAccount = evmDerivedAddresses[evmAddress];
|
const evmDerivedAccount = evmDerivedAddresses[evmAddress];
|
||||||
|
|
||||||
if (evmDerivedAccount?.encryptedSignature) {
|
if (walletConnectionType === WalletConnectionType.Privy && authenticated && ready) {
|
||||||
|
try {
|
||||||
|
const signature = await signTypedDataAsync();
|
||||||
|
|
||||||
|
await setWalletFromEvmSignature(signature);
|
||||||
|
dispatch(setOnboardingState(OnboardingState.AccountConnected));
|
||||||
|
} catch (error) {
|
||||||
|
log('useAccounts/decryptSignature', error);
|
||||||
|
forgetEvmSignature();
|
||||||
|
}
|
||||||
|
} else if (evmDerivedAccount?.encryptedSignature) {
|
||||||
try {
|
try {
|
||||||
const signature = decryptSignature(evmDerivedAccount.encryptedSignature);
|
const signature = decryptSignature(evmDerivedAccount.encryptedSignature);
|
||||||
|
|
||||||
@@ -211,7 +252,15 @@ const useAccountsContext = () => {
|
|||||||
dispatch(setOnboardingState(OnboardingState.Disconnected));
|
dispatch(setOnboardingState(OnboardingState.Disconnected));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [evmAddress, evmDerivedAddresses, signerWagmi, connectedDydxAddress, signerGraz]);
|
}, [
|
||||||
|
evmAddress,
|
||||||
|
evmDerivedAddresses,
|
||||||
|
signerWagmi,
|
||||||
|
connectedDydxAddress,
|
||||||
|
signerGraz,
|
||||||
|
authenticated,
|
||||||
|
ready,
|
||||||
|
]);
|
||||||
|
|
||||||
// abacus
|
// abacus
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -219,6 +268,17 @@ const useAccountsContext = () => {
|
|||||||
else abacusStateManager.attemptDisconnectAccount();
|
else abacusStateManager.attemptDisconnectAccount();
|
||||||
}, [localDydxWallet]);
|
}, [localDydxWallet]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const setNobleWallet = async () => {
|
||||||
|
if (hdKey?.mnemonic) {
|
||||||
|
const nobleWallet = await LocalWallet.fromMnemonic(hdKey.mnemonic, NOBLE_BECH32_PREFIX);
|
||||||
|
abacusStateManager.setNobleWallet(nobleWallet);
|
||||||
|
setLocalNobleWallet(nobleWallet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setNobleWallet();
|
||||||
|
}, [hdKey?.mnemonic]);
|
||||||
|
|
||||||
// clear subaccounts when no dydxAddress is set
|
// clear subaccounts when no dydxAddress is set
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -312,6 +372,7 @@ const useAccountsContext = () => {
|
|||||||
localDydxWallet,
|
localDydxWallet,
|
||||||
dydxAccounts,
|
dydxAccounts,
|
||||||
dydxAddress,
|
dydxAddress,
|
||||||
|
nobleAddress,
|
||||||
|
|
||||||
// Onboarding state
|
// Onboarding state
|
||||||
saveHasAcknowledgedTerms,
|
saveHasAcknowledgedTerms,
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// AnalyticsEvent.NetworkStatus
|
// AnalyticsEvent.NetworkStatus
|
||||||
const { height, indexerHeight, status } = useApiState();
|
const { height, indexerHeight, status, trailingBlocks} = useApiState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status) {
|
if (status) {
|
||||||
@@ -114,6 +114,7 @@ export const useAnalytics = () => {
|
|||||||
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
|
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
|
||||||
blockHeight: height ?? undefined,
|
blockHeight: height ?? undefined,
|
||||||
indexerBlockHeight: indexerHeight ?? undefined,
|
indexerBlockHeight: indexerHeight ?? undefined,
|
||||||
|
trailingBlocks: trailingBlocks ?? undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const getIndexerHeight = (apiState: Nullable<AbacusApiState>) => {
|
|||||||
export const useApiState = () => {
|
export const useApiState = () => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const apiState = useSelector(getApiState, shallowEqual);
|
const apiState = useSelector(getApiState, shallowEqual);
|
||||||
const { haltedBlock, height, status, trailingBlocks } = apiState ?? {};
|
const { haltedBlock, height, status, trailingBlocks} = apiState ?? {};
|
||||||
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
|
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
|
||||||
const indexerHeight = getIndexerHeight(apiState);
|
const indexerHeight = getIndexerHeight(apiState);
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ import { useMatch, useNavigate } from 'react-router-dom';
|
|||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
import { AppRoute } from '@/constants/routes';
|
import { AppRoute } from '@/constants/routes';
|
||||||
|
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||||
|
|
||||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||||
import { closeDialogInTradeBox } from '@/state/dialogs';
|
import { closeDialogInTradeBox } from '@/state/dialogs';
|
||||||
import { setCurrentMarketId } from '@/state/perpetuals';
|
import { setCurrentMarketId } from '@/state/perpetuals';
|
||||||
|
import { getMarketIds } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
import abacusStateManager from '@/lib/abacus';
|
||||||
|
|
||||||
import { useLocalStorage } from './useLocalStorage';
|
|
||||||
import { getMarketIds } from '@/state/perpetualsSelectors';
|
|
||||||
|
|
||||||
export const useCurrentMarketId = () => {
|
export const useCurrentMarketId = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const match = useMatch(`/${AppRoute.Trade}/:marketId`);
|
const match = useMatch(`/${AppRoute.Trade}/:marketId`);
|
||||||
@@ -22,6 +21,8 @@ export const useCurrentMarketId = () => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
const marketIds = useSelector(getMarketIds, shallowEqual);
|
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||||
|
const hasMarketIds = marketIds.length > 0;
|
||||||
|
|
||||||
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
|
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
|
||||||
key: LocalStorageKey.LastViewedMarket,
|
key: LocalStorageKey.LastViewedMarket,
|
||||||
defaultValue: DEFAULT_MARKETID,
|
defaultValue: DEFAULT_MARKETID,
|
||||||
@@ -31,22 +32,40 @@ export const useCurrentMarketId = () => {
|
|||||||
if (marketIds.length === 0) return marketId ?? lastViewedMarket;
|
if (marketIds.length === 0) return marketId ?? lastViewedMarket;
|
||||||
if (!marketIds.includes(marketId ?? lastViewedMarket)) return DEFAULT_MARKETID;
|
if (!marketIds.includes(marketId ?? lastViewedMarket)) return DEFAULT_MARKETID;
|
||||||
return marketId ?? lastViewedMarket;
|
return marketId ?? lastViewedMarket;
|
||||||
}, [marketIds, marketId]);
|
}, [hasMarketIds, marketId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLastViewedMarket(validId);
|
// If v4_markets has not been subscribed to yet or marketId is not specified, default to validId
|
||||||
dispatch(setCurrentMarketId(validId));
|
if (!hasMarketIds || !marketId) {
|
||||||
dispatch(closeDialogInTradeBox());
|
setLastViewedMarket(validId);
|
||||||
|
dispatch(setCurrentMarketId(validId));
|
||||||
|
dispatch(closeDialogInTradeBox());
|
||||||
|
|
||||||
navigate(`${AppRoute.Trade}/${validId}`, {
|
if (validId !== marketId) {
|
||||||
replace: true,
|
navigate(`${AppRoute.Trade}/${validId}`, {
|
||||||
});
|
replace: true,
|
||||||
}, [validId]);
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If v4_markets has been subscribed to, check if marketId is valid
|
||||||
|
if (!marketIds.includes(marketId)) {
|
||||||
|
// If marketId is not valid (i.e. final settlement), navigate to markets page
|
||||||
|
navigate(AppRoute.Markets, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If marketId is valid, set currentMarketId
|
||||||
|
setLastViewedMarket(marketId);
|
||||||
|
dispatch(setCurrentMarketId(marketId));
|
||||||
|
dispatch(closeDialogInTradeBox());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [hasMarketIds, marketId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check for marketIds otherwise Abacus will silently fail its isMarketValid check
|
// Check for marketIds otherwise Abacus will silently fail its isMarketValid check
|
||||||
if (marketIds) {
|
if (marketIds) {
|
||||||
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
|
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
|
||||||
}
|
}
|
||||||
}, [selectedNetwork, marketIds, marketId]);
|
}, [selectedNetwork, hasMarketIds, marketId]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const useDisplayedWallets = () => {
|
|||||||
// WalletType.BitKeep,
|
// WalletType.BitKeep,
|
||||||
// WalletType.Coin98,
|
// WalletType.Coin98,
|
||||||
|
|
||||||
|
WalletType.Privy,
|
||||||
WalletType.OtherWallet,
|
WalletType.OtherWallet,
|
||||||
].filter(isTruthy);
|
].filter(isTruthy);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
|
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import type { StatusResponse } from '@0xsquid/sdk';
|
|
||||||
|
|
||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
|
||||||
import { type TransferNotifcation } from '@/constants/notifications';
|
import { type TransferNotifcation } from '@/constants/notifications';
|
||||||
|
|
||||||
import { useAccounts } from '@/hooks/useAccounts';
|
import { useAccounts } from '@/hooks/useAccounts';
|
||||||
import { STATUS_ERROR_GRACE_PERIOD, useSquid } from '@/hooks/useSquid';
|
|
||||||
|
import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid';
|
||||||
|
|
||||||
import { useLocalStorage } from './useLocalStorage';
|
import { useLocalStorage } from './useLocalStorage';
|
||||||
|
|
||||||
const LocalNotificationsContext = createContext<
|
const LocalNotificationsContext = createContext<
|
||||||
@@ -22,16 +22,32 @@ export const LocalNotificationsProvider = ({ ...props }) => (
|
|||||||
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
|
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
|
||||||
|
|
||||||
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
|
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
|
||||||
|
const ERROR_COUNT_THRESHOLD = 3;
|
||||||
|
|
||||||
const useLocalNotificationsContext = () => {
|
const useLocalNotificationsContext = () => {
|
||||||
// transfer notifications
|
// transfer notifications
|
||||||
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
|
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
|
||||||
[key: `dydx${string}`]: TransferNotifcation[];
|
[key: `dydx${string}`]: TransferNotifcation[];
|
||||||
|
version: string;
|
||||||
}>({
|
}>({
|
||||||
key: LocalStorageKey.TransferNotifications,
|
key: LocalStorageKey.TransferNotifications,
|
||||||
defaultValue: {},
|
defaultValue: {
|
||||||
|
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ensure version matches, otherwise wipe
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
allTransferNotifications?.version !==
|
||||||
|
LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications]
|
||||||
|
) {
|
||||||
|
setAllTransferNotifications({
|
||||||
|
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [allTransferNotifications]);
|
||||||
|
|
||||||
const { dydxAddress } = useAccounts();
|
const { dydxAddress } = useAccounts();
|
||||||
|
|
||||||
const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : [];
|
const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : [];
|
||||||
@@ -52,49 +68,65 @@ const useLocalNotificationsContext = () => {
|
|||||||
[transferNotifications]
|
[transferNotifications]
|
||||||
);
|
);
|
||||||
|
|
||||||
const squid = useSquid();
|
useQuery({
|
||||||
|
queryKey: 'getTransactionStatus',
|
||||||
const { data: transferStatuses } = useQuery({
|
|
||||||
queryKey: ['getTransactionStatus', transferNotifications],
|
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const statuses: { [key: string]: StatusResponse } = {};
|
const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => {
|
||||||
for (const {
|
const newTransferNotifications = await Promise.all(
|
||||||
txHash,
|
transferNotifications.map(async (transferNotification) => {
|
||||||
toChainId,
|
const {
|
||||||
fromChainId,
|
txHash,
|
||||||
triggeredAt,
|
toChainId,
|
||||||
status: currentStatus,
|
fromChainId,
|
||||||
} of transferNotifications) {
|
triggeredAt,
|
||||||
try {
|
isCctp,
|
||||||
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
|
errorCount,
|
||||||
|
status: currentStatus,
|
||||||
|
} = transferNotification;
|
||||||
|
|
||||||
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
|
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||||
if (status) statuses[txHash] = status;
|
// also error can some time come back as an empty object so we need to ignore for that
|
||||||
} catch (error) {
|
const hasErrors = !!currentStatus?.errors ||
|
||||||
// ignore errors for the first 120s since the status might not be available yet
|
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
|
||||||
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
|
|
||||||
statuses[txHash] = error;
|
if (
|
||||||
}
|
!hasErrors &&
|
||||||
}
|
(!currentStatus?.squidTransactionStatus ||
|
||||||
}
|
currentStatus?.squidTransactionStatus === 'ongoing')
|
||||||
return statuses;
|
) {
|
||||||
|
try {
|
||||||
|
const status = await fetchSquidStatus({
|
||||||
|
transactionId: txHash,
|
||||||
|
toChainId,
|
||||||
|
fromChainId,
|
||||||
|
}, isCctp);
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
transferNotification.status = status;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
|
||||||
|
if (errorCount && errorCount > ERROR_COUNT_THRESHOLD) {
|
||||||
|
transferNotification.status = error;
|
||||||
|
} else {
|
||||||
|
transferNotification.errorCount = errorCount ? errorCount + 1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transferNotification;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return newTransferNotifications;
|
||||||
|
};
|
||||||
|
const newTransferNotifications = await processTransferNotifications(transferNotifications);
|
||||||
|
setTransferNotifications(newTransferNotifications);
|
||||||
},
|
},
|
||||||
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
|
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!transferStatuses) return;
|
|
||||||
const newTransferNotifications = transferNotifications.map((notification) => {
|
|
||||||
const status = transferStatuses[notification.txHash];
|
|
||||||
if (!status) return notification;
|
|
||||||
return {
|
|
||||||
...notification,
|
|
||||||
status,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setTransferNotifications(newTransferNotifications);
|
|
||||||
}, [transferStatuses]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transferNotifications,
|
transferNotifications,
|
||||||
addTransferNotification,
|
addTransferNotification,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useNetwork, useSwitchNetwork } from 'wagmi';
|
import { useNetwork, useSwitchNetwork } from 'wagmi';
|
||||||
|
import { useSwitchNetwork as useSwitchNetworkPrivy } from '@privy-io/wagmi-connector';
|
||||||
|
import { WalletConnectionType } from '@/constants/wallets';
|
||||||
|
|
||||||
|
import { useWalletConnection } from './useWalletConnection';
|
||||||
|
|
||||||
export const useMatchingEvmNetwork = ({
|
export const useMatchingEvmNetwork = ({
|
||||||
chainId,
|
chainId,
|
||||||
@@ -11,7 +15,10 @@ export const useMatchingEvmNetwork = ({
|
|||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { chain } = useNetwork();
|
const { chain } = useNetwork();
|
||||||
|
const { walletConnectionType } = useWalletConnection();
|
||||||
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
|
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
|
||||||
|
const { isLoading: isLoadingPrivy, switchNetworkAsync: switchNetworkAsyncPrivy } =
|
||||||
|
useSwitchNetworkPrivy({ onError });
|
||||||
|
|
||||||
// If chainId is not a number, we can assume it is a non EVM compatible chain
|
// If chainId is not a number, we can assume it is a non EVM compatible chain
|
||||||
const isMatchingNetwork = useMemo(
|
const isMatchingNetwork = useMemo(
|
||||||
@@ -21,7 +28,11 @@ export const useMatchingEvmNetwork = ({
|
|||||||
|
|
||||||
const matchNetwork = useCallback(async () => {
|
const matchNetwork = useCallback(async () => {
|
||||||
if (!isMatchingNetwork) {
|
if (!isMatchingNetwork) {
|
||||||
await switchNetworkAsync?.(Number(chainId));
|
if (walletConnectionType === WalletConnectionType.Privy) {
|
||||||
|
await switchNetworkAsyncPrivy?.(Number(chainId));
|
||||||
|
} else {
|
||||||
|
await switchNetworkAsync?.(Number(chainId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [chainId, chain]);
|
}, [chainId, chain]);
|
||||||
|
|
||||||
@@ -34,6 +45,6 @@ export const useMatchingEvmNetwork = ({
|
|||||||
return {
|
return {
|
||||||
isMatchingNetwork,
|
isMatchingNetwork,
|
||||||
matchNetwork,
|
matchNetwork,
|
||||||
isSwitchingNetwork: isLoading,
|
isSwitchingNetwork: isLoading || isLoadingPrivy,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,125 +1,175 @@
|
|||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { type ReactNode, useEffect } from 'react';
|
||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import { groupBy } from 'lodash';
|
import { isEqual, groupBy } from 'lodash';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { AlertType } from '@/constants/alerts';
|
|
||||||
import { AbacusOrderStatus, ORDER_SIDES } from '@/constants/abacus';
|
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { STRING_KEYS, StringKey } from '@/constants/localization';
|
|
||||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
|
import { AppRoute } from '@/constants/routes';
|
||||||
import { ORDER_SIDE_STRINGS } from '@/constants/trade';
|
import { DydxChainAsset } from '@/constants/wallets';
|
||||||
|
|
||||||
import { useSelectedNetwork } from '@/hooks';
|
import {
|
||||||
|
STRING_KEYS,
|
||||||
|
STRING_KEY_VALUES,
|
||||||
|
type StringGetterFunction,
|
||||||
|
type StringKey,
|
||||||
|
} from '@/constants/localization';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type NotificationTypeConfig,
|
||||||
|
NotificationType,
|
||||||
|
DEFAULT_TOAST_AUTO_CLOSE_MS,
|
||||||
|
TransferNotificationTypes,
|
||||||
|
} from '@/constants/notifications';
|
||||||
|
|
||||||
|
import { useSelectedNetwork, useStringGetter } from '@/hooks';
|
||||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||||
|
|
||||||
|
import { AssetIcon } from '@/components/AssetIcon';
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
import { Output, OutputType } from '@/components/Output';
|
import { BlockRewardNotification } from '@/views/notifications/BlockRewardNotification';
|
||||||
import { TransferStatusToast } from '@/views/TransferStatus';
|
import { TradeNotification } from '@/views/notifications/TradeNotification';
|
||||||
|
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';
|
||||||
|
|
||||||
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
|
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
|
||||||
import { openDialog } from '@/state/dialogs';
|
import { openDialog } from '@/state/dialogs';
|
||||||
|
import { getAbacusNotifications } from '@/state/notificationsSelectors';
|
||||||
|
import { getMarketIds } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
|
import { formatSeconds } from '@/lib/timeUtils';
|
||||||
|
|
||||||
import { useStringGetter } from './useStringGetter';
|
const parseStringParamsForNotification = ({
|
||||||
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
stringGetter,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
stringGetter: StringGetterFunction;
|
||||||
|
value: unknown;
|
||||||
|
}): ReactNode => {
|
||||||
|
if (STRING_KEY_VALUES[value as StringKey]) {
|
||||||
|
return stringGetter({ key: value as StringKey });
|
||||||
|
}
|
||||||
|
|
||||||
export const notificationTypes = [
|
return value as ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const notificationTypes: NotificationTypeConfig[] = [
|
||||||
{
|
{
|
||||||
type: NotificationType.OrderStatusChanged,
|
type: NotificationType.AbacusGenerated,
|
||||||
|
useTrigger: ({ trigger }) => {
|
||||||
useTrigger: ({ trigger, lastUpdated }) => {
|
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
const abacusNotifications = useSelector(getAbacusNotifications, isEqual);
|
||||||
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
|
|
||||||
const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
|
|
||||||
|
|
||||||
const fills = useSelector(getSubaccountFills, shallowEqual) || [];
|
|
||||||
const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
|
|
||||||
|
|
||||||
const orderIds = useMemo(
|
|
||||||
() => [...Object.keys(ordersByOrderId), ...Object.keys(fillsByOrderId)],
|
|
||||||
[orders, fills]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const orderId of orderIds) {
|
for (const abacusNotif of abacusNotifications) {
|
||||||
const fills = fillsByOrderId[orderId];
|
const [abacusNotificationType = '', id = ''] = abacusNotif.id.split(':');
|
||||||
|
const parsedData = abacusNotif.data ? JSON.parse(abacusNotif.data) : {};
|
||||||
|
|
||||||
const order =
|
const params = Object.fromEntries(
|
||||||
ordersByOrderId[orderId] ??
|
Object.entries(parsedData).map(([key, value]) => {
|
||||||
(fills?.length
|
return [key, parseStringParamsForNotification({ stringGetter, value })];
|
||||||
? {
|
})
|
||||||
...fills[fills.length - 1],
|
);
|
||||||
id: orderId,
|
|
||||||
createdAtMilliseconds: Math.max(
|
switch (abacusNotificationType) {
|
||||||
...fills.map((fill) => fill.createdAtMilliseconds)
|
case 'order': {
|
||||||
|
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 }) => (
|
||||||
|
<TradeNotification
|
||||||
|
isToast={isToast}
|
||||||
|
data={parsedData}
|
||||||
|
notification={notification}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
status: AbacusOrderStatus.filled,
|
},
|
||||||
}
|
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data],
|
||||||
: undefined);
|
true
|
||||||
|
);
|
||||||
if (order)
|
break;
|
||||||
trigger(
|
}
|
||||||
order.id,
|
case 'blockReward': {
|
||||||
{
|
trigger(
|
||||||
icon: (
|
abacusNotif.id,
|
||||||
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />
|
{
|
||||||
),
|
icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
|
||||||
title: `${stringGetter({
|
title: stringGetter({ key: abacusNotif.title }),
|
||||||
key: order.resources.typeStringKey as StringKey,
|
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
|
||||||
})} ${
|
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
|
||||||
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0
|
toastSensitivity: 'foreground',
|
||||||
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
|
groupKey: abacusNotificationType,
|
||||||
: stringGetter({ key: order.resources.statusStringKey as StringKey })
|
renderCustomBody: ({ isToast, notification }) => (
|
||||||
}`,
|
<BlockRewardNotification
|
||||||
description: `${stringGetter({
|
isToast={isToast}
|
||||||
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
|
data={parsedData}
|
||||||
})} ${order.size} ${order.marketId} @ $${order.price}`,
|
notification={notification}
|
||||||
actionDescription: 'View Order',
|
/>
|
||||||
actionAltText: 'View this order in the Orders tab or the Notifications menu.',
|
),
|
||||||
toastSensitivity:
|
},
|
||||||
order.status === AbacusOrderStatus.pending ? 'foreground' : 'background',
|
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data],
|
||||||
toastDuration: 5000,
|
true
|
||||||
},
|
);
|
||||||
[order.status.name, order.size],
|
break;
|
||||||
!order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated
|
}
|
||||||
);
|
default:
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [orderIds]);
|
}, [abacusNotifications, stringGetter]);
|
||||||
},
|
},
|
||||||
|
useNotificationAction: () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
|
||||||
|
const ordersById = groupBy(orders, 'id');
|
||||||
|
const fills = useSelector(getSubaccountFills, shallowEqual) || [];
|
||||||
|
const fillsById = groupBy(fills, 'id');
|
||||||
|
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// useNotificationAction: () => {
|
return (notificationId: string) => {
|
||||||
// const dispatch = useDispatch();
|
const [abacusNotificationType = '', id = ''] = notificationId.split(':');
|
||||||
// const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
|
|
||||||
// const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
|
|
||||||
|
|
||||||
// const fills = useSelector(getSubaccountFills, shallowEqual) || [];
|
if (ordersById[id]) {
|
||||||
// const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
|
dispatch(
|
||||||
|
openDialog({
|
||||||
// return (id) => {
|
type: DialogTypes.OrderDetails,
|
||||||
// if (ordersByOrderId[id]) {
|
dialogProps: { orderId: id },
|
||||||
// dispatch(
|
})
|
||||||
// openDialog({
|
);
|
||||||
// type: DialogTypes.OrderDetails,
|
} else if (fillsById[id]) {
|
||||||
// dialogProps: { orderId: id },
|
dispatch(
|
||||||
// })
|
openDialog({
|
||||||
// );
|
type: DialogTypes.FillDetails,
|
||||||
// } else if (fillsByOrderId[id]) {
|
dialogProps: { fillId: id },
|
||||||
// const fillId = fillsByOrderId[id][0].id;
|
})
|
||||||
|
);
|
||||||
// dispatch(
|
} else if (marketIds.includes(id)) {
|
||||||
// openDialog({
|
navigate(`${AppRoute.Trade}/${id}`, {
|
||||||
// type: DialogTypes.FillDetails,
|
replace: true,
|
||||||
// dialogProps: { fillId },
|
});
|
||||||
// })
|
}
|
||||||
// );
|
};
|
||||||
// }
|
},
|
||||||
// };
|
},
|
||||||
// },
|
|
||||||
} as NotificationTypeConfig<string, [string, number]>,
|
|
||||||
{
|
{
|
||||||
type: NotificationType.SquidTransfer,
|
type: NotificationType.SquidTransfer,
|
||||||
useTrigger: ({ trigger }) => {
|
useTrigger: ({ trigger }) => {
|
||||||
@@ -127,81 +177,115 @@ export const notificationTypes = [
|
|||||||
const { transferNotifications } = useLocalNotifications();
|
const { transferNotifications } = useLocalNotifications();
|
||||||
const { selectedNetwork } = useSelectedNetwork();
|
const { selectedNetwork } = useSelectedNetwork();
|
||||||
|
|
||||||
const getTitleStringKey = useCallback((type: 'deposit' | 'withdrawal', finished: boolean) => {
|
|
||||||
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
|
|
||||||
if (type === 'deposit' && finished) return STRING_KEYS.DEPOSIT;
|
|
||||||
if (type === 'withdrawal' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
|
|
||||||
return STRING_KEYS.WITHDRAW;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const transfer of transferNotifications) {
|
for (const transfer of transferNotifications) {
|
||||||
const { fromChainId, status, txHash, toAmount } = transfer;
|
const { fromChainId, status, txHash, toAmount, type } = transfer;
|
||||||
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
const isFinished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
||||||
const type = fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId ? 'withdrawal' : 'deposit';
|
const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;
|
||||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
|
||||||
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
|
||||||
|
|
||||||
// TODO: confirm with design what the description should be
|
const transferType =
|
||||||
const description = (
|
type ?? fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
|
||||||
<div>
|
? TransferNotificationTypes.Withdrawal
|
||||||
<Styled.TransferText>
|
: TransferNotificationTypes.Deposit;
|
||||||
{type === 'deposit' ? 'Deposit of ' : 'Withdraw of '}
|
|
||||||
<Output type={OutputType.Fiat} value={toAmount} />
|
|
||||||
</Styled.TransferText>
|
|
||||||
|
|
||||||
{error && (
|
const title = stringGetter({
|
||||||
<Styled.ErrorMessage type={AlertType.Error}>
|
key: {
|
||||||
{stringGetter({
|
deposit: isFinished ? STRING_KEYS.DEPOSIT : STRING_KEYS.DEPOSIT_IN_PROGRESS,
|
||||||
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
withdrawal: isFinished ? STRING_KEYS.WITHDRAW : STRING_KEYS.WITHDRAW_IN_PROGRESS,
|
||||||
params: {
|
}[transferType],
|
||||||
ERROR_MESSAGE:
|
});
|
||||||
error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
|
|
||||||
},
|
const toChainEta = status?.toChain?.chainData?.estimatedRouteDuration || 0;
|
||||||
})}
|
const estimatedDuration = formatSeconds(Math.max(toChainEta, 0));
|
||||||
</Styled.ErrorMessage>
|
const body = stringGetter({
|
||||||
)}
|
key: STRING_KEYS.DEPOSIT_STATUS,
|
||||||
</div>
|
params: {
|
||||||
);
|
AMOUNT_USD: `${toAmount} ${DydxChainAsset.USDC.toUpperCase()}`,
|
||||||
|
ESTIMATED_DURATION: estimatedDuration,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
trigger(
|
trigger(
|
||||||
txHash,
|
txHash,
|
||||||
{
|
{
|
||||||
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />,
|
icon,
|
||||||
title: stringGetter({ key: getTitleStringKey(type, finished) }),
|
title,
|
||||||
description: description,
|
body,
|
||||||
customContent: (
|
renderCustomBody: ({ isToast, notification }) => (
|
||||||
<TransferStatusToast
|
<TransferStatusNotification
|
||||||
type={type}
|
isToast={isToast}
|
||||||
toAmount={transfer.toAmount}
|
slotIcon={icon}
|
||||||
|
slotTitle={title}
|
||||||
|
transfer={transfer}
|
||||||
|
type={transferType}
|
||||||
triggeredAt={transfer.triggeredAt}
|
triggeredAt={transfer.triggeredAt}
|
||||||
status={transfer.status}
|
notification={notification}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
customMenuContent: !finished && (
|
|
||||||
<div>
|
|
||||||
{description}
|
|
||||||
<TransferStatusSteps status={transfer.status} type={type} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
toastSensitivity: 'foreground',
|
toastSensitivity: 'foreground',
|
||||||
|
groupKey: NotificationType.SquidTransfer,
|
||||||
},
|
},
|
||||||
[]
|
[isFinished]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [transferNotifications]);
|
}, [transferNotifications, stringGetter]);
|
||||||
|
},
|
||||||
|
useNotificationAction: () => {
|
||||||
|
return () => {};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] satisfies NotificationTypeConfig[];
|
{
|
||||||
|
type: NotificationType.ReleaseUpdates,
|
||||||
|
useTrigger: ({ trigger }) => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
useEffect(() => {
|
||||||
|
trigger(
|
||||||
|
'rewards-and-full-trading-live',
|
||||||
|
{
|
||||||
|
icon: <AssetIcon symbol="DYDX" />,
|
||||||
|
title: stringGetter({ key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.TITLE' }),
|
||||||
|
body: stringGetter({
|
||||||
|
key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.BODY',
|
||||||
|
params: {
|
||||||
|
DOS_BLOGPOST: (
|
||||||
|
<$Link
|
||||||
|
href="https://www.dydxopsdao.com/blog/deep-dive-full-trading"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{stringGetter({ key: STRING_KEYS.HERE })}
|
||||||
|
</$Link>
|
||||||
|
),
|
||||||
|
TRADING_BLOGPOST: (
|
||||||
|
<$Link
|
||||||
|
href="https://dydx.exchange/blog/v4-full-trading"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{stringGetter({ key: STRING_KEYS.HERE })}
|
||||||
|
</$Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
toastSensitivity: 'foreground',
|
||||||
|
groupKey: NotificationType.ReleaseUpdates,
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}, [stringGetter]);
|
||||||
|
},
|
||||||
|
useNotificationAction: () => {
|
||||||
|
return () => {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
Styled.TransferText = styled.span`
|
const $Icon = styled.img`
|
||||||
display: inline-flex;
|
height: 1.5rem;
|
||||||
align-items: center;
|
width: 1.5rem;
|
||||||
gap: 0.5ch;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ErrorMessage = styled.div`
|
const $Link = styled.a`
|
||||||
max-width: 13rem;
|
--link-color: var(--color-text-2);
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
|
||||||
import {
|
import {
|
||||||
type Notification,
|
type Notification,
|
||||||
type NotificationDisplayData,
|
type NotificationDisplayData,
|
||||||
|
type NotificationPreferences,
|
||||||
type Notifications,
|
type Notifications,
|
||||||
NotificationStatus,
|
NotificationStatus,
|
||||||
|
NotificationType,
|
||||||
} from '@/constants/notifications';
|
} from '@/constants/notifications';
|
||||||
|
|
||||||
import { useLocalStorage } from './useLocalStorage';
|
import { useLocalStorage } from './useLocalStorage';
|
||||||
@@ -13,8 +15,10 @@ import { notificationTypes } from './useNotificationTypes';
|
|||||||
|
|
||||||
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
|
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
|
||||||
|
|
||||||
const NotificationsContext = createContext<ReturnType<typeof useNotificationsContext> | undefined>(
|
type NotificationsContextType = ReturnType<typeof useNotificationsContext>;
|
||||||
undefined
|
|
||||||
|
const NotificationsContext = createContext<NotificationsContextType>(
|
||||||
|
{} as NotificationsContextType
|
||||||
);
|
);
|
||||||
|
|
||||||
NotificationsContext.displayName = 'Notifications';
|
NotificationsContext.displayName = 'Notifications';
|
||||||
@@ -27,6 +31,7 @@ export const useNotifications = () => useContext(NotificationsContext)!;
|
|||||||
|
|
||||||
const useNotificationsContext = () => {
|
const useNotificationsContext = () => {
|
||||||
// Local storage
|
// Local storage
|
||||||
|
// const [notifications, setNotifications] = useState<Notifications>({});
|
||||||
const [notifications, setNotifications] = useLocalStorage<Notifications>({
|
const [notifications, setNotifications] = useLocalStorage<Notifications>({
|
||||||
key: LocalStorageKey.Notifications,
|
key: LocalStorageKey.Notifications,
|
||||||
defaultValue: {},
|
defaultValue: {},
|
||||||
@@ -37,12 +42,25 @@ const useNotificationsContext = () => {
|
|||||||
defaultValue: Date.now(),
|
defaultValue: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [notificationPreferences, setNotificationPreferences] =
|
||||||
|
useLocalStorage<NotificationPreferences>({
|
||||||
|
key: LocalStorageKey.NotificationPreferences,
|
||||||
|
defaultValue: {
|
||||||
|
[NotificationType.AbacusGenerated]: true,
|
||||||
|
[NotificationType.SquidTransfer]: true,
|
||||||
|
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNotificationsLastUpdated(Date.now());
|
setNotificationsLastUpdated(Date.now());
|
||||||
}, [notifications]);
|
}, [notifications]);
|
||||||
|
|
||||||
const getKey = <T extends string | number>(notification: Pick<Notification<T>, 'type' | 'id'>) =>
|
const getKey = useCallback(
|
||||||
`${notification.type}/${notification.id}`;
|
<T extends string | number>(notification: Pick<Notification<T>, 'type' | 'id'>) =>
|
||||||
|
`${notification.type}/${notification.id}`,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// Display data
|
// Display data
|
||||||
const [notificationsDisplayData, setNotificationsDisplayData] = useState(
|
const [notificationsDisplayData, setNotificationsDisplayData] = useState(
|
||||||
@@ -54,29 +72,47 @@ const useNotificationsContext = () => {
|
|||||||
[notificationsDisplayData]
|
[notificationsDisplayData]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check for version changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
notificationPreferences.version !==
|
||||||
|
LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences]
|
||||||
|
) {
|
||||||
|
setNotificationPreferences({
|
||||||
|
[NotificationType.AbacusGenerated]: true,
|
||||||
|
[NotificationType.SquidTransfer]: true,
|
||||||
|
[NotificationType.ReleaseUpdates]: true,
|
||||||
|
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Status changes
|
// Status changes
|
||||||
const updateStatus = useCallback(
|
const updateStatus = useCallback(
|
||||||
(notification: Notification, status: NotificationStatus) => {
|
(notification: Notification, status: NotificationStatus) => {
|
||||||
notification.status = status;
|
notification.status = status;
|
||||||
notification.timestamps[notification.status] = Date.now();
|
notification.timestamps[notification.status] = Date.now();
|
||||||
setNotifications({ ...notifications });
|
setNotifications({ ...notifications, [getKey(notification)]: notification });
|
||||||
},
|
},
|
||||||
[notifications]
|
[notifications, getKey]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { markUnseen, markSeen, markCleared } = useMemo(
|
const { markUnseen, markSeen, markCleared } = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
markUnseen: (notification: Notification) => {
|
markUnseen: (notification: Notification) => {
|
||||||
if (notification.status < NotificationStatus.Unseen)
|
if (notification.status < NotificationStatus.Unseen) {
|
||||||
updateStatus(notification, NotificationStatus.Unseen);
|
updateStatus(notification, NotificationStatus.Unseen);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
markSeen: (notification: Notification) => {
|
markSeen: (notification: Notification) => {
|
||||||
if (notification.status < NotificationStatus.Seen)
|
if (notification.status < NotificationStatus.Seen) {
|
||||||
updateStatus(notification, NotificationStatus.Seen);
|
updateStatus(notification, NotificationStatus.Seen);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
markCleared: (notification: Notification) => {
|
markCleared: (notification: Notification) => {
|
||||||
if (notification.status < NotificationStatus.Cleared)
|
if (notification.status < NotificationStatus.Cleared) {
|
||||||
updateStatus(notification, NotificationStatus.Cleared);
|
updateStatus(notification, NotificationStatus.Cleared);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[updateStatus]
|
[updateStatus]
|
||||||
@@ -97,33 +133,37 @@ const useNotificationsContext = () => {
|
|||||||
|
|
||||||
const notification = notifications[key];
|
const notification = notifications[key];
|
||||||
|
|
||||||
// New unique key - create new notification
|
// Filter out notifications that are not enabled
|
||||||
if (!notification) {
|
if (notificationPreferences[type] !== false) {
|
||||||
const notification = (notifications[key] = {
|
// New unique key - create new notification
|
||||||
id,
|
if (!notification) {
|
||||||
type,
|
const notification = (notifications[key] = {
|
||||||
timestamps: {},
|
id,
|
||||||
updateKey,
|
type,
|
||||||
} as Notification);
|
timestamps: {},
|
||||||
|
updateKey,
|
||||||
|
} as Notification);
|
||||||
|
updateStatus(
|
||||||
|
notification,
|
||||||
|
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
|
||||||
|
);
|
||||||
|
} else if (JSON.stringify(updateKey) !== JSON.stringify(notification.updateKey)) {
|
||||||
|
// updateKey changed - update existing notification
|
||||||
|
|
||||||
updateStatus(
|
const notification = notifications[key];
|
||||||
notification,
|
|
||||||
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateKey changed - update existing notification
|
notification.updateKey = updateKey;
|
||||||
else if (JSON.stringify(updateKey) !== JSON.stringify(notification.updateKey)) {
|
updateStatus(notification, NotificationStatus.Updated);
|
||||||
const notification = notifications[key];
|
}
|
||||||
|
} else {
|
||||||
notification.updateKey = updateKey;
|
// Notification is disabled - remove it
|
||||||
updateStatus(notification, NotificationStatus.Updated);
|
delete notifications[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationsDisplayData[key] = displayData;
|
notificationsDisplayData[key] = displayData;
|
||||||
setNotificationsDisplayData({ ...notificationsDisplayData });
|
setNotificationsDisplayData({ ...notificationsDisplayData });
|
||||||
},
|
},
|
||||||
[notifications, updateStatus]
|
[notifications, updateStatus, notificationPreferences[type]]
|
||||||
),
|
),
|
||||||
|
|
||||||
lastUpdated: notificationsLastUpdated,
|
lastUpdated: notificationsLastUpdated,
|
||||||
@@ -136,10 +176,8 @@ const useNotificationsContext = () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const onNotificationAction = useCallback(
|
const onNotificationAction = async (notification: Notification) =>
|
||||||
async (notification: Notification) => await actions[notification.type]?.(notification.id),
|
await actions[notification.type]?.(notification.id);
|
||||||
[actions]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Push notifications
|
// Push notifications
|
||||||
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
|
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
|
||||||
@@ -186,7 +224,7 @@ const useNotificationsContext = () => {
|
|||||||
renotify: true,
|
renotify: true,
|
||||||
tag: getKey(notification),
|
tag: getKey(notification),
|
||||||
data: notification,
|
data: notification,
|
||||||
body: displayData.description,
|
description: displayData.body,
|
||||||
icon: iconUrl ?? '/favicon.svg',
|
icon: iconUrl ?? '/favicon.svg',
|
||||||
badge: iconUrl ?? '/favicon.svg',
|
badge: iconUrl ?? '/favicon.svg',
|
||||||
image: iconUrl ?? '/favicon.svg',
|
image: iconUrl ?? '/favicon.svg',
|
||||||
@@ -208,7 +246,7 @@ const useNotificationsContext = () => {
|
|||||||
|
|
||||||
setPushNotificationsLastUpdated(Date.now());
|
setPushNotificationsLastUpdated(Date.now());
|
||||||
})();
|
})();
|
||||||
}, [hasEnabledPush, notifications, onNotificationAction, markSeen]);
|
}, [hasEnabledPush, notifications, markSeen]);
|
||||||
|
|
||||||
// Menu state
|
// Menu state
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
@@ -239,5 +277,13 @@ const useNotificationsContext = () => {
|
|||||||
// Menu state
|
// Menu state
|
||||||
isMenuOpen,
|
isMenuOpen,
|
||||||
setIsMenuOpen,
|
setIsMenuOpen,
|
||||||
|
|
||||||
|
// Notification Preferences
|
||||||
|
notificationPreferences,
|
||||||
|
setNotificationPreferences,
|
||||||
|
getNotificationPreferenceForType: useCallback(
|
||||||
|
(type: NotificationType) => notificationPreferences[type],
|
||||||
|
[notificationPreferences]
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
|
||||||
|
|
||||||
|
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
|
||||||
|
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,50 +0,0 @@
|
|||||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { Squid } from '@0xsquid/sdk';
|
|
||||||
|
|
||||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
|
||||||
|
|
||||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
|
||||||
|
|
||||||
export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
|
||||||
|
|
||||||
export const STATUS_ERROR_GRACE_PERIOD = 120_000;
|
|
||||||
|
|
||||||
const useSquidContext = () => {
|
|
||||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
|
||||||
const [_, setInitialized] = useState(false);
|
|
||||||
|
|
||||||
const initializeClient = async () => {
|
|
||||||
setInitialized(false);
|
|
||||||
if (!squid) return;
|
|
||||||
await squid.init();
|
|
||||||
setInitialized(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const squid = useMemo(
|
|
||||||
() =>
|
|
||||||
new Squid({
|
|
||||||
baseUrl: ENVIRONMENT_CONFIG_MAP[selectedNetwork]?.endpoints['0xsquid'],
|
|
||||||
integratorId: ENVIRONMENT_CONFIG_MAP[selectedNetwork]?.squidIntegratorId,
|
|
||||||
}),
|
|
||||||
[selectedNetwork]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (squid) {
|
|
||||||
initializeClient();
|
|
||||||
}
|
|
||||||
}, [squid]);
|
|
||||||
|
|
||||||
return squid;
|
|
||||||
};
|
|
||||||
|
|
||||||
type SquidContextType = ReturnType<typeof useSquidContext>;
|
|
||||||
const SquidContext = createContext<SquidContextType | undefined>(undefined);
|
|
||||||
SquidContext.displayName = '0xSquid';
|
|
||||||
|
|
||||||
export const SquidProvider = ({ ...props }) => (
|
|
||||||
<SquidContext.Provider value={useSquidContext()} {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useSquid = () => useContext(SquidContext);
|
|
||||||
@@ -31,6 +31,7 @@ import { log } from '@/lib/telemetry';
|
|||||||
import { useAccounts } from './useAccounts';
|
import { useAccounts } from './useAccounts';
|
||||||
import { useTokenConfigs } from './useTokenConfigs';
|
import { useTokenConfigs } from './useTokenConfigs';
|
||||||
import { useDydxClient } from './useDydxClient';
|
import { useDydxClient } from './useDydxClient';
|
||||||
|
import { hashFromTx } from '@/lib/hashfromTx';
|
||||||
|
|
||||||
type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
|
type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
|
||||||
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
|
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
|
||||||
@@ -293,12 +294,29 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
);
|
);
|
||||||
|
|
||||||
const sendSquidWithdraw = useCallback(
|
const sendSquidWithdraw = useCallback(
|
||||||
async (amount: number, payload: string) => {
|
async (amount: number, payload: string, isCctp?: boolean) => {
|
||||||
|
|
||||||
|
const cctpWithdraw = () => {
|
||||||
|
return new Promise<string>((resolve, reject) =>
|
||||||
|
abacusStateManager.cctpWithdraw((success, error, data) => {
|
||||||
|
const parsedData = JSON.parse(data);
|
||||||
|
if (success && parsedData?.code == 0) {
|
||||||
|
resolve(parsedData?.transactionHash);
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isCctp) {
|
||||||
|
return await cctpWithdraw();
|
||||||
|
}
|
||||||
|
|
||||||
if (!subaccountClient) {
|
if (!subaccountClient) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const tx = await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
|
||||||
return await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
|
return hashFromTx(tx?.hash);
|
||||||
},
|
},
|
||||||
[subaccountClient, sendSquidWithdrawFromSubaccount]
|
[subaccountClient, sendSquidWithdrawFromSubaccount]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import { createContext, useContext, useEffect, useState } from 'react';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import queryString from 'query-string';
|
|
||||||
|
|
||||||
class TestFlags {
|
|
||||||
public queryParams: queryString.ParsedQuery<string>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.queryParams = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
setQueryParams(flags: queryString.ParsedQuery<string>) {
|
|
||||||
this.queryParams = flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayInitializingMarkets() {
|
|
||||||
return !!this.queryParams.displayInitializingMarkets;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const testFlags = new TestFlags();
|
|
||||||
|
|
||||||
const useTestFlagsContext = () => {
|
|
||||||
const [queryParams, setQueryParams] = useState<queryString.ParsedQuery<string>>({});
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const parsedQueryParams = queryString.parse(location.search.substring(-1));
|
|
||||||
testFlags.setQueryParams(parsedQueryParams);
|
|
||||||
setQueryParams(parsedQueryParams);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...queryParams,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type TestFlagsContextType = ReturnType<typeof useTestFlagsContext>;
|
|
||||||
const TestFlagsContext = createContext<TestFlagsContextType>({} as TestFlagsContextType);
|
|
||||||
TestFlagsContext.displayName = 'TestFlags';
|
|
||||||
|
|
||||||
export const TestFlagsProvider = ({ ...props }) => (
|
|
||||||
<TestFlagsContext.Provider value={useTestFlagsContext()} {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useTestFlags = () => useContext(TestFlagsContext);
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { getTradeFormInputs } from '@/state/inputsSelectors';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { TradeInputField } from '@/constants/abacus';
|
||||||
|
|
||||||
|
import abacusStateManager from '@/lib/abacus';
|
||||||
|
|
||||||
|
export const useTradeFormInputs = () => {
|
||||||
|
const tradeFormInputValues = useSelector(getTradeFormInputs, shallowEqual);
|
||||||
|
const { limitPriceInput, triggerPriceInput, trailingPercentInput } = tradeFormInputValues;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
abacusStateManager.setTradeValue({
|
||||||
|
value: triggerPriceInput,
|
||||||
|
field: TradeInputField.triggerPrice,
|
||||||
|
});
|
||||||
|
}, [triggerPriceInput]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
abacusStateManager.setTradeValue({ value: limitPriceInput, field: TradeInputField.limitPrice });
|
||||||
|
}, [limitPriceInput]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
abacusStateManager.setTradeValue({
|
||||||
|
value: trailingPercentInput,
|
||||||
|
field: TradeInputField.trailingPercent,
|
||||||
|
});
|
||||||
|
}, [trailingPercentInput]);
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState, useMemo } from 'react';
|
import { useCallback, useEffect, useState, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { usePrivy, useLogout } from '@privy-io/react-auth';
|
||||||
|
|
||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
@@ -104,8 +105,10 @@ export const useWalletConnection = () => {
|
|||||||
[walletConnectConfig, walletType, walletConnectionType]
|
[walletConnectConfig, walletType, walletConnectionType]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
|
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector });
|
||||||
const { suggestAndConnect: connectGraz } = useConnectGraz();
|
const { suggestAndConnect: connectGraz } = useConnectGraz();
|
||||||
|
const { login, ready, authenticated } = usePrivy();
|
||||||
|
const { logout } = useLogout();
|
||||||
|
|
||||||
const connectWallet = useCallback(
|
const connectWallet = useCallback(
|
||||||
async ({ walletType }: { walletType: WalletType }) => {
|
async ({ walletType }: { walletType: WalletType }) => {
|
||||||
@@ -114,6 +117,11 @@ export const useWalletConnection = () => {
|
|||||||
try {
|
try {
|
||||||
if (!walletConnection) {
|
if (!walletConnection) {
|
||||||
throw new Error('Onboarding: No wallet connection found.');
|
throw new Error('Onboarding: No wallet connection found.');
|
||||||
|
} else if (walletConnection.type === WalletConnectionType.Privy) {
|
||||||
|
|
||||||
|
if (!isConnectedWagmi && !authenticated && ready) {
|
||||||
|
login();
|
||||||
|
}
|
||||||
} else if (walletConnection.type === WalletConnectionType.CosmosSigner) {
|
} else if (walletConnection.type === WalletConnectionType.CosmosSigner) {
|
||||||
const cosmosWalletType = {
|
const cosmosWalletType = {
|
||||||
[WalletType.Keplr as string]: CosmosWalletType.KEPLR,
|
[WalletType.Keplr as string]: CosmosWalletType.KEPLR,
|
||||||
@@ -156,7 +164,7 @@ export const useWalletConnection = () => {
|
|||||||
walletConnectionType: walletConnection.type,
|
walletConnectionType: walletConnection.type,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi]
|
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi, login, ready, authenticated]
|
||||||
);
|
);
|
||||||
|
|
||||||
const disconnectWallet = useCallback(async () => {
|
const disconnectWallet = useCallback(async () => {
|
||||||
@@ -165,6 +173,7 @@ export const useWalletConnection = () => {
|
|||||||
|
|
||||||
if (isConnectedWagmi) await disconnectWagmi();
|
if (isConnectedWagmi) await disconnectWagmi();
|
||||||
if (isConnectedGraz) await disconnectGraz();
|
if (isConnectedGraz) await disconnectGraz();
|
||||||
|
if (authenticated) await logout();
|
||||||
}, [isConnectedGraz, isConnectedWagmi]);
|
}, [isConnectedGraz, isConnectedWagmi]);
|
||||||
|
|
||||||
// Wallet selection
|
// Wallet selection
|
||||||
|
|||||||
@@ -1,3 +1 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<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>
|
||||||
<path d="M14 6L7.83335 10.5L2 6" stroke="currentColor" stroke-width="2.25" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 840 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="27" height="28" viewBox="0 0 27 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Frame">
|
||||||
|
<path id="Vector" d="M9.5 15L12.5 18L17.5 11M25.5 14C25.5 15.5759 25.1896 17.1363 24.5866 18.5922C23.9835 20.0481 23.0996 21.371 21.9853 22.4853C20.871 23.5996 19.5481 24.4835 18.0922 25.0866C16.6363 25.6896 15.0759 26 13.5 26C11.9241 26 10.3637 25.6896 8.9078 25.0866C7.45189 24.4835 6.12902 23.5996 5.01472 22.4853C3.90042 21.371 3.0165 20.0481 2.41345 18.5922C1.81039 17.1363 1.5 15.5759 1.5 14C1.5 10.8174 2.76428 7.76516 5.01472 5.51472C7.26516 3.26428 10.3174 2 13.5 2C16.6826 2 19.7348 3.26428 21.9853 5.51472C24.2357 7.76516 25.5 10.8174 25.5 14Z" stroke="#3ED9A4" stroke-opacity="0.5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_2" d="M9.5 15L12.5 18L17.5 11" stroke="#3ED9A4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 914 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M2 6.39098L5.68182 10L11 2" stroke="currentColor" stroke-width="3" stroke-linecap="round"
|
<path d="M2 6.39098L5.68182 10L11 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
stroke-linejoin="round" />
|
stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B |
@@ -1,3 +0,0 @@
|
|||||||
<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>
|
|
||||||
|
Before Width: | Height: | Size: 969 B |
@@ -10,6 +10,7 @@ export { default as CautionCircleIcon } from './caution-circle.svg';
|
|||||||
export { default as CautionCircleStrokeIcon } from './caution-circle-stroke.svg';
|
export { default as CautionCircleStrokeIcon } from './caution-circle-stroke.svg';
|
||||||
export { default as ChatIcon } from './chat-bubble.svg';
|
export { default as ChatIcon } from './chat-bubble.svg';
|
||||||
export { default as CheckIcon } from './check.svg';
|
export { default as CheckIcon } from './check.svg';
|
||||||
|
export { default as CheckCircleIcon } from './check-circle.svg';
|
||||||
export { default as ChevronLeftIcon } from './chevron-left.svg';
|
export { default as ChevronLeftIcon } from './chevron-left.svg';
|
||||||
export { default as ChevronRightIcon } from './chevron-right.svg';
|
export { default as ChevronRightIcon } from './chevron-right.svg';
|
||||||
export { default as ClockIcon } from './clock.svg';
|
export { default as ClockIcon } from './clock.svg';
|
||||||
@@ -18,7 +19,6 @@ export { default as CoinMarketCapIcon } from './logos/coinmarketcap.svg';
|
|||||||
export { default as CoinsIcon } from './coins.svg';
|
export { default as CoinsIcon } from './coins.svg';
|
||||||
export { default as CommentIcon } from './comment.svg';
|
export { default as CommentIcon } from './comment.svg';
|
||||||
export { default as CopyIcon } from './copy.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 DepositIcon } from './deposit.svg';
|
||||||
export { default as DepthChartIcon } from './depth-chart.svg';
|
export { default as DepthChartIcon } from './depth-chart.svg';
|
||||||
export { default as DiscordIcon } from './discord.svg';
|
export { default as DiscordIcon } from './discord.svg';
|
||||||
@@ -31,6 +31,7 @@ export { default as GiftboxIcon } from './giftbox.svg';
|
|||||||
export { default as HelpCircleIcon } from './help-circle.svg';
|
export { default as HelpCircleIcon } from './help-circle.svg';
|
||||||
export { default as HideIcon } from './hide.svg';
|
export { default as HideIcon } from './hide.svg';
|
||||||
export { default as HistoryIcon } from './history.svg';
|
export { default as HistoryIcon } from './history.svg';
|
||||||
|
export { default as LeaderboardIcon } from './leaderboard.svg';
|
||||||
export { default as LinkOutIcon } from './link-out.svg';
|
export { default as LinkOutIcon } from './link-out.svg';
|
||||||
export { default as LockIcon } from './lock.svg';
|
export { default as LockIcon } from './lock.svg';
|
||||||
export { default as LogoShortIcon } from './logo-short';
|
export { default as LogoShortIcon } from './logo-short';
|
||||||
@@ -42,12 +43,14 @@ export { default as OrderbookIcon } from './orderbook.svg';
|
|||||||
export { default as OverviewIcon } from './overview.svg';
|
export { default as OverviewIcon } from './overview.svg';
|
||||||
export { default as PencilIcon } from './pencil.svg';
|
export { default as PencilIcon } from './pencil.svg';
|
||||||
export { default as PlayIcon } from './play.svg';
|
export { default as PlayIcon } from './play.svg';
|
||||||
|
export { default as PlusIcon } from './plus.svg';
|
||||||
export { default as PortfolioIcon } from './portfolio.svg';
|
export { default as PortfolioIcon } from './portfolio.svg';
|
||||||
export { default as PositionsIcon } from './positions.svg';
|
export { default as PositionsIcon } from './positions.svg';
|
||||||
export { default as PriceChartIcon } from './price-chart.svg';
|
export { default as PriceChartIcon } from './price-chart.svg';
|
||||||
export { default as PrivacyIcon } from './privacy.svg';
|
export { default as PrivacyIcon } from './privacy.svg';
|
||||||
export { default as ProfileIcon } from './profile.svg';
|
export { default as ProfileIcon } from './profile.svg';
|
||||||
export { default as QrIcon } from './qr.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 SearchIcon } from './search.svg';
|
||||||
export { default as SendIcon } from './send.svg';
|
export { default as SendIcon } from './send.svg';
|
||||||
export { default as ShareIcon } from './share.svg';
|
export { default as ShareIcon } from './share.svg';
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="13" height="14" viewBox="0 0 13 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.0996094 2.60156C0.0996094 2.07113 0.310323 1.56242 0.685396 1.18735C1.06047 0.812276 1.56918 0.601563 2.09961 0.601562H10.8996C11.43 0.601563 11.9387 0.812276 12.3138 1.18735C12.6889 1.56242 12.8996 2.07113 12.8996 2.60156C12.8996 3.132 12.6889 3.6407 12.3138 4.01578C11.9387 4.39085 11.43 4.60156 10.8996 4.60156H2.09961C1.56918 4.60156 1.06047 4.39085 0.685396 4.01578C0.310323 3.6407 0.0996094 3.132 0.0996094 2.60156ZM0.699609 6.26796C0.540479 6.26796 0.387867 6.33118 0.275345 6.4437C0.162823 6.55622 0.0996094 6.70883 0.0996094 6.86796C0.0996094 7.02709 0.162823 7.1797 0.275345 7.29223C0.387867 7.40475 0.540479 7.46796 0.699609 7.46796H12.2996C12.4587 7.46796 12.6114 7.40475 12.7239 7.29223C12.8364 7.1797 12.8996 7.02709 12.8996 6.86796C12.8996 6.70883 12.8364 6.55622 12.7239 6.4437C12.6114 6.33118 12.4587 6.26796 12.2996 6.26796H0.699609ZM0.699609 9.13196C0.540479 9.13196 0.387867 9.19518 0.275345 9.3077C0.162823 9.42022 0.0996094 9.57283 0.0996094 9.73196C0.0996094 9.89109 0.162823 10.0437 0.275345 10.1562C0.387867 10.2687 0.540479 10.332 0.699609 10.332H12.2996C12.4587 10.332 12.6114 10.2687 12.7239 10.1562C12.8364 10.0437 12.8996 9.89109 12.8996 9.73196C12.8996 9.57283 12.8364 9.42022 12.7239 9.3077C12.6114 9.19518 12.4587 9.13196 12.2996 9.13196H0.699609ZM0.699609 12.0016C0.540479 12.0016 0.387867 12.0648 0.275345 12.1773C0.162823 12.2898 0.0996094 12.4424 0.0996094 12.6016C0.0996094 12.7607 0.162823 12.9133 0.275345 13.0258C0.387867 13.1383 0.540479 13.2016 0.699609 13.2016H12.2996C12.4587 13.2016 12.6114 13.1383 12.7239 13.0258C12.8364 12.9133 12.8996 12.7607 12.8996 12.6016C12.8996 12.4424 12.8364 12.2898 12.7239 12.1773C12.6114 12.0648 12.4587 12.0016 12.2996 12.0016H0.699609Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m6.75.75c0-.198912-.07902-.389678-.21967-.53033-.14065-.1406525-.33142-.21967-.53033-.21967s-.38968.0790175-.53033.21967c-.14065.140652-.21967.331418-.21967.53033v4.5h-4.5c-.198912 0-.389678.07902-.53033.21967-.1406525.14065-.21967.33142-.21967.53033s.0790175.38968.21967.53033c.140652.14065.331418.21967.53033.21967h4.5v4.5c0 .1989.07902.3897.21967.5303.14065.1407.33142.2197.53033.2197s.38968-.079.53033-.2197c.14065-.1406.21967-.3314.21967-.5303v-4.5h4.5c.1989 0 .3897-.07902.5303-.21967.1407-.14065.2197-.33142.2197-.53033s-.079-.38968-.2197-.53033c-.1406-.14065-.3314-.21967-.5303-.21967h-4.5z" fill="currentColor"/></svg>
|
||||||
|
After Width: | Height: | Size: 731 B |
|
After Width: | Height: | Size: 481 KiB |
@@ -1,5 +1,13 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g fill="#FFFFFF">
|
<g id="Wallet" clip-path="url(#clip0_14122_34311)">
|
||||||
<path d="M461.2 128H80c-8.84 0-16-7.16-16-16s7.16-16 16-16h384c8.84 0 16-7.16 16-16 0-26.51-21.49-48-48-48H64C28.65 32 0 60.65 0 96v320c0 35.35 28.65 64 64 64h397.2c28.02 0 50.8-21.53 50.8-48V176c0-26.47-22.78-48-50.8-48zM416 336c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32z"/>
|
<g id="Group 151">
|
||||||
</g>
|
<path id="Exclude" fill-rule="evenodd" clip-rule="evenodd" d="M11.4979 10.9976C11.1764 11.3193 10.7404 11.5 10.2857 11.5L1.71429 11.5C1.25963 11.5 0.823594 11.3193 0.502102 10.9976C0.180612 10.6758 -4.76837e-07 10.2395 -4.76837e-07 9.78455L-4.76837e-07 2.06502C-4.76837e-07 2.51999 0.180612 2.95632 0.502102 3.27803C0.823594 3.59974 1.25963 3.78047 1.71429 3.78047L10.2857 3.78047C10.7404 3.78047 11.1764 3.96121 11.4979 4.28292C11.8194 4.60462 12 5.04096 12 5.49592V9.78455C12 10.2395 11.8194 10.6758 11.4979 10.9976ZM9.64294 8.7126C10.2347 8.7126 10.7144 8.23258 10.7144 7.64044C10.7144 7.0483 10.2347 6.56828 9.64294 6.56828C9.05121 6.56828 8.57151 7.0483 8.57151 7.64044C8.57151 8.23258 9.05121 8.7126 9.64294 8.7126Z" fill="#C8C7D8"/>
|
||||||
|
<path id="Vector" d="M8.57031 2.92186V2.06413C8.57031 1.60917 8.3897 1.17284 8.06821 0.851128C7.74672 0.529419 7.31068 0.348684 6.85603 0.348684L2.14174 0.348684C1.80075 0.348684 1.47372 0.484235 1.2326 0.725517C0.991486 0.966799 0.856028 1.29405 0.856028 1.63527C0.856028 1.9765 0.991486 2.30374 1.2326 2.54503C1.47372 2.78631 1.80075 2.92186 2.14174 2.92186L8.57031 2.92186Z" fill="#C8C7D8"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_14122_34311">
|
||||||
|
<rect width="12" height="12" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 1.4 KiB |
@@ -11,17 +11,19 @@ import { DepositDialog } from '@/views/dialogs/DepositDialog';
|
|||||||
import { DisconnectDialog } from '@/views/dialogs/DisconnectDialog';
|
import { DisconnectDialog } from '@/views/dialogs/DisconnectDialog';
|
||||||
import { ExchangeOfflineDialog } from '@/views/dialogs/ExchangeOfflineDialog';
|
import { ExchangeOfflineDialog } from '@/views/dialogs/ExchangeOfflineDialog';
|
||||||
import { HelpDialog } from '@/views/dialogs/HelpDialog';
|
import { HelpDialog } from '@/views/dialogs/HelpDialog';
|
||||||
|
import { ExternalLinkDialog } from '@/views/dialogs/ExternalLinkDialog';
|
||||||
import { ExternalNavKeplrDialog } from '@/views/dialogs/ExternalNavKeplrDialog';
|
import { ExternalNavKeplrDialog } from '@/views/dialogs/ExternalNavKeplrDialog';
|
||||||
import { MnemonicExportDialog } from '@/views/dialogs/MnemonicExportDialog';
|
import { MnemonicExportDialog } from '@/views/dialogs/MnemonicExportDialog';
|
||||||
import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog';
|
import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog';
|
||||||
import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog';
|
import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog';
|
||||||
|
import { PreferencesDialog } from '@/views/dialogs/PreferencesDialog';
|
||||||
import { RateLimitDialog } from '@/views/dialogs/RateLimitDialog';
|
import { RateLimitDialog } from '@/views/dialogs/RateLimitDialog';
|
||||||
import { ReceiveDialog } from '@/views/dialogs/ReceiveDialog';
|
|
||||||
import { RestrictedGeoDialog } from '@/views/dialogs/RestrictedGeoDialog';
|
import { RestrictedGeoDialog } from '@/views/dialogs/RestrictedGeoDialog';
|
||||||
import { TradeDialog } from '@/views/dialogs/TradeDialog';
|
import { TradeDialog } from '@/views/dialogs/TradeDialog';
|
||||||
import { TransferDialog } from '@/views/dialogs/TransferDialog';
|
import { TransferDialog } from '@/views/dialogs/TransferDialog';
|
||||||
import { RestrictedWalletDialog } from '@/views/dialogs/RestrictedWalletDialog';
|
import { RestrictedWalletDialog } from '@/views/dialogs/RestrictedWalletDialog';
|
||||||
import { WithdrawDialog } from '@/views/dialogs/WithdrawDialog';
|
import { WithdrawDialog } from '@/views/dialogs/WithdrawDialog';
|
||||||
|
import { ManageFundsDialog } from '@/views/dialogs/ManageFundsDialog';
|
||||||
|
|
||||||
import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog';
|
import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog';
|
||||||
import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDialog';
|
import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDialog';
|
||||||
@@ -52,16 +54,18 @@ export const DialogManager = () => {
|
|||||||
[DialogTypes.FillDetails]: <FillDetailsDialog {...modalProps} />,
|
[DialogTypes.FillDetails]: <FillDetailsDialog {...modalProps} />,
|
||||||
[DialogTypes.Help]: <HelpDialog {...modalProps} />,
|
[DialogTypes.Help]: <HelpDialog {...modalProps} />,
|
||||||
[DialogTypes.ExternalNavKeplr]: <ExternalNavKeplrDialog {...modalProps} />,
|
[DialogTypes.ExternalNavKeplr]: <ExternalNavKeplrDialog {...modalProps} />,
|
||||||
|
[DialogTypes.ExternalLink]: <ExternalLinkDialog {...modalProps} />,
|
||||||
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
|
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
|
||||||
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
|
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
|
||||||
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
|
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
|
||||||
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
|
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
|
||||||
|
[DialogTypes.Preferences]: <PreferencesDialog {...modalProps} />,
|
||||||
[DialogTypes.RateLimit]: <RateLimitDialog {...modalProps} />,
|
[DialogTypes.RateLimit]: <RateLimitDialog {...modalProps} />,
|
||||||
[DialogTypes.Receive]: <ReceiveDialog {...modalProps} />,
|
|
||||||
[DialogTypes.RestrictedGeo]: <RestrictedGeoDialog {...modalProps} />,
|
[DialogTypes.RestrictedGeo]: <RestrictedGeoDialog {...modalProps} />,
|
||||||
[DialogTypes.RestrictedWallet]: <RestrictedWalletDialog {...modalProps} />,
|
[DialogTypes.RestrictedWallet]: <RestrictedWalletDialog {...modalProps} />,
|
||||||
[DialogTypes.Trade]: <TradeDialog {...modalProps} />,
|
[DialogTypes.Trade]: <TradeDialog {...modalProps} />,
|
||||||
[DialogTypes.Transfer]: <TransferDialog {...modalProps} />,
|
[DialogTypes.Transfer]: <TransferDialog {...modalProps} />,
|
||||||
[DialogTypes.Withdraw]: <WithdrawDialog {...modalProps} />,
|
[DialogTypes.Withdraw]: <WithdrawDialog {...modalProps} />,
|
||||||
|
[DialogTypes.ManageFunds]: <ManageFundsDialog {...modalProps} />,
|
||||||
}[type];
|
}[type];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||||
|
|
||||||
import { AbacusApiStatus } from '@/constants/abacus';
|
import { AbacusApiStatus } from '@/constants/abacus';
|
||||||
import { ButtonSize } from '@/constants/buttons';
|
import { ButtonSize, ButtonType } from '@/constants/buttons';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { ENVIRONMENT_CONFIG_MAP, isDev } from '@/constants/networks';
|
import { ENVIRONMENT_CONFIG_MAP, isDev } from '@/constants/networks';
|
||||||
|
|
||||||
@@ -55,10 +55,12 @@ export const FooterDesktop = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Styled.FooterButton
|
<Styled.FooterButton
|
||||||
|
type={statusPage ? ButtonType.Link : ButtonType.Button}
|
||||||
slotLeft={<Styled.StatusDot exchangeStatus={exchangeStatus} />}
|
slotLeft={<Styled.StatusDot exchangeStatus={exchangeStatus} />}
|
||||||
slotRight={statusPage && <LinkOutIcon />}
|
slotRight={statusPage && <LinkOutIcon />}
|
||||||
size={ButtonSize.XSmall}
|
size={ButtonSize.XSmall}
|
||||||
state={{ isDisabled: !statusPage }}
|
state={{ isDisabled: !statusPage }}
|
||||||
|
href={statusPage}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Styled.FooterButton>
|
</Styled.FooterButton>
|
||||||
@@ -132,7 +134,7 @@ Styled.StatusDot = styled.div<{ exchangeStatus: ExchangeStatus }>`
|
|||||||
Styled.FooterButton = styled(Button)`
|
Styled.FooterButton = styled(Button)`
|
||||||
--button-height: 1.5rem;
|
--button-height: 1.5rem;
|
||||||
--button-radius: 0.25rem;
|
--button-radius: 0.25rem;
|
||||||
--button-backgroundColor: var(--color-layer-2);
|
--button-backgroundColor: transparent;
|
||||||
--button-border: none;
|
--button-border: none;
|
||||||
--button-textColor: var(--color-text-0);
|
--button-textColor: var(--color-text-0);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
@@ -11,8 +10,8 @@ import { useShouldShowFooter, useStringGetter } from '@/hooks';
|
|||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { NavigationMenu } from '@/components/NavigationMenu';
|
import { NavigationMenu } from '@/components/NavigationMenu';
|
||||||
import { Icon } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
import { BellIcon, MarketsIcon, PlayIcon, PortfolioIcon, ProfileIcon, TradeIcon } from '@/icons';
|
import { BellIcon, MarketsIcon, PortfolioIcon, ProfileIcon } from '@/icons';
|
||||||
import { IconButton } from '@/components/IconButton';
|
import { IconButton } from '@/components/IconButton';
|
||||||
|
|
||||||
import { calculateCanAccountTrade } from '@/state/accountCalculators';
|
import { calculateCanAccountTrade } from '@/state/accountCalculators';
|
||||||
@@ -42,11 +41,9 @@ export const FooterMobile = () => {
|
|||||||
value: 'trade',
|
value: 'trade',
|
||||||
label: stringGetter({ key: STRING_KEYS.TRADE }),
|
label: stringGetter({ key: STRING_KEYS.TRADE }),
|
||||||
slotBefore: (
|
slotBefore: (
|
||||||
<Styled.IconButton
|
<Styled.StartIcon>
|
||||||
iconComponent={TradeIcon}
|
<Icon iconName={IconName.Trade} />
|
||||||
size={ButtonSize.Large}
|
</Styled.StartIcon>
|
||||||
action={ButtonAction.Primary}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
href: `${AppRoute.Trade}/${marketId ?? DEFAULT_MARKETID}`,
|
href: `${AppRoute.Trade}/${marketId ?? DEFAULT_MARKETID}`,
|
||||||
}
|
}
|
||||||
@@ -54,11 +51,9 @@ export const FooterMobile = () => {
|
|||||||
value: 'onboarding',
|
value: 'onboarding',
|
||||||
label: stringGetter({ key: STRING_KEYS.ONBOARDING }),
|
label: stringGetter({ key: STRING_KEYS.ONBOARDING }),
|
||||||
slotBefore: (
|
slotBefore: (
|
||||||
<Styled.IconButton
|
<Styled.StartIcon>
|
||||||
iconComponent={PlayIcon}
|
<Icon iconName={IconName.Play} />
|
||||||
size={ButtonSize.Large}
|
</Styled.StartIcon>
|
||||||
action={ButtonAction.Primary}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
onClick: () => dispatch(openDialog({ type: DialogTypes.Onboarding })),
|
onClick: () => dispatch(openDialog({ type: DialogTypes.Onboarding })),
|
||||||
},
|
},
|
||||||
@@ -168,3 +163,24 @@ Styled.IconButton = styled(IconButton)`
|
|||||||
Styled.Icon = styled(Icon)`
|
Styled.Icon = styled(Icon)`
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.StartIcon = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: -0.25rem;
|
||||||
|
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 3.5rem;
|
||||||
|
|
||||||
|
color: var(--color-text-2);
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
border: solid var(--border-width) var(--color-border-white);
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { ButtonShape } from '@/constants/buttons';
|
import { ButtonShape } from '@/constants/buttons';
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
@@ -8,6 +8,10 @@ import { STRING_KEYS } from '@/constants/localization';
|
|||||||
import { AppRoute } from '@/constants/routes';
|
import { AppRoute } from '@/constants/routes';
|
||||||
import { LogoShortIcon, BellStrokeIcon } from '@/icons';
|
import { LogoShortIcon, BellStrokeIcon } from '@/icons';
|
||||||
|
|
||||||
|
import { headerMixins } from '@/styles/headerMixins';
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
import breakpoints from '@/styles/breakpoints';
|
||||||
|
|
||||||
import { useTokenConfigs, useStringGetter, useURLConfigs } from '@/hooks';
|
import { useTokenConfigs, useStringGetter, useURLConfigs } from '@/hooks';
|
||||||
|
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
@@ -21,10 +25,7 @@ import { NotificationsMenu } from '@/views/menus/NotificationsMenu';
|
|||||||
import { LanguageSelector } from '@/views/menus/LanguageSelector';
|
import { LanguageSelector } from '@/views/menus/LanguageSelector';
|
||||||
|
|
||||||
import { openDialog } from '@/state/dialogs';
|
import { openDialog } from '@/state/dialogs';
|
||||||
|
import { getHasSeenLaunchIncentives } from '@/state/configsSelectors';
|
||||||
import { headerMixins } from '@/styles/headerMixins';
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
|
||||||
import breakpoints from '@/styles/breakpoints';
|
|
||||||
|
|
||||||
export const HeaderDesktop = () => {
|
export const HeaderDesktop = () => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
@@ -32,6 +33,8 @@ export const HeaderDesktop = () => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { chainTokenLabel } = useTokenConfigs();
|
const { chainTokenLabel } = useTokenConfigs();
|
||||||
|
|
||||||
|
const hasSeenLaunchIncentives = useSelector(getHasSeenLaunchIncentives);
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{
|
{
|
||||||
group: 'navigation',
|
group: 'navigation',
|
||||||
@@ -55,6 +58,7 @@ export const HeaderDesktop = () => {
|
|||||||
value: chainTokenLabel,
|
value: chainTokenLabel,
|
||||||
label: chainTokenLabel,
|
label: chainTokenLabel,
|
||||||
href: `/${chainTokenLabel}`,
|
href: `/${chainTokenLabel}`,
|
||||||
|
slotAfter: !hasSeenLaunchIncentives && <Styled.UnreadIndicator />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'MORE',
|
value: 'MORE',
|
||||||
@@ -238,3 +242,10 @@ Styled.IconButton = styled(IconButton)<{ size?: string }>`
|
|||||||
--button-icon-size: 1rem;
|
--button-icon-size: 1rem;
|
||||||
--button-padding: 0 0.5em;
|
--button-padding: 0 0.5em;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.UnreadIndicator = styled.div`
|
||||||
|
width: 0.4375rem;
|
||||||
|
height: 0.4375rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { NotificationStatus } from '@/constants/notifications';
|
|
||||||
import { ButtonSize } from '@/constants/buttons';
|
|
||||||
|
|
||||||
import { useNotifications } from '@/hooks/useNotifications';
|
|
||||||
import { useBreakpoints } from '@/hooks/useBreakpoints';
|
|
||||||
|
|
||||||
import { ToastArea } from '@/components/ToastArea';
|
|
||||||
import { Toast } from '@/components/Toast';
|
|
||||||
import { Button } from '@/components/Button';
|
|
||||||
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
type StyleProps = {
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NotificationsToastArea = ({ className }: StyleProps) => {
|
|
||||||
const {
|
|
||||||
notifications,
|
|
||||||
getKey,
|
|
||||||
getDisplayData,
|
|
||||||
markUnseen,
|
|
||||||
markSeen,
|
|
||||||
isMenuOpen,
|
|
||||||
onNotificationAction,
|
|
||||||
} = useNotifications();
|
|
||||||
|
|
||||||
const { isMobile } = useBreakpoints();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledToastArea swipeDirection={isMobile ? 'up' : 'right'} className={className}>
|
|
||||||
{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 }) => displayData)
|
|
||||||
.map(({ notification, key, displayData }) => (
|
|
||||||
<Toast
|
|
||||||
key={key}
|
|
||||||
isOpen={notification.status < NotificationStatus.Unseen}
|
|
||||||
slotIcon={displayData.icon}
|
|
||||||
slotTitle={displayData.title}
|
|
||||||
slotDescription={displayData.description}
|
|
||||||
slotCustomContent={displayData.customContent}
|
|
||||||
slotAction={
|
|
||||||
<Button size={ButtonSize.Small} onClick={() => onNotificationAction(notification)}>
|
|
||||||
{displayData.actionDescription}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
actionDescription={displayData.actionDescription}
|
|
||||||
actionAltText={displayData.actionAltText}
|
|
||||||
duration={isMenuOpen ? Infinity : displayData.toastDuration}
|
|
||||||
sensitivity={displayData.toastSensitivity}
|
|
||||||
setIsOpen={(isOpen, isClosedFromTimeout) => {
|
|
||||||
if (!isOpen)
|
|
||||||
if (isClosedFromTimeout)
|
|
||||||
// Toast timer expired without user interaction
|
|
||||||
markUnseen(notification);
|
|
||||||
// Toast interacted with or dismissed
|
|
||||||
else markSeen(notification);
|
|
||||||
}}
|
|
||||||
lastUpdated={notification.timestamps[notification.status]}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</StyledToastArea>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledToastArea = styled(ToastArea)`
|
|
||||||
position: absolute;
|
|
||||||
width: min(16rem, 100%);
|
|
||||||
inset: 0 0 0 auto;
|
|
||||||
|
|
||||||
padding: 0.75rem 0.75rem 0.75rem 0;
|
|
||||||
|
|
||||||
mask-image: linear-gradient(to left, transparent, white 0.5rem);
|
|
||||||
`;
|
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type Notification,
|
||||||
|
type NotificationDisplayData,
|
||||||
|
NotificationStatus,
|
||||||
|
} from '@/constants/notifications';
|
||||||
|
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||||
|
import { useNotifications } from '@/hooks/useNotifications';
|
||||||
|
import { useBreakpoints } from '@/hooks/useBreakpoints';
|
||||||
|
import { ChevronLeftIcon } from '@/icons';
|
||||||
|
import { breakpoints } from '@/styles';
|
||||||
|
|
||||||
|
import { Button } from '@/components/Button';
|
||||||
|
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 [shouldStackNotifications, setshouldStackNotifications] = useState(true);
|
||||||
|
|
||||||
|
const { markUnseen, markSeen, onNotificationAction } = useNotifications();
|
||||||
|
const { isMobile } = useBreakpoints();
|
||||||
|
const hasMultipleToasts = notifications.length > 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledToastArea
|
||||||
|
swipeDirection={isMobile ? 'up' : 'right'}
|
||||||
|
className={className}
|
||||||
|
size={notifications.length}
|
||||||
|
>
|
||||||
|
{hasMultipleToasts && (
|
||||||
|
<StyledToggleButton
|
||||||
|
shape={ButtonShape.Pill}
|
||||||
|
size={ButtonSize.XSmall}
|
||||||
|
isPressed={shouldStackNotifications}
|
||||||
|
onPressedChange={() => setshouldStackNotifications(!shouldStackNotifications)}
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon />
|
||||||
|
</StyledToggleButton>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{notifications.map(({ notification, key, displayData }, idx) => (
|
||||||
|
<StyledToast
|
||||||
|
key={key}
|
||||||
|
isStacked={idx > 0 && shouldStackNotifications}
|
||||||
|
isOpen={notification.status < NotificationStatus.Unseen}
|
||||||
|
layer={idx}
|
||||||
|
notification={notification}
|
||||||
|
slotIcon={displayData.icon}
|
||||||
|
slotTitle={displayData.title}
|
||||||
|
slotTitleLeft={displayData.slotTitleLeft}
|
||||||
|
slotTitleRight={displayData.slotTitleRight}
|
||||||
|
slotDescription={displayData.body}
|
||||||
|
slotCustomContent={displayData.renderCustomBody?.({ isToast: true, notification })}
|
||||||
|
onClick={() => onNotificationAction(notification)}
|
||||||
|
slotAction={
|
||||||
|
<Button size={ButtonSize.Small} onClick={() => onNotificationAction(notification)}>
|
||||||
|
{displayData.actionDescription}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
actionDescription={displayData.actionDescription}
|
||||||
|
actionAltText={displayData.actionAltText}
|
||||||
|
duration={displayData.toastDuration ?? Infinity}
|
||||||
|
sensitivity={displayData.toastSensitivity}
|
||||||
|
setIsOpen={(isOpen: boolean, isClosedFromTimeout?: boolean) => {
|
||||||
|
if (!isOpen)
|
||||||
|
if (isClosedFromTimeout)
|
||||||
|
// Toast timer expired without user interaction
|
||||||
|
markUnseen(notification);
|
||||||
|
// Toast interacted with or dismissed
|
||||||
|
else markSeen(notification);
|
||||||
|
}}
|
||||||
|
lastUpdated={notification.timestamps[notification.status]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledToastArea>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledToastArea = styled(ToastArea)<{ size: number }>`
|
||||||
|
position: relative;
|
||||||
|
${({ size }) =>
|
||||||
|
size &&
|
||||||
|
css`
|
||||||
|
padding-bottom: calc(${size - 1} * 2px);
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
|
||||||
|
// Stacked toast
|
||||||
|
${({ isStacked, layer }) =>
|
||||||
|
isStacked
|
||||||
|
? css`
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(${layer} * 2px);
|
||||||
|
right: 0;
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledToggleButton = styled(ToggleButton)<{ isPressed: boolean }>`
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: auto;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: -0.5rem;
|
||||||
|
left: calc(50% - 0.75rem);
|
||||||
|
--button-width: 2rem;
|
||||||
|
--button-height: 1rem;
|
||||||
|
|
||||||
|
${StyledToastArea}:hover & {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
width: 4px;
|
||||||
|
margin-top: 1px;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
|
||||||
|
${({ isPressed }) =>
|
||||||
|
!isPressed &&
|
||||||
|
css`
|
||||||
|
transform: rotate(90deg);
|
||||||
|
`}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media ${breakpoints.mobile} {
|
||||||
|
display: flex;
|
||||||
|
left: calc(50% - 2rem);
|
||||||
|
--button-width: 4rem;
|
||||||
|
--button-height: 2rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import Abacus, { type Nullable } from '@dydxprotocol/v4-abacus';
|
import Abacus, { type Nullable } from '@dydxprotocol/v4-abacus';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
import type { IndexedTx } from '@cosmjs/stargate';
|
import type { IndexedTx } from '@cosmjs/stargate';
|
||||||
|
import { GAS_MULTIPLIER, encodeJson } from '@dydxprotocol/v4-client-js';
|
||||||
|
import { EncodeObject } from '@cosmjs/proto-signing';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CompositeClient,
|
CompositeClient,
|
||||||
@@ -8,6 +10,7 @@ import {
|
|||||||
type LocalWallet,
|
type LocalWallet,
|
||||||
Network,
|
Network,
|
||||||
NetworkOptimizer,
|
NetworkOptimizer,
|
||||||
|
NobleClient,
|
||||||
SubaccountClient,
|
SubaccountClient,
|
||||||
ValidatorConfig,
|
ValidatorConfig,
|
||||||
OrderType,
|
OrderType,
|
||||||
@@ -39,11 +42,14 @@ import { openDialog } from '@/state/dialogs';
|
|||||||
import { StatefulOrderError } from '../errors';
|
import { StatefulOrderError } from '../errors';
|
||||||
import { bytesToBigInt } from '../numbers';
|
import { bytesToBigInt } from '../numbers';
|
||||||
import { log } from '../telemetry';
|
import { log } from '../telemetry';
|
||||||
|
import { hashFromTx } from '../hashfromTx';
|
||||||
|
|
||||||
class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||||
private compositeClient: CompositeClient | undefined;
|
private compositeClient: CompositeClient | undefined;
|
||||||
|
private nobleClient: NobleClient | undefined;
|
||||||
private store: RootStore | undefined;
|
private store: RootStore | undefined;
|
||||||
private localWallet: LocalWallet | undefined;
|
private localWallet: LocalWallet | undefined;
|
||||||
|
private nobleWallet: LocalWallet | undefined;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.compositeClient = undefined;
|
this.compositeClient = undefined;
|
||||||
@@ -58,6 +64,17 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
this.localWallet = localWallet;
|
this.localWallet = localWallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setNobleWallet(nobleWallet: LocalWallet) {
|
||||||
|
try {
|
||||||
|
this.nobleWallet = nobleWallet;
|
||||||
|
if (this.nobleClient) {
|
||||||
|
this.nobleClient.connect(nobleWallet);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('DydxChainTransactions/setNobleWallet', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async connectNetwork(
|
async connectNetwork(
|
||||||
paramsInJson: Nullable<string>,
|
paramsInJson: Nullable<string>,
|
||||||
callback: (p0: Nullable<string>) => void
|
callback: (p0: Nullable<string>) => void
|
||||||
@@ -69,6 +86,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
websocketUrl,
|
websocketUrl,
|
||||||
validatorUrl,
|
validatorUrl,
|
||||||
chainId,
|
chainId,
|
||||||
|
nobleValidatorUrl,
|
||||||
USDC_DENOM,
|
USDC_DENOM,
|
||||||
USDC_DECIMALS,
|
USDC_DECIMALS,
|
||||||
USDC_GAS_DENOM,
|
USDC_GAS_DENOM,
|
||||||
@@ -100,6 +118,15 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
|
|
||||||
this.compositeClient = compositeClient;
|
this.compositeClient = compositeClient;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (nobleValidatorUrl) {
|
||||||
|
this.nobleClient = new NobleClient(nobleValidatorUrl);
|
||||||
|
if (this.nobleWallet) await this.nobleClient.connect(this.nobleWallet);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('DydxChainTransactions/connectNetwork/NobleClient', e);
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch custom event to notify other parts of the app that the network has been connected
|
// Dispatch custom event to notify other parts of the app that the network has been connected
|
||||||
const customEvent = new CustomEvent('abacus:connectNetwork', {
|
const customEvent = new CustomEvent('abacus:connectNetwork', {
|
||||||
detail: parsedParams,
|
detail: parsedParams,
|
||||||
@@ -185,8 +212,8 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
timeInForce as OrderTimeInForce,
|
timeInForce as OrderTimeInForce,
|
||||||
goodTilTimeInSeconds ?? 0,
|
goodTilTimeInSeconds ?? 0,
|
||||||
execution as OrderExecution,
|
execution as OrderExecution,
|
||||||
postOnly,
|
postOnly ?? undefined,
|
||||||
reduceOnly,
|
reduceOnly ?? undefined,
|
||||||
triggerPrice ?? undefined
|
triggerPrice ?? undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -195,19 +222,19 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
throw new StatefulOrderError('Stateful order has failed to commit.', tx);
|
throw new StatefulOrderError('Stateful order has failed to commit.', tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedTx = this.parseToPrimitives(tx);
|
const encodedTx = encodeJson(tx);
|
||||||
const hash = parsedTx?.hash;
|
const parsedTx = JSON.parse(encodedTx);
|
||||||
|
const hash = parsedTx.hash.toUpperCase();
|
||||||
|
|
||||||
if (isTestnet) {
|
if (isTestnet) {
|
||||||
console.log(
|
console.log(
|
||||||
`${
|
`${ENVIRONMENT_CONFIG_MAP[
|
||||||
ENVIRONMENT_CONFIG_MAP[this.compositeClient.network.getString() as DydxNetwork]?.links
|
this.compositeClient.network.getString() as DydxNetwork
|
||||||
?.mintscanBase
|
]?.links?.mintscan?.replace('{tx_hash}', hash.toString())}`
|
||||||
}/txs/${hash}`
|
|
||||||
);
|
);
|
||||||
} else console.log(`txHash: ${hash}`);
|
} else console.log(`txHash: ${hash}`);
|
||||||
|
|
||||||
return JSON.stringify(parsedTx);
|
return encodedTx;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.name !== 'BroadcastError') {
|
if (error?.name !== 'BroadcastError') {
|
||||||
log('DydxChainTransactions/placeOrderTransaction', error);
|
log('DydxChainTransactions/placeOrderTransaction', error);
|
||||||
@@ -237,9 +264,14 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
goodTilBlockTime || undefined
|
goodTilBlockTime || undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
const parsedTx = this.parseToPrimitives(tx);
|
const encodedTx = encodeJson(tx);
|
||||||
|
|
||||||
return JSON.stringify(parsedTx);
|
if (import.meta.env.MODE === 'development') {
|
||||||
|
const parsedTx = JSON.parse(encodedTx);
|
||||||
|
console.log(parsedTx, parsedTx.hash.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodedTx;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('DydxChainTransactions/cancelOrderTransaction', error);
|
log('DydxChainTransactions/cancelOrderTransaction', error);
|
||||||
|
|
||||||
@@ -319,6 +351,122 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendNobleIBC(params: { msgTypeUrl: string; msg: any }): Promise<string> {
|
||||||
|
if (!this.nobleClient?.isConnected) {
|
||||||
|
throw new Error('Missing nobleClient or localWallet');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ibcMsg = {
|
||||||
|
typeUrl: params.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer',
|
||||||
|
value: params.msg,
|
||||||
|
};
|
||||||
|
const fee = await this.nobleClient.simulateTransaction([ibcMsg]);
|
||||||
|
|
||||||
|
// take out fee from amount before sweeping
|
||||||
|
const amount =
|
||||||
|
parseInt(ibcMsg.value.token.amount, 10) -
|
||||||
|
Math.floor(parseInt(fee.amount[0].amount, 10) * GAS_MULTIPLIER);
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
throw new Error('noble balance does not cover fees');
|
||||||
|
}
|
||||||
|
|
||||||
|
ibcMsg.value.token.amount = amount.toString();
|
||||||
|
const tx = await this.nobleClient.send([ibcMsg]);
|
||||||
|
|
||||||
|
const parsedTx = this.parseToPrimitives(tx);
|
||||||
|
|
||||||
|
return JSON.stringify(parsedTx);
|
||||||
|
} catch (error) {
|
||||||
|
log('DydxChainTransactions/sendNobleIBC', error);
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async withdrawToNobleIBC(params: {
|
||||||
|
subaccountNumber: number;
|
||||||
|
amount: string;
|
||||||
|
ibcPayload: string;
|
||||||
|
}): Promise<string> {
|
||||||
|
if (!this.compositeClient || !this.localWallet) {
|
||||||
|
throw new Error('Missing compositeClient or localWallet');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { subaccountNumber, amount, ibcPayload } = params ?? {};
|
||||||
|
const parsedIbcPayload: {
|
||||||
|
msgTypeUrl: string;
|
||||||
|
msg: any;
|
||||||
|
} = ibcPayload ? JSON.parse(atob(ibcPayload)) : undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const msg = this.compositeClient.withdrawFromSubaccountMessage(
|
||||||
|
new SubaccountClient(this.localWallet, subaccountNumber),
|
||||||
|
parseFloat(amount).toFixed(this.compositeClient.validatorClient.config.denoms.USDC_DECIMALS)
|
||||||
|
);
|
||||||
|
const ibcMsg: EncodeObject = {
|
||||||
|
typeUrl: parsedIbcPayload.msgTypeUrl,
|
||||||
|
value: parsedIbcPayload.msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tx = await this.compositeClient.send(
|
||||||
|
this.localWallet,
|
||||||
|
() => Promise.resolve([msg, ibcMsg]),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
txHash: hashFromTx(tx?.hash),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
log('DydxChainTransactions/withdrawToNobleIBC', error);
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async cctpWithdraw(params: { typeUrl: string; value: any }): Promise<string> {
|
||||||
|
if (!this.nobleClient?.isConnected) {
|
||||||
|
throw new Error('Missing nobleClient or localWallet');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ibcMsg = {
|
||||||
|
typeUrl: params.typeUrl, // '/circle.cctp.v1.MsgDepositForBurn',
|
||||||
|
value: params.value,
|
||||||
|
};
|
||||||
|
const fee = await this.nobleClient.simulateTransaction([ibcMsg]);
|
||||||
|
|
||||||
|
// take out fee from amount before sweeping
|
||||||
|
const amount =
|
||||||
|
parseInt(ibcMsg.value.amount, 10) -
|
||||||
|
Math.floor(parseInt(fee.amount[0].amount, 10) * GAS_MULTIPLIER);
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
throw new Error('noble balance does not cover fees');
|
||||||
|
}
|
||||||
|
|
||||||
|
ibcMsg.value.amount = amount.toString();
|
||||||
|
|
||||||
|
const tx = await this.nobleClient.send([ibcMsg]);
|
||||||
|
|
||||||
|
const parsedTx = this.parseToPrimitives(tx);
|
||||||
|
|
||||||
|
return JSON.stringify(parsedTx);
|
||||||
|
} catch (error) {
|
||||||
|
log('DydxChainTransactions/cctpWithdraw', error);
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async transaction(
|
async transaction(
|
||||||
type: TransactionTypes,
|
type: TransactionTypes,
|
||||||
paramsInJson: Abacus.Nullable<string>,
|
paramsInJson: Abacus.Nullable<string>,
|
||||||
@@ -348,6 +496,22 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
callback(result);
|
callback(result);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TransactionType.SendNobleIBC: {
|
||||||
|
const result = await this.sendNobleIBC(params);
|
||||||
|
callback(result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TransactionType.WithdrawToNobleIBC: {
|
||||||
|
const result = await this.withdrawToNobleIBC(params);
|
||||||
|
callback(result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TransactionType.CctpWithdraw: {
|
||||||
|
const result = await this.cctpWithdraw(params);
|
||||||
|
callback(result);
|
||||||
|
break;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -435,8 +599,13 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
const parseDelegations = this.parseToPrimitives(delegations);
|
const parseDelegations = this.parseToPrimitives(delegations);
|
||||||
callback(JSON.stringify(parseDelegations));
|
callback(JSON.stringify(parseDelegations));
|
||||||
break;
|
break;
|
||||||
// Do not implement Transfers (yet)
|
case QueryType.GetNobleBalance:
|
||||||
case QueryType.Transfers:
|
if (this.nobleClient?.isConnected) {
|
||||||
|
const nobleBalance = await this.nobleClient.getAccountBalance('uusdc');
|
||||||
|
const parsedNobleBalance = this.parseToPrimitives(nobleBalance);
|
||||||
|
callback(JSON.stringify(parsedNobleBalance));
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,14 @@ import {
|
|||||||
} from '@/constants/abacus';
|
} from '@/constants/abacus';
|
||||||
|
|
||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
import { CURRENT_ABACUS_DEPLOYMENT, type DydxNetwork } from '@/constants/networks';
|
import { CURRENT_ABACUS_DEPLOYMENT, type DydxNetwork, isMainnet } from '@/constants/networks';
|
||||||
|
import { CLEARED_SIZE_INPUTS, CLEARED_TRADE_INPUTS } from '@/constants/trade';
|
||||||
|
|
||||||
import type { RootStore } from '@/state/_store';
|
import type { RootStore } from '@/state/_store';
|
||||||
|
import { setTradeFormInputs } from '@/state/inputs';
|
||||||
|
import { getInputTradeOptions, getTransferInputs } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
import { getInputTradeOptions } from '@/state/inputsSelectors';
|
import { testFlags } from '@/lib/testFlags';
|
||||||
import { getTransferInputs } from '@/state/inputsSelectors';
|
|
||||||
|
|
||||||
import AbacusRest from './rest';
|
import AbacusRest from './rest';
|
||||||
import AbacusAnalytics from './analytics';
|
import AbacusAnalytics from './analytics';
|
||||||
@@ -43,6 +45,7 @@ import AbacusFormatter from './formatter';
|
|||||||
import AbacusThreading from './threading';
|
import AbacusThreading from './threading';
|
||||||
import AbacusFileSystem from './filesystem';
|
import AbacusFileSystem from './filesystem';
|
||||||
import { LocaleSeparators } from '../numbers';
|
import { LocaleSeparators } from '../numbers';
|
||||||
|
|
||||||
class AbacusStateManager {
|
class AbacusStateManager {
|
||||||
private store: RootStore | undefined;
|
private store: RootStore | undefined;
|
||||||
private currentMarket: string | undefined;
|
private currentMarket: string | undefined;
|
||||||
@@ -80,10 +83,13 @@ class AbacusStateManager {
|
|||||||
this.abacusFormatter
|
this.abacusFormatter
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const appConfigs = AbacusAppConfig.Companion.forWeb;
|
||||||
|
appConfigs.squidVersion = AbacusAppConfig.SquidVersion.V2;
|
||||||
|
|
||||||
this.stateManager = new AsyncAbacusStateManager(
|
this.stateManager = new AsyncAbacusStateManager(
|
||||||
'',
|
'',
|
||||||
CURRENT_ABACUS_DEPLOYMENT,
|
CURRENT_ABACUS_DEPLOYMENT,
|
||||||
AbacusAppConfig.Companion.forWeb,
|
appConfigs,
|
||||||
ioImplementations,
|
ioImplementations,
|
||||||
uiImplementations,
|
uiImplementations,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -132,6 +138,8 @@ class AbacusStateManager {
|
|||||||
this.setTradeValue({ value: null, field: TradeInputField.limitPrice });
|
this.setTradeValue({ value: null, field: TradeInputField.limitPrice });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.store?.dispatch(setTradeFormInputs(CLEARED_TRADE_INPUTS));
|
||||||
|
|
||||||
if (shouldResetSize) {
|
if (shouldResetSize) {
|
||||||
this.setTradeValue({ value: null, field: TradeInputField.size });
|
this.setTradeValue({ value: null, field: TradeInputField.size });
|
||||||
this.setTradeValue({ value: null, field: TradeInputField.usdcSize });
|
this.setTradeValue({ value: null, field: TradeInputField.usdcSize });
|
||||||
@@ -139,6 +147,8 @@ class AbacusStateManager {
|
|||||||
if (needsLeverage) {
|
if (needsLeverage) {
|
||||||
this.setTradeValue({ value: null, field: TradeInputField.leverage });
|
this.setTradeValue({ value: null, field: TradeInputField.leverage });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.store?.dispatch(setTradeFormInputs(CLEARED_SIZE_INPUTS));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -175,6 +185,12 @@ class AbacusStateManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setNobleWallet = (nobleWallet?: LocalWallet) => {
|
||||||
|
if (nobleWallet) {
|
||||||
|
this.chainTransactions.setNobleWallet(nobleWallet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTransfersSourceAddress = (evmAddress: string) => {
|
setTransfersSourceAddress = (evmAddress: string) => {
|
||||||
this.stateManager.sourceAddress = evmAddress;
|
this.stateManager.sourceAddress = evmAddress;
|
||||||
};
|
};
|
||||||
@@ -218,18 +234,6 @@ class AbacusStateManager {
|
|||||||
this.abacusFormatter.setLocaleSeparators({ group, decimal });
|
this.abacusFormatter.setLocaleSeparators({ group, decimal });
|
||||||
};
|
};
|
||||||
|
|
||||||
setTransferStatus = ({
|
|
||||||
hash,
|
|
||||||
fromChainId,
|
|
||||||
toChainId,
|
|
||||||
}: {
|
|
||||||
hash: string;
|
|
||||||
fromChainId?: string;
|
|
||||||
toChainId?: string;
|
|
||||||
}) => {
|
|
||||||
this.stateManager.transferStatus(hash, fromChainId, toChainId);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------ Transactions ------ //
|
// ------ Transactions ------ //
|
||||||
|
|
||||||
placeOrder = (
|
placeOrder = (
|
||||||
@@ -257,6 +261,14 @@ class AbacusStateManager {
|
|||||||
) => void
|
) => void
|
||||||
) => this.stateManager.cancelOrder(orderId, callback);
|
) => this.stateManager.cancelOrder(orderId, callback);
|
||||||
|
|
||||||
|
cctpWithdraw = (
|
||||||
|
callback: (
|
||||||
|
success: boolean,
|
||||||
|
parsingError: Nullable<ParsingError>,
|
||||||
|
data: string,
|
||||||
|
) => void
|
||||||
|
): void => this.stateManager.commitCCTPWithdraw(callback);
|
||||||
|
|
||||||
// ------ Utils ------ //
|
// ------ Utils ------ //
|
||||||
getHistoricalPnlPeriod = (): Nullable<HistoricalPnlPeriods> =>
|
getHistoricalPnlPeriod = (): Nullable<HistoricalPnlPeriods> =>
|
||||||
this.stateManager.historicalPnlPeriod;
|
this.stateManager.historicalPnlPeriod;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { setApiState } from '@/state/app';
|
|||||||
import { setAssets } from '@/state/assets';
|
import { setAssets } from '@/state/assets';
|
||||||
import { setConfigs } from '@/state/configs';
|
import { setConfigs } from '@/state/configs';
|
||||||
import { setInputs } from '@/state/inputs';
|
import { setInputs } from '@/state/inputs';
|
||||||
|
import { updateNotifications } from '@/state/notifications';
|
||||||
import { setHistoricalFundings, setLiveTrades, setMarkets, setOrderbook } from '@/state/perpetuals';
|
import { setHistoricalFundings, setLiveTrades, setMarkets, setOrderbook } from '@/state/perpetuals';
|
||||||
import { isTruthy } from '../isTruthy';
|
import { isTruthy } from '../isTruthy';
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notificationsChanged(notifications: kollections.List<AbacusNotification>): void {
|
notificationsChanged(notifications: kollections.List<AbacusNotification>): void {
|
||||||
|
this.store?.dispatch(updateNotifications(notifications.toArray()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@/constants/websocket';
|
} from '@/constants/websocket';
|
||||||
|
|
||||||
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
|
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
|
||||||
import { testFlags } from '@/hooks/useTestFlags';
|
import { testFlags } from '@/lib/testFlags';
|
||||||
|
|
||||||
import { subscriptionsByChannelId } from '@/lib/tradingView/dydxfeed/cache';
|
import { subscriptionsByChannelId } from '@/lib/tradingView/dydxfeed/cache';
|
||||||
import { mapCandle } from '@/lib/tradingView/utils';
|
import { mapCandle } from '@/lib/tradingView/utils';
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const hashFromTx = (
|
||||||
|
txHash: string | Uint8Array
|
||||||
|
): string => `0x${Buffer.from(txHash).toString('hex')}`;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { isMainnet } from '@/constants/networks';
|
||||||
|
import { GetStatus, StatusResponse } from '@0xsquid/sdk';
|
||||||
|
|
||||||
|
export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
||||||
|
|
||||||
|
export const STATUS_ERROR_GRACE_PERIOD = 300_000;
|
||||||
|
|
||||||
|
const getSquidStatusUrl = (isV2: boolean) => {
|
||||||
|
if (isV2) {
|
||||||
|
return isMainnet
|
||||||
|
? 'https://v2.api.squidrouter.com/v2/status'
|
||||||
|
: 'https://testnet.v2.api.squidrouter.com/v2/status';
|
||||||
|
}
|
||||||
|
return isMainnet
|
||||||
|
? 'https://api.squidrouter.com/v1/status'
|
||||||
|
: 'https://testnet.api.squidrouter.com/v1/status';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchSquidStatus = async (
|
||||||
|
params: GetStatus,
|
||||||
|
isV2?: boolean,
|
||||||
|
integratorId?: string
|
||||||
|
): Promise<StatusResponse> => {
|
||||||
|
const parsedParams: { [key: string]: string } = {
|
||||||
|
transactionId: params.transactionId,
|
||||||
|
fromChainId: String(params.fromChainId),
|
||||||
|
toChainId: String(params.toChainId),
|
||||||
|
};
|
||||||
|
if (isV2) parsedParams.bridgeType = 'cctp';
|
||||||
|
const url = `${getSquidStatusUrl(!!isV2)}?${new URLSearchParams(parsedParams).toString()}`;
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
"x-integrator-id": integratorId || 'dYdX-api'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNobleChainId = () => {
|
||||||
|
return isMainnet ? 'noble-1' : 'grand-1';
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
class TestFlags {
|
||||||
|
public queryParams: { [key: string]: string };
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.queryParams = {};
|
||||||
|
const hash = window.location.hash;
|
||||||
|
const queryIndex = hash.indexOf('?');
|
||||||
|
if (queryIndex === -1) return;
|
||||||
|
|
||||||
|
const queryParamsString = hash.substring(queryIndex + 1);
|
||||||
|
const params = new URLSearchParams(queryParamsString);
|
||||||
|
|
||||||
|
for (const [key, value] of params) {
|
||||||
|
this.queryParams[key.toLowerCase()] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayInitializingMarkets() {
|
||||||
|
return !!this.queryParams.displayinitializingmarkets;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showMobileSignInOption() {
|
||||||
|
return !!this.queryParams.mobilesignin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testFlags = new TestFlags();
|
||||||