Compare commits

..
Author SHA1 Message Date
Rui db3e7331b8 Fix path to chainLogo 2023-12-06 13:35:08 -08:00
100 changed files with 2099 additions and 3868 deletions
+5 -6
View File
@@ -39,12 +39,11 @@
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.2.5",
"@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.1.11",
"@dydxprotocol/v4-abacus": "^1.1.4",
"@dydxprotocol/v4-client-js": "^1.0.6",
"@dydxprotocol/v4-localization": "^1.0.17",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.4",
@@ -111,8 +110,8 @@
"reselect": "^4.1.8",
"styled-components": "^5.3.11",
"use-latest": "^1.2.1",
"viem": "^1.20.0",
"wagmi": "^1.4.12"
"viem": "^1.12.2",
"wagmi": "^1.4.5"
},
"devDependencies": {
"@babel/core": "^7.22.5",
+368 -1041
View File
File diff suppressed because it is too large Load Diff
+49
View File
@@ -1,12 +1,61 @@
[
{
"chainId": "42161",
"tokenAddress": "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8",
"name": "Arbitrum"
},
{
"chainId": "43114",
"tokenAddress": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
"name": "Avalanche"
},
{
"chainId": "8453",
"tokenAddress": "0x66627F389ae46D881773B7131139b2411980E09E",
"name": "Base"
},
{
"chainId": "1",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"name": "Ethereum"
},
{
"chainId": "10",
"tokenAddress": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
"name": "OP Mainnet"
},
{
"chainId": "421613",
"tokenAddress": "0xfd064a18f3bf249cf1f87fc203e90d8f650f2d63",
"name": "Arbitrum Goerli"
},
{
"chainId": "43113",
"tokenAddress": "0x5425890298aed601595a70AB815c96711a31Bc65",
"name": "Avalanche Fuji"
},
{
"chainId": "84531",
"tokenAddress": "0xf175520c52418dfe19c8098071a252da48cd1c19",
"name": "Base Goerli"
},
{
"chainId": "5",
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
"name": "Ethereum Goerli"
},
{
"chainId": "grand-1",
"tokenAddress": "uusdc",
"name": "Noble Testnet"
},
{
"chainId": "420",
"tokenAddress": "0xe05606174bac4a6364b31bd0eca4bf4dd368f8c6",
"name": "OP Goerli"
},
{
"chainId": "103",
"name": "Solana Devnet"
}
]
-16
View File
@@ -1,16 +0,0 @@
{
"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"
}
]
}
+52 -52
View File
@@ -39,7 +39,7 @@
"environments": {
"dydxprotocol-dev": {
"name": "v4 Dev",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -71,7 +71,7 @@
"https://validator.v4dev.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4dev.dydx.exchange"
},
"links": {
@@ -89,7 +89,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -113,7 +113,7 @@
},
"dydxprotocol-dev-2": {
"name": "v4 Dev 2",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -145,7 +145,7 @@
"http://54.92.118.111"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -162,7 +162,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -186,7 +186,7 @@
},
"dydxprotocol-dev-4": {
"name": "v4 Dev 4",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -218,7 +218,7 @@
"http://validator.v4dev4.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -235,7 +235,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -259,7 +259,7 @@
},
"dydxprotocol-dev-5": {
"name": "v4 Dev 5",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -291,7 +291,7 @@
"http://18.223.78.50"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -308,7 +308,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -332,7 +332,7 @@
},
"dydxprotocol-staging": {
"name": "v4 Staging",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -365,7 +365,7 @@
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -383,7 +383,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -407,7 +407,7 @@
},
"dydxprotocol-staging-forced-update": {
"name": "v4 Staging Forced Update",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -440,7 +440,7 @@
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -480,7 +480,7 @@
},
"dydxprotocol-staging-west": {
"name": "v4 Staging West",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -513,7 +513,7 @@
"https://validator-uswest1.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -531,7 +531,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -555,7 +555,7 @@
},
"dydxprotocol-testnet": {
"name": "v4 Public Testnet",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -579,8 +579,8 @@
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
@@ -591,7 +591,7 @@
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -610,7 +610,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -634,7 +634,7 @@
},
"dydxprotocol-testnet-dydx": {
"name": "v4 Public Testnet/dYdX",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -666,7 +666,7 @@
"https://validator.v4testnet.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -686,7 +686,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -710,7 +710,7 @@
},
"dydxprotocol-testnet-nodefleet": {
"name": "v4 Public Testnet/nodefleet",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -734,15 +734,15 @@
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-testnet.nodefleet.org"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -762,7 +762,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -786,7 +786,7 @@
},
"dydxprotocol-testnet-kingnodes": {
"name": "v4 Public Testnet/KingNodes",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -810,15 +810,15 @@
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://test-dydx.kingnodes.com"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -838,7 +838,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -862,7 +862,7 @@
},
"dydxprotocol-testnet-liquify": {
"name": "v4 Public Testnet/Liquify",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -886,15 +886,15 @@
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -914,7 +914,7 @@
"governanceLearnMore": "https://help.dydx.exchange",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"accountExportLearnMore": "https://help.dydx.exchange",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
@@ -938,7 +938,7 @@
},
"dydxprotocol-testnet-polkachu": {
"name": "v4 Public Testnet/Polkahcu",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -962,15 +962,15 @@
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-testnet-rpc.polkachu.com/"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -1005,7 +1005,7 @@
},
"dydxprotocol-testnet-bware": {
"name": "v4 Public Testnet/BWare",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -1029,15 +1029,15 @@
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-testnet-full-rpc.public.blastapi.io/"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -1126,7 +1126,7 @@
"blogs": "[HTTP link to blogs, can be null]",
"foundation": "[HTTP link to foundation, can be null]",
"initialMarginFractionLearnMore": "[HTTP link to initial margin fraction learn more, can be null]",
"reduceOnlyLearnMore": "[HTTP link to reduce-only learn more, can be null]",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"documentation": "[HTTP link to documentation, can be null]",
"community": "[HTTP link to community, can be null]",
"help": "[HTTP link to help page, can be null]",
+453 -359
View File
@@ -1,360 +1,454 @@
{
"1INCH-USD": {
"name": "1inch",
"tags": ["Defi"],
"websiteLink": "https://1inch.io/",
"whitepaperLink": "https://github.com/1inch",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/1inch/"
},
"AAVE-USD": {
"name": "Aave",
"tags": ["Defi"],
"websiteLink": "https://aave.com/",
"whitepaperLink": "https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/aave/"
},
"ADA-USD": {
"name": "Cardano",
"tags": ["Layer 1"],
"websiteLink": "https://cardano.org/",
"whitepaperLink": "https://why.cardano.org/en/introduction/motivation/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/cardano/"
},
"ALGO-USD": {
"name": "Algorand",
"tags": ["Layer 1"],
"websiteLink": "https://algorand.com/",
"whitepaperLink": "https://algorand.com/technology/white-papers",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/algorand/"
},
"APE-USD": {
"name": "ApeCoin",
"tags": [],
"websiteLink": "https://apecoin.com/",
"whitepaperLink": "https://apecoin.com/about",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/apecoin-ape/"
},
"APT-USD": {
"name": "Aptos",
"tags": ["Layer 1"],
"websiteLink": "https://aptoslabs.com/",
"whitepaperLink": "https://aptos.dev/aptos-white-paper/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/aptos/"
},
"ARB-USD": {
"name": "Arbitrum",
"tags": [],
"websiteLink": "https://arbitrum.io/",
"whitepaperLink": "https://github.com/OffchainLabs/nitro",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/arbitrum/"
},
"ATOM-USD": {
"name": "Cosmos",
"tags": ["Layer 1"],
"websiteLink": "https://cosmos.network/",
"whitepaperLink": "https://v1.cosmos.network/resources/whitepaper",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/cosmos/"
},
"AVAX-USD": {
"name": "Avalanche",
"tags": ["Layer 1"],
"websiteLink": "https://www.avalabs.org/",
"whitepaperLink": "https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/avalanche/"
},
"BCH-USD": {
"name": "Bitcoin Cash",
"tags": ["Layer 1"],
"websiteLink": "https://bitcoincash.org/",
"whitepaperLink": "https://bitcoincash.org/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin-cash/"
},
"BLUR-USD": {
"name": "Blur",
"tags": [],
"websiteLink": "https://blur.io/",
"whitepaperLink": "https://docs.blur.foundation/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/blur-token/"
},
"CELO-USD": {
"name": "Celo",
"tags": [],
"websiteLink": "https://celo.org",
"whitepaperLink": "https://docs.celo.org",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/celo/"
},
"BTC-USD": {
"name": "Bitcoin",
"tags": ["Layer 1"],
"websiteLink": "https://bitcoin.org/",
"whitepaperLink": "https://bitcoin.org/bitcoin.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin/"
},
"COMP-USD": {
"name": "Compound",
"tags": ["Defi"],
"websiteLink": "https://compound.finance/",
"whitepaperLink": "https://compound.finance/documents/Compound.Whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/compound/"
},
"CRV-USD": {
"name": "Curve",
"tags": ["Governance"],
"websiteLink": "https://curve.fi/",
"whitepaperLink": "https://curve.fi/whitepaper",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/curve-dao-token/"
},
"DOGE-USD": {
"name": "Dogecoin",
"tags": ["Layer 1"],
"websiteLink": "https://dogecoin.com/",
"whitepaperLink": "https://github.com/dogecoin/dogecoin",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/dogecoin/"
},
"DOT-USD": {
"name": "Polkadot",
"tags": ["Layer 1"],
"websiteLink": "https://polkadot.network/",
"whitepaperLink": "https://polkadot.network/PolkaDotPaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/polkadot-new/"
},
"ENJ-USD": {
"name": "Enjin",
"tags": [],
"websiteLink": "https://enjin.io/",
"whitepaperLink": "https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/enjin-coin/"
},
"EOS-USD": {
"name": "EOS",
"tags": ["Layer 1"],
"websiteLink": "https://eos.io/",
"whitepaperLink": "https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/eos/"
},
"ETC-USD": {
"name": "Ethereum Classic",
"tags": ["Layer 1"],
"websiteLink": "https://ethereumclassic.org/",
"whitepaperLink": "https://ethereumclassic.org/why-classic",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum-classic/"
},
"ETH-USD": {
"name": "Ethereum",
"tags": ["Layer 1"],
"websiteLink": "https://ethereum.org/",
"whitepaperLink": "https://ethereum.org/whitepaper/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum/",
"displayStepSize": "0.001",
"displayTickSize": "0.1"
},
"FIL-USD": {
"name": "Filecoin",
"tags": ["Layer 1"],
"websiteLink": "https://filecoin.io/",
"whitepaperLink": "https://filecoin.io/filecoin.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/filecoin/"
},
"HNT-USD": {
"name": "Helium",
"tags": ["Layer 1"],
"websiteLink": "https://www.helium.com",
"whitepaperLink": "http://whitepaper.helium.com",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/helium/"
},
"ICP-USD": {
"name": "Internet Computer",
"tags": ["Layer 1"],
"websiteLink": "https://dfinity.org",
"whitepaperLink": "https://dfinity.org/whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/internet-computer/"
},
"LDO-USD": {
"name": "Lido DAO",
"tags": ["Defi"],
"websiteLink": "https://lido.fi/",
"whitepaperLink": "https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/lido-dao/"
},
"LINK-USD": {
"name": "Chainlink",
"tags": ["Defi"],
"websiteLink": "https://chain.link/",
"whitepaperLink": "https://link.smartcontract.com/whitepaper",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/chainlink/"
},
"LTC-USD": {
"name": "Litecoin",
"tags": ["Layer 1"],
"websiteLink": "https://litecoin.org/",
"whitepaperLink": "https://litecoin.info/index.php/Main_Page",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/litecoin/"
},
"MATIC-USD": {
"name": "Polygon",
"tags": ["Layer 2"],
"websiteLink": "https://polygon.technology/",
"whitepaperLink": "https://polygon.technology/lightpaper-polygon.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/polygon/"
},
"MKR-USD": {
"name": "Maker",
"tags": ["Governance"],
"websiteLink": "https://makerdao.com",
"whitepaperLink": "https://makerdao.com/whitepaper",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/maker/"
},
"NEAR-USD": {
"name": "NEAR Protocol",
"tags": ["Layer 1"],
"websiteLink": "https://near.org",
"whitepaperLink": "https://near.org/papers/the-official-near-white-paper/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/near-protocol/"
},
"OP-USD": {
"name": "Optimism",
"tags": [],
"websiteLink": "https://www.optimism.io/",
"whitepaperLink": "https://github.com/ethereum-optimism",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/optimism-ethereum/"
},
"PEPE-USD": {
"name": "Pepe",
"tags": [],
"websiteLink": "https://www.pepe.vip/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/pepe/"
},
"RUNE-USD": {
"name": "THORChain",
"tags": ["Layer 1"],
"websiteLink": "https://thorchain.org",
"whitepaperLink": "https://whitepaper.io/document/709/thorchain-whitepaper",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/thorchain/"
},
"SEI-USD": {
"name": "Sei",
"tags": ["Layer 1", "Defi"],
"websiteLink": "https://www.sei.io/",
"whitepaperLink": "https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sei/"
},
"SHIB-USD": {
"name": "Shiba Inu",
"tags": [],
"websiteLink": "https://shibatoken.com/",
"whitepaperLink": "https://docs.shibatoken.com/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/shiba-inu/"
},
"SNX-USD": {
"name": "Synthetix",
"tags": ["Defi"],
"websiteLink": "https://synthetix.io/",
"whitepaperLink": "https://docs.synthetix.io/litepaper",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/synthetix-network-token/"
},
"SOL-USD": {
"name": "Solana",
"tags": ["Layer 1"],
"websiteLink": "https://solana.com/",
"whitepaperLink": "https://solana.com/solana-whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/solana/"
},
"SUI-USD": {
"name": "Sui",
"tags": ["Layer 1"],
"websiteLink": "https://sui.io/",
"whitepaperLink": "https://github.com/MystenLabs",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sui/"
},
"SUSHI-USD": {
"name": "Sushi",
"tags": ["Defi"],
"websiteLink": "https://sushi.com/",
"whitepaperLink": "https://docs.sushi.com/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sushiswap/"
},
"TIA-USD": {
"name": "Celestia",
"tags": ["Layer 1"],
"websiteLink": "https://celestia.org/",
"whitepaperLink": "https://arxiv.org/pdf/1905.09274.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/celestia/"
},
"TRX-USD": {
"name": "TRON",
"tags": ["Defi"],
"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/"
}
}
"1INCH-USD":{
"name":"1inch",
"tags":[
"Defi"
],
"websiteLink":"https://1inch.io/",
"whitepaperLink":"https://github.com/1inch",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/1inch/"
},
"AAVE-USD":{
"name":"Aave",
"tags":[
"Defi"
],
"websiteLink":"https://aave.com/",
"whitepaperLink":"https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aave/"
},
"ADA-USD":{
"name":"Cardano",
"tags":[
"Layer 1"
],
"websiteLink":"https://cardano.org/",
"whitepaperLink":"https://why.cardano.org/en/introduction/motivation/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cardano/"
},
"ALGO-USD":{
"name":"Algorand",
"tags":[
"Layer 1"
],
"websiteLink":"https://algorand.com/",
"whitepaperLink":"https://algorand.com/technology/white-papers",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/algorand/"
},
"APE-USD":{
"name":"ApeCoin",
"tags":[
],
"websiteLink":"https://apecoin.com/",
"whitepaperLink":"https://apecoin.com/about",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/apecoin-ape/"
},
"APT-USD":{
"name":"Aptos",
"tags":[
"Layer 1"
],
"websiteLink":"https://aptoslabs.com/",
"whitepaperLink":"https://aptos.dev/aptos-white-paper/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aptos/"
},
"ARB-USD":{
"name":"Arbitrum",
"tags":[
],
"websiteLink":"https://arbitrum.io/",
"whitepaperLink":"https://github.com/OffchainLabs/nitro",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/arbitrum/"
},
"ATOM-USD":{
"name":"Cosmos",
"tags":[
"Layer 1"
],
"websiteLink":"https://cosmos.network/",
"whitepaperLink":"https://v1.cosmos.network/resources/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cosmos/"
},
"AVAX-USD":{
"name":"Avalanche",
"tags":[
"Layer 1"
],
"websiteLink":"https://www.avalabs.org/",
"whitepaperLink":"https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/avalanche/"
},
"BCH-USD":{
"name":"Bitcoin Cash",
"tags":[
"Layer 1"
],
"websiteLink":"https://bitcoincash.org/",
"whitepaperLink":"https://bitcoincash.org/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin-cash/"
},
"BLUR-USD":{
"name":"Blur",
"tags":[
],
"websiteLink":"https://blur.io/",
"whitepaperLink":"https://docs.blur.foundation/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/blur-token/"
},
"CELO-USD":{
"name":"Celo",
"tags":[
],
"websiteLink":"https://celo.org",
"whitepaperLink":"https://docs.celo.org",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/celo/"
},
"BTC-USD":{
"name":"Bitcoin",
"tags":[
"Layer 1"
],
"websiteLink":"https://bitcoin.org/",
"whitepaperLink":"https://bitcoin.org/bitcoin.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin/"
},
"COMP-USD":{
"name":"Compound",
"tags":[
"Defi"
],
"websiteLink":"https://compound.finance/",
"whitepaperLink":"https://compound.finance/documents/Compound.Whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/compound/"
},
"CRV-USD":{
"name":"Curve",
"tags":[
"Governance"
],
"websiteLink":"https://curve.fi/",
"whitepaperLink":"https://curve.fi/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/curve-dao-token/"
},
"DOGE-USD":{
"name":"Dogecoin",
"tags":[
"Layer 1"
],
"websiteLink":"https://dogecoin.com/",
"whitepaperLink":"https://github.com/dogecoin/dogecoin",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/dogecoin/"
},
"DOT-USD":{
"name":"Polkadot",
"tags":[
"Layer 1"
],
"websiteLink":"https://polkadot.network/",
"whitepaperLink":"https://polkadot.network/PolkaDotPaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polkadot-new/"
},
"ENJ-USD":{
"name":"Enjin",
"tags":[
],
"websiteLink":"https://enjin.io/",
"whitepaperLink":"https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/enjin-coin/"
},
"EOS-USD":{
"name":"EOS",
"tags":[
"Layer 1"
],
"websiteLink":"https://eos.io/",
"whitepaperLink":"https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/eos/"
},
"ETC-USD":{
"name":"Ethereum Classic",
"tags":[
"Layer 1"
],
"websiteLink":"https://ethereumclassic.org/",
"whitepaperLink":"https://ethereumclassic.org/why-classic",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum-classic/"
},
"ETH-USD":{
"name":"Ethereum",
"tags":[
"Layer 1"
],
"websiteLink":"https://ethereum.org/",
"whitepaperLink":"https://ethereum.org/whitepaper/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum/",
"displayStepSize":"0.001",
"displayTickSize":"0.1"
},
"FIL-USD":{
"name":"Filecoin",
"tags":[
"Layer 1"
],
"websiteLink":"https://filecoin.io/",
"whitepaperLink":"https://filecoin.io/filecoin.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/filecoin/"
},
"HNT-USD":{
"name":"Helium",
"tags":[
"Layer 1"
],
"websiteLink":"https://www.helium.com",
"whitepaperLink":"http://whitepaper.helium.com",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/helium/"
},
"ICP-USD":{
"name":"Internet Computer",
"tags":[
"Layer 1"
],
"websiteLink":"https://dfinity.org",
"whitepaperLink":"https://dfinity.org/whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/internet-computer/"
},
"LDO-USD":{
"name":"Lido DAO",
"tags":[
"Defi"
],
"websiteLink":"https://lido.fi/",
"whitepaperLink":"https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/lido-dao/"
},
"LINK-USD":{
"name":"Chainlink",
"tags":[
"Defi"
],
"websiteLink":"https://chain.link/",
"whitepaperLink":"https://link.smartcontract.com/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/chainlink/"
},
"LTC-USD":{
"name":"Litecoin",
"tags":[
"Layer 1"
],
"websiteLink":"https://litecoin.org/",
"whitepaperLink":"https://litecoin.info/index.php/Main_Page",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/litecoin/"
},
"MATIC-USD":{
"name":"Polygon",
"tags":[
"Layer 2"
],
"websiteLink":"https://polygon.technology/",
"whitepaperLink":"https://polygon.technology/lightpaper-polygon.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polygon/"
},
"MKR-USD":{
"name":"Maker",
"tags":[
"Governance"
],
"websiteLink":"https://makerdao.com",
"whitepaperLink":"https://makerdao.com/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/maker/"
},
"NEAR-USD":{
"name":"NEAR Protocol",
"tags":[
"Layer 1"
],
"websiteLink":"https://near.org",
"whitepaperLink":"https://near.org/papers/the-official-near-white-paper/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/near-protocol/"
},
"OP-USD":{
"name":"Optimism",
"tags":[
],
"websiteLink":"https://www.optimism.io/",
"whitepaperLink":"https://github.com/ethereum-optimism",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/optimism-ethereum/"
},
"PEPE-USD":{
"name":"Pepe",
"tags":[
],
"websiteLink":"https://www.pepe.vip/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/pepe/"
},
"RUNE-USD":{
"name":"THORChain",
"tags":[
"Layer 1"
],
"websiteLink":"https://thorchain.org",
"whitepaperLink":"https://whitepaper.io/document/709/thorchain-whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/thorchain/"
},
"SEI-USD":{
"name":"Sei",
"tags":[
"Layer 1",
"Defi"
],
"websiteLink":"https://www.sei.io/",
"whitepaperLink":"https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sei/"
},
"SHIB-USD":{
"name":"Shiba Inu",
"tags":[
],
"websiteLink":"https://shibatoken.com/",
"whitepaperLink":"https://docs.shibatoken.com/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/shiba-inu/"
},
"SNX-USD":{
"name":"Synthetix",
"tags":[
"Defi"
],
"websiteLink":"https://synthetix.io/",
"whitepaperLink":"https://docs.synthetix.io/litepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/synthetix-network-token/"
},
"SOL-USD":{
"name":"Solana",
"tags":[
"Layer 1"
],
"websiteLink":"https://solana.com/",
"whitepaperLink":"https://solana.com/solana-whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/solana/"
},
"SUI-USD":{
"name":"Sui",
"tags":[
"Layer 1"
],
"websiteLink":"https://sui.io/",
"whitepaperLink":"https://github.com/MystenLabs",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sui/"
},
"SUSHI-USD":{
"name":"Sushi",
"tags":[
"Defi"
],
"websiteLink":"https://sushi.com/",
"whitepaperLink":"https://docs.sushi.com/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sushiswap/"
},
"TRX-USD":{
"name":"TRON",
"tags":[
"Defi"
],
"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/"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

-230
View File
@@ -1,230 +0,0 @@
<svg width="254" height="77" viewBox="0 0 254 77" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 1258">
<g id="Ellipse 9" opacity="0.4" filter="url(#filter0_f_20300_14972)">
<ellipse cx="86.5425" cy="-3.91219" rx="116.542" ry="20.0878" fill="#7774FF"/>
</g>
<g id="Ellipse 10" opacity="0.3" filter="url(#filter1_f_20300_14972)">
<circle cx="74.5734" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 12" opacity="0.3" filter="url(#filter2_f_20300_14972)">
<circle cx="82.1105" cy="7.64179" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 22" opacity="0.3" filter="url(#filter3_f_20300_14972)">
<circle cx="116.785" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 11" opacity="0.3" filter="url(#filter4_f_20300_14972)">
<circle cx="64.774" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 18" opacity="0.3" filter="url(#filter5_f_20300_14972)">
<circle cx="96.4322" cy="3.87363" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 20" opacity="0.3" filter="url(#filter6_f_20300_14972)">
<circle cx="76.0807" cy="14.9361" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 21" opacity="0.3" filter="url(#filter7_f_20300_14972)">
<circle cx="104.725" cy="4.87754" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 23" opacity="0.3" filter="url(#filter8_f_20300_14972)">
<circle cx="114.021" cy="7.13925" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 24" opacity="0.3" filter="url(#filter9_f_20300_14972)">
<circle cx="109.75" cy="15.4361" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 25" opacity="0.3" filter="url(#filter10_f_20300_14972)">
<circle cx="119.801" cy="12.6705" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 27" opacity="0.3" filter="url(#filter11_f_20300_14972)">
<circle cx="94.0885" cy="11.0182" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 30" opacity="0.3" filter="url(#filter12_f_20300_14972)">
<circle cx="154.475" cy="11.6666" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 29" opacity="0.3" filter="url(#filter13_f_20300_14972)">
<circle cx="146.435" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 15" opacity="0.3" filter="url(#filter14_f_20300_14972)">
<circle cx="24.0103" cy="4.44785" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 33" opacity="0.3" filter="url(#filter15_f_20300_14972)">
<circle cx="-1.05899" cy="13.6783" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 16" opacity="0.3" filter="url(#filter16_f_20300_14972)">
<circle cx="17.441" cy="16.4908" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 35" opacity="0.3" filter="url(#filter17_f_20300_14972)">
<circle cx="36.0553" cy="6.63535" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 32" opacity="0.3" filter="url(#filter18_f_20300_14972)">
<circle cx="10.8707" cy="14.2994" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 13" opacity="0.3" filter="url(#filter19_f_20300_14972)">
<circle cx="55.7274" cy="9.65615" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 19" opacity="0.3" filter="url(#filter20_f_20300_14972)">
<circle cx="87.3886" cy="20.7108" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 26" opacity="0.3" filter="url(#filter21_f_20300_14972)">
<circle cx="123.82" cy="15.1874" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 36" opacity="0.3" filter="url(#filter22_f_20300_14972)">
<circle cx="134.876" cy="14.4335" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 28" opacity="0.3" filter="url(#filter23_f_20300_14972)">
<circle cx="129.6" cy="6.38662" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 14" opacity="0.3" filter="url(#filter24_f_20300_14972)">
<circle cx="41.6571" cy="14.9335" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 31" opacity="0.3" filter="url(#filter25_f_20300_14972)">
<circle cx="4.21767" cy="17.9491" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 34" opacity="0.3" filter="url(#filter26_f_20300_14972)">
<circle cx="12.2177" cy="6.88662" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 17" opacity="0.3" filter="url(#filter27_f_20300_14972)">
<circle cx="50.2001" cy="3.6249" r="0.753803" fill="#9A9AFF"/>
</g>
</g>
<defs>
<filter id="filter0_f_20300_14972" x="-94" y="-88" width="361.085" height="168.176" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="32" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter1_f_20300_14972" x="72.5633" y="2.61993" width="4.01991" height="4.02186" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter2_f_20300_14972" x="80.6029" y="6.13418" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter3_f_20300_14972" x="115.278" y="3.12246" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter4_f_20300_14972" x="63.2664" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter5_f_20300_14972" x="94.9246" y="2.36602" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter6_f_20300_14972" x="74.5731" y="13.4285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter7_f_20300_14972" x="103.218" y="3.36993" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter8_f_20300_14972" x="112.513" y="5.63165" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter9_f_20300_14972" x="108.242" y="13.9285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter10_f_20300_14972" x="118.294" y="11.1629" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter11_f_20300_14972" x="92.5809" y="9.51055" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter12_f_20300_14972" x="153.47" y="10.6615" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter13_f_20300_14972" x="144.928" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter14_f_20300_14972" x="22.5027" y="2.94024" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter15_f_20300_14972" x="-2.56659" y="12.1707" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter16_f_20300_14972" x="15.9334" y="14.9832" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter17_f_20300_14972" x="35.0502" y="5.63028" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter18_f_20300_14972" x="9.86563" y="13.2943" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter19_f_20300_14972" x="53.9686" y="7.89727" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter20_f_20300_14972" x="85.6297" y="18.952" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter21_f_20300_14972" x="122.061" y="13.4285" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter22_f_20300_14972" x="133.117" y="12.6746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter23_f_20300_14972" x="127.842" y="4.62774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter24_f_20300_14972" x="39.8982" y="13.1746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter25_f_20300_14972" x="2.4588" y="16.1902" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter26_f_20300_14972" x="10.4588" y="5.12774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter27_f_20300_14972" x="48.4412" y="1.86602" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

+12 -13
View File
@@ -1,4 +1,4 @@
import { lazy, Suspense } from 'react';
import { Suspense } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { WagmiConfig } from 'wagmi';
@@ -25,6 +25,16 @@ import { SubaccountProvider } from '@/hooks/useSubaccount';
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 { FooterDesktop } from '@/layout/Footer/FooterDesktop';
import { FooterMobile } from '@/layout/Footer/FooterMobile';
@@ -36,22 +46,11 @@ import { config } from '@/lib/wagmi';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { LoadingSpace } from './components/Loading/LoadingSpinner';
import '@/styles/constants.css';
import '@/styles/fonts.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 Content = () => {
@@ -70,7 +69,7 @@ const Content = () => {
{isNotTablet && <HeaderDesktop />}
<Styled.Main>
<Suspense fallback={<LoadingSpace id="main" />}>
<Suspense fallback={null}>
<Routes>
<Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} />
-26
View File
@@ -1,26 +0,0 @@
import type { Story } from '@ladle/react';
import { Accordion as AccordionComponent, AccordionProps } from '@/components/Accordion';
import { StoryWrapper } from '.ladle/components';
export const Accordion: Story<AccordionProps> = (args) => {
return (
<StoryWrapper>
<AccordionComponent {...args} />
</StoryWrapper>
);
};
Accordion.args = {
items: [
{
header: 'Question 1?',
content: 'Answer 1.',
},
{
header: 'Question 2?',
content: 'Answer 2.',
},
],
};
-129
View File
@@ -1,129 +0,0 @@
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);
}
`;
+2 -8
View File
@@ -45,7 +45,6 @@ const assetIcons = {
SOL: '/currencies/sol.png',
SUI: '/currencies/sui.png',
SUSHI: '/currencies/sushi.png',
TIA: '/currencies/tia.png',
TRX: '/currencies/trx.png',
UMA: '/currencies/uma.png',
UNI: '/currencies/uni.png',
@@ -72,13 +71,8 @@ export const AssetIcon = ({
}: {
symbol?: Nullable<string>;
className?: string;
}) => (
<Styled.Img
src={isAssetSymbol(symbol) ? assetIcons[symbol] : '/currencies/unavailable.png'}
className={className}
alt={symbol}
/>
);
}) =>
isAssetSymbol(symbol) ? <Styled.Img src={assetIcons[symbol]} className={className} /> : null;
const Styled: Record<string, AnyStyledComponent> = {};
+5 -7
View File
@@ -31,12 +31,10 @@ type ElementProps<TabItemsValue> = {
};
type StyleProps = {
className?: string;
fullWidthTabs?: boolean;
className?: string;
};
export type CollapsibleTabsProps<TabItemsValue> = ElementProps<TabItemsValue> & StyleProps;
export const CollapsibleTabs = <TabItemsValue extends string>({
defaultValue,
items,
@@ -45,9 +43,8 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
onOpenChange,
fullWidthTabs,
className,
}: CollapsibleTabsProps<TabItemsValue>) => {
}: ElementProps<TabItemsValue> & StyleProps) => {
const [value, setValue] = useState(defaultValue);
const currentItem = items.find((item) => item.value === value);
@@ -89,8 +86,8 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
</Styled.Header>
<Styled.CollapsibleContent>
{items.map(({ asChild, value, content }) => (
<Styled.TabsContent key={value} asChild={asChild} value={value}>
{items.map(({ value, content }) => (
<Styled.TabsContent key={value} value={value}>
{content}
</Styled.TabsContent>
))}
@@ -222,6 +219,7 @@ Styled.Header = styled.header`
Styled.CollapsibleContent = styled(CollapsibleContent)`
${layoutMixins.stack}
${layoutMixins.perspectiveArea}
box-shadow: none;
`;
+1
View File
@@ -11,5 +11,6 @@ export const DetachedScrollableSection = styled.section`
export const AttachedExpandingSection = styled.section`
${layoutMixins.contentSectionAttached}
${layoutMixins.expandingColumnWithHeader}
gap: var(--border-width);
`;
+3 -6
View File
@@ -24,6 +24,7 @@ import {
CoinsIcon,
CommentIcon,
CopyIcon,
CubeIcon,
DepositIcon,
DepthChartIcon,
DiscordIcon,
@@ -54,12 +55,10 @@ import {
OverviewIcon,
PencilIcon,
PlayIcon,
PlusIcon,
PositionsIcon,
PriceChartIcon,
PrivacyIcon,
QrIcon,
RewardStarIcon,
SearchIcon,
SendIcon,
ShareIcon,
@@ -99,6 +98,7 @@ export enum IconName {
Coins = 'Coins',
Comment = 'Comment',
Copy = 'Copy',
Cube = 'Cube',
Deposit = 'Deposit',
DepthChart = 'DepthChart',
Discord = 'Discord',
@@ -130,12 +130,10 @@ export enum IconName {
Overview = 'Overview',
Pencil = 'Pencil',
Play = 'Play',
Plus = 'Plus',
Positions = 'Positions',
PriceChart = 'PriceChart',
Privacy = 'Privacy',
Qr = 'Qr',
RewardStar = 'RewardStar',
Search = 'Search',
Send = 'Send',
Share = 'Share',
@@ -175,6 +173,7 @@ const icons = {
[IconName.Coins]: CoinsIcon,
[IconName.Comment]: CommentIcon,
[IconName.Copy]: CopyIcon,
[IconName.Cube]: CubeIcon,
[IconName.Deposit]: DepositIcon,
[IconName.DepthChart]: DepthChartIcon,
[IconName.Discord]: DiscordIcon,
@@ -205,12 +204,10 @@ const icons = {
[IconName.Overview]: OverviewIcon,
[IconName.Pencil]: PencilIcon,
[IconName.Play]: PlayIcon,
[IconName.Plus]: PlusIcon,
[IconName.Positions]: PositionsIcon,
[IconName.PriceChart]: PriceChartIcon,
[IconName.Privacy]: PrivacyIcon,
[IconName.Qr]: QrIcon,
[IconName.RewardStar]: RewardStarIcon,
[IconName.Search]: SearchIcon,
[IconName.Send]: SendIcon,
[IconName.Share]: ShareIcon,
+1
View File
@@ -198,6 +198,7 @@ Styled.InputContainer = styled.div`
border-radius: inherit;
input {
user-select: all;
flex: 1;
width: 100%;
}
+1 -8
View File
@@ -4,10 +4,9 @@ import { Link } from 'react-router-dom';
import { Icon, IconName } from '@/components/Icon';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
type PanelProps = {
slotHeaderContent?: React.ReactNode;
slotHeaderContent?: string;
slotHeader?: React.ReactNode;
slotRight?: React.ReactNode;
children?: React.ReactNode;
@@ -68,12 +67,6 @@ Styled.Panel = styled.section<{ onClick?: () => void }>`
--panel-content-paddingY: var(--panel-paddingY);
--panel-content-paddingX: var(--panel-paddingX);
@media ${breakpoints.notTablet} {
--panel-paddingX: 1.5rem;
--panel-paddingY: 1.25rem;
--panel-content-paddingY: 1rem;
}
${layoutMixins.row}
background-color: var(--color-layer-3);
+12 -71
View File
@@ -33,20 +33,16 @@ import {
import { useAsyncList } from 'react-stately';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { useBreakpoints } from '@/hooks';
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
import { Checkbox } from '@/components/Checkbox';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { CaretIcon } from '@/icons';
import { STRING_KEYS } from '@/constants/localization';
import { MustBigNumber } from '@/lib/numbers';
import { Icon, IconName } from './Icon';
import { Tag } from './Tag';
import { Button } from './Button';
export { TableCell } from './Table/TableCell';
export { TableColumnHeader } from './Table/TableColumnHeader';
@@ -68,7 +64,7 @@ export type TableItem<TableRowData> = {
onSelect?: (key: TableRowData) => void;
};
type ColumnDef<TableRowData extends object> = {
export type ColumnDef<TableRowData extends object> = {
columnKey: string;
label: React.ReactNode;
tag?: React.ReactNode;
@@ -83,7 +79,7 @@ type ColumnDef<TableRowData extends object> = {
width?: ColumnSize;
};
export type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
label?: string;
columns: ColumnDef<TableRowData>[];
data: TableRowData[];
@@ -95,7 +91,6 @@ export type ElementProps<TableRowData extends object | CustomRowConfig, TableRow
selectionBehavior?: 'replace' | 'toggle';
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
slotEmpty?: React.ReactNode;
initialNumRowsToShow?: number;
// collection: TableCollection<string>;
// children: React.ReactNode;
};
@@ -125,7 +120,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
selectionMode = 'single',
selectionBehavior = 'toggle',
slotEmpty,
initialNumRowsToShow = data.length,
// shouldRowRender,
// collection,
@@ -141,7 +135,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
style,
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
const [numRowsToShow, setNumRowsToShow] = useState(initialNumRowsToShow);
const currentBreakpoints = useBreakpoints();
const shownColumns = columns.filter(
@@ -168,7 +161,10 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
? // String
collator.compare(first as string, second as string)
: // Number
MustBigNumber(first).comparedTo(MustBigNumber(second))) *
Math.sign(
(parseInt(first as string) || (first as number)) -
(parseInt(second as string) || (second as number))
)) *
// Flip the direction if descending order is specified.
(sortDirection === 'descending' ? -1 : 1)
);
@@ -215,12 +211,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
onRowAction &&
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
}
numColumns={shownColumns.length}
onViewMoreClick={
numRowsToShow !== undefined && numRowsToShow < data.length
? () => setNumRowsToShow(data.length)
: undefined
}
// shouldRowRender={shouldRowRender}
hideHeader={hideHeader}
withGradientCardRows={withGradientCardRows}
@@ -245,7 +235,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
)}
</TableHeader>
<TableBody items={list.items.slice(0, numRowsToShow)}>
<TableBody items={list.items}>
{(item) => (
<Row key={getRowKey(item)}>
{(columnKey) => (
@@ -279,8 +269,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
onRowAction?: (key: TableRowKey) => void;
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
children: CollectionChildren<TableRowData>;
numColumns: number;
onViewMoreClick?: () => void;
hideHeader?: boolean;
withGradientCardRows?: boolean;
@@ -290,7 +278,7 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
withScrollSnapColumns?: boolean;
withScrollSnapRows?: boolean;
}) => {
const { selectionMode, selectionBehavior, numColumns, onViewMoreClick } = props;
const { selectionMode, selectionBehavior } = props;
const state = useTableState<TableRowData>({
...props,
@@ -351,7 +339,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
<TableBodyRowGroup
withGradientCardRows={props.withGradientCardRows}
withInnerBorders={props.withInnerBorders}
withOuterBorder={props.withOuterBorder}
>
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
{[...collection.body.childNodes].map((row) =>
@@ -397,9 +384,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
</TableRow>
)
)}
{onViewMoreClick ? (
<ViewMoreRow colSpan={numColumns} onClick={onViewMoreClick} />
) : undefined}
</TableBodyRowGroup>
</Styled.Table>
);
@@ -433,7 +417,6 @@ const TableBodyRowGroup = ({
children,
withGradientCardRows,
withInnerBorders,
withOuterBorder,
}: { children: React.ReactNode } & StyleProps) => {
const { rowGroupProps } = useTableRowGroup();
@@ -442,7 +425,6 @@ const TableBodyRowGroup = ({
{...rowGroupProps}
withGradientCardRows={withGradientCardRows}
withInnerBorders={withInnerBorders}
withOuterBorder={withOuterBorder}
>
{children}
</Styled.Tbody>
@@ -509,23 +491,6 @@ const TableColumnHeader = <TableRowData extends object>({
);
};
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
const stringGetter = useStringGetter();
return (
<Styled.Tr key="viewmore">
<Styled.Td
colSpan={colSpan}
onMouseDown={(e: MouseEvent) => e.preventDefault()}
onPointerDown={(e: MouseEvent) => e.preventDefault()}
>
<Styled.ViewMoreButton slotRight={<CaretIcon />} onClick={onClick}>
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
</Styled.ViewMoreButton>
</Styled.Td>
</Styled.Tr>
);
};
export const TableRow = <TableRowData extends object>({
item,
children,
@@ -697,7 +662,7 @@ Styled.Empty = styled.div<{ withOuterBorder: boolean }>`
justify-items: center;
align-content: center;
padding: 4rem;
padding: 2rem;
gap: 0.75em;
color: var(--color-text-0);
@@ -905,18 +870,6 @@ Styled.Tbody = styled.tbody<StyleProps>`
--stickyArea2-paddingLeft: var(--border-width);
--stickyArea2-paddingRight: var(--border-width);
tr:first-of-type {
box-shadow: 0 calc(var(--border-width)) 0 0 var(--border-color);
}
`}
${({ withOuterBorder }) =>
withOuterBorder &&
css`
tr:last-of-type:not(:only-of-type) {
box-shadow: 0 calc(-1 * var(--border-width)) 0 0 var(--border-color);
}
tr:first-of-type {
box-shadow: none;
}
@@ -971,15 +924,3 @@ Styled.Row = styled.div`
${layoutMixins.inlineRow}
padding: var(--tableCell-padding);
`;
Styled.ViewMoreButton = styled(Button)`
--button-backgroundColor: var(--color-layer-2);
--button-textColor: var(--color-text-1);
width: 100%;
svg {
width: 0.675rem;
margin-left: 0.5ch;
}
`;
+2 -5
View File
@@ -21,7 +21,6 @@ export type TabItem<TabItemsValue> = {
content?: React.ReactNode;
subitems?: TabItem<TabItemsValue>[];
customTrigger?: ReactNode;
asChild?: boolean;
};
type ElementProps<TabItemsValue> = {
@@ -42,8 +41,6 @@ type StyleProps = {
className?: string;
};
export type TabsProps<TabItemsValue> = ElementProps<TabItemsValue> & StyleProps;
export const Tabs = <TabItemsValue extends string>({
defaultValue,
value,
@@ -110,10 +107,9 @@ export const Tabs = <TabItemsValue extends string>({
sharedContent
) : (
<Styled.Stack>
{items.map(({ asChild, value, content, forceMount }) => (
{items.map(({ value, content, forceMount }) => (
<Styled.Content
key={value}
asChild={asChild}
value={value}
forceMount={forceMount}
$hide={forceMount && currentItem?.value !== value}
@@ -246,6 +242,7 @@ Styled.Trigger = styled(Trigger)<{ $withBorders?: boolean }>`
Styled.Stack = styled.div`
${layoutMixins.stack}
${layoutMixins.perspectiveArea}
box-shadow: none;
`;
+2 -1
View File
@@ -27,8 +27,9 @@ export const ToastArea = ({ swipeDirection, children, className }: ToastAreaProp
const $ToastArea = styled.aside`
// Params
--toasts-gap: 0.5rem;
// Rules
${layoutMixins.scrollArea}
z-index: 1;
pointer-events: none;
@@ -1,33 +1,26 @@
import React, { PropsWithChildren, useContext, useState } from 'react';
import React, { useContext, useState } from 'react';
import { Point } from '@visx/point';
import { localPoint } from '@visx/event';
import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart';
import { XYChart, DataContext } from '@visx/xychart';
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
export const XYChartWithPointerEvents = ({
onPointerMove,
onPointerUp,
onPointerPressedChange,
...props
onPointerMove, onPointerUp, onPointerPressedChange, ...props
}: {
onPointerMove?: (point: Point | EventHandlerParams<object>) => void;
onPointerUp?: (point: Point | EventHandlerParams<object>) => void;
onPointerMove?: (point: Point) => void;
onPointerUp?: (point: Point) => void;
onPointerPressedChange?: (isPointerPressed: boolean) => void;
} & PropsWithChildren<Parameters<typeof XYChart>[0]>) => {
} & React.PropsWithChildren<Parameters<typeof XYChart>>) => {
const { xScale, yScale } = useContext(DataContext);
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
const pointerChartPosition =
xScale &&
yScale &&
pointerContainerPosition &&
const pointerChartPosition = xScale && yScale && pointerContainerPosition &&
new Point({
// @ts-expect-error invert supposedly doesn't exist on AxisScale
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2),
// @ts-expect-error invert supposedly doesn't exist on AxisScale
y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
});
@@ -36,13 +29,15 @@ export const XYChartWithPointerEvents = ({
{...props}
onPointerMove={({ event }) => {
setLastPointerMoveEvent(event as React.PointerEvent);
if (pointerChartPosition) onPointerMove?.(pointerChartPosition);
if (pointerChartPosition)
onPointerMove?.(pointerChartPosition);
}}
onPointerOut={() => setLastPointerMoveEvent(undefined)}
onPointerDown={() => onPointerPressedChange?.(true)}
onPointerUp={() => {
onPointerPressedChange?.(false);
if (pointerChartPosition) onPointerUp?.(pointerChartPosition);
if (pointerChartPosition)
onPointerUp?.(pointerChartPosition);
}}
>
{props.children}
+3 -3
View File
@@ -142,7 +142,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
// ------ Transfer Items ------ //
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField;
export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField;
const transferInputFields = [...TransferInputField.values()] as const;
export type TransferInputFields = (typeof transferInputFields)[number];
@@ -151,7 +151,7 @@ const transferTypes = [...TransferType.values()] as const;
export type TransferTypes = (typeof transferTypes)[number];
// ------ Trade Items ------ //
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField;
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
const tradeInputFields = [...TradeInputField.values()] as const;
export type TradeInputFields = (typeof tradeInputFields)[number];
@@ -162,7 +162,7 @@ export type TradeState<T> = {
};
export const ClosePositionInputField =
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField;
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
-1
View File
@@ -96,7 +96,6 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
elapsedTime?: number;
blockHeight?: number;
indexerBlockHeight?: number;
trailingBlocks?: number;
}
: // Navigation
T extends AnalyticsEvent.NavigatePage
-40
View File
@@ -1,40 +0,0 @@
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { FundingDirection } from './markets';
// ------ Depth Chart ------ //
export enum DepthChartSeries {
Asks = 'Asks',
Bids = 'Bids',
MidMarket = 'MidMarket',
}
export type DepthChartDatum = {
size: number;
price: number;
depth: number;
seriesKey: DepthChartSeries;
};
export type DepthChartPoint = {
side: OrderSide;
price: number;
size: number;
};
export const SERIES_KEY_FOR_ORDER_SIDE = {
[OrderSide.BUY]: DepthChartSeries.Bids,
[OrderSide.SELL]: DepthChartSeries.Asks,
};
// ------ Funding Chart ------ //
export enum FundingRateResolution {
OneHour = 'OneHour',
EightHour = 'EightHour',
Annualized = 'Annualized',
}
export type FundingChartDatum = {
time: number;
fundingRate: number;
direction: FundingDirection;
};
+36 -1
View File
@@ -25,5 +25,40 @@ export const DEFAULT_MARKETID = 'ETH-USD';
export enum FundingDirection {
ToShort = 'ToShort',
ToLong = 'ToLong',
None = 'None',
}
export const MARKETS_TO_DISPLAY = [
'BTC-USD',
'ETH-USD',
'LINK-USD',
'SOL-USD',
'MATIC-USD',
'ATOM-USD',
'AVAX-USD',
'APE-USD',
'XRP-USD',
'UNI-USD',
'ADA-USD',
'TRX-USD',
'OP-USD',
'MKR-USD',
'DOGE-USD',
'SHIB-USD',
'COMP-USD',
'LDO-USD',
'NEAR-USD',
'APT-USD',
'SUI-USD',
'DOT-USD',
'ETC-USD',
'ARB-USD',
'CRV-USD',
'BLUR-USD',
'FIL-USD',
'XLM-USD',
'PEPE-USD',
'WLD-USD',
'SEI-USD',
'LTC-USD',
'BCH-USD',
];
-8
View File
@@ -87,8 +87,6 @@ export type NotificationDisplayData = {
slotTitleLeft?: React.ReactNode;
slotTitleRight?: React.ReactNode;
groupKey: string; // Grouping key toast notification stacking
// Overrides title/body for Notification in NotificationMenu
renderCustomBody?: ({
isToast,
@@ -124,15 +122,9 @@ export type NotificationDisplayData = {
toastDuration?: number;
};
export enum TransferNotificationTypes {
Withdrawal = 'withdrawal',
Deposit = 'deposit',
}
// Notification types
export type TransferNotifcation = {
txHash: string;
type?: TransferNotificationTypes;
toChainId?: string;
fromChainId?: string;
toAmount?: number;
-5
View File
@@ -10,7 +10,6 @@ export const LEVERAGE_DECIMALS = 2;
export const TOKEN_DECIMALS = 4;
export const LARGE_TOKEN_DECIMALS = 2;
export const FEE_DECIMALS = 3;
export const FUNDING_DECIMALS = 6;
export const QUANTUM_MULTIPLIER = 1_000_000;
@@ -19,7 +18,3 @@ export enum NumberSign {
Negative = 'Negative',
Neutral = 'Neutral',
}
// Deposit/Withdraw
export const MAX_CCTP_TRANSFER_AMOUNT = 1_000_000;
export const MAX_PRICE_IMPACT = 0.02; // 2%
-4
View File
@@ -1,10 +1,6 @@
import { type TooltipStrings, TOOLTIP_STRING_KEYS } from '@/constants/localization';
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 }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_BODY }),
-4
View File
@@ -5,10 +5,6 @@ export const withdrawTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_TITLE }),
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 }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_BODY }),
+1 -2
View File
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
}, []);
// AnalyticsEvent.NetworkStatus
const { height, indexerHeight, status, trailingBlocks} = useApiState();
const { height, indexerHeight, status } = useApiState();
useEffect(() => {
if (status) {
@@ -114,7 +114,6 @@ export const useAnalytics = () => {
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
blockHeight: height ?? undefined,
indexerBlockHeight: indexerHeight ?? undefined,
trailingBlocks: trailingBlocks ?? undefined
});
}
}, [status]);
+1 -1
View File
@@ -71,7 +71,7 @@ export const getIndexerHeight = (apiState: Nullable<AbacusApiState>) => {
export const useApiState = () => {
const stringGetter = useStringGetter();
const apiState = useSelector(getApiState, shallowEqual);
const { haltedBlock, height, status, trailingBlocks} = apiState ?? {};
const { haltedBlock, height, status, trailingBlocks } = apiState ?? {};
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
const indexerHeight = getIndexerHeight(apiState);
+11 -28
View File
@@ -5,15 +5,16 @@ import { useMatch, useNavigate } from 'react-router-dom';
import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_MARKETID } from '@/constants/markets';
import { AppRoute } from '@/constants/routes';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { getSelectedNetwork } from '@/state/appSelectors';
import { closeDialogInTradeBox } from '@/state/dialogs';
import { setCurrentMarketId } from '@/state/perpetuals';
import { getMarketIds } from '@/state/perpetualsSelectors';
import abacusStateManager from '@/lib/abacus';
import { useLocalStorage } from './useLocalStorage';
import { getMarketIds } from '@/state/perpetualsSelectors';
export const useCurrentMarketId = () => {
const navigate = useNavigate();
const match = useMatch(`/${AppRoute.Trade}/:marketId`);
@@ -32,35 +33,17 @@ export const useCurrentMarketId = () => {
if (marketIds.length === 0) return marketId ?? lastViewedMarket;
if (!marketIds.includes(marketId ?? lastViewedMarket)) return DEFAULT_MARKETID;
return marketId ?? lastViewedMarket;
}, [hasMarketIds, marketId]);
}, [marketIds, marketId]);
useEffect(() => {
// If v4_markets has not been subscribed to yet or marketId is not specified, default to validId
if (!hasMarketIds || !marketId) {
setLastViewedMarket(validId);
dispatch(setCurrentMarketId(validId));
dispatch(closeDialogInTradeBox());
setLastViewedMarket(validId);
dispatch(setCurrentMarketId(validId));
dispatch(closeDialogInTradeBox());
if (validId !== marketId) {
navigate(`${AppRoute.Trade}/${validId}`, {
replace: true,
});
}
} 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]);
navigate(`${AppRoute.Trade}/${validId}`, {
replace: true,
});
}, [validId]);
useEffect(() => {
// Check for marketIds otherwise Abacus will silently fail its isMarketValid check
+11 -2
View File
@@ -1,7 +1,14 @@
import { useMemo } from 'react';
import { useSelector, shallowEqual } from 'react-redux';
import { MarketFilters, MARKET_FILTER_LABELS, type MarketData } from '@/constants/markets';
import {
MarketFilters,
MARKET_FILTER_LABELS,
type MarketData,
MARKETS_TO_DISPLAY,
} from '@/constants/markets';
import { testFlags } from '@/lib/testFlags';
import { getAssets } from '@/state/assetsSelectors';
import { getPerpetualMarkets } from '@/state/perpetualsSelectors';
@@ -42,7 +49,9 @@ export const useMarketsData = (
}, [allPerpetualMarkets, allAssets]);
const filteredMarkets = useMemo(() => {
const filtered = markets.filter(filterFunctions[filter]);
const filtered = markets
.filter(filterFunctions[filter])
.filter(({ id }) => (testFlags.displayAllMarkets ? true : MARKETS_TO_DISPLAY.includes(id)));
if (searchFilter) {
return filtered.filter(
+7 -36
View File
@@ -20,7 +20,6 @@ import {
type NotificationTypeConfig,
NotificationType,
DEFAULT_TOAST_AUTO_CLOSE_MS,
TransferNotificationTypes,
} from '@/constants/notifications';
import { useSelectedNetwork, useStringGetter } from '@/hooks';
@@ -28,7 +27,6 @@ import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
import { Icon, IconName } from '@/components/Icon';
import { BlockRewardNotification } from '@/views/notifications/BlockRewardNotification';
import { TradeNotification } from '@/views/notifications/TradeNotification';
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';
@@ -81,7 +79,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
renderCustomBody: ({ isToast, notification }) => (
<TradeNotification
isToast={isToast}
@@ -95,29 +92,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
);
break;
}
case 'blockReward': {
trigger(
abacusNotif.id,
{
icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
title: stringGetter({ key: abacusNotif.title }),
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
renderCustomBody: ({ isToast, notification }) => (
<BlockRewardNotification
isToast={isToast}
data={parsedData}
notification={notification}
/>
),
},
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data],
true
);
break;
}
default:
trigger(
abacusNotif.id,
@@ -127,7 +101,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
},
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data]
);
@@ -179,20 +152,20 @@ export const notificationTypes: NotificationTypeConfig[] = [
useEffect(() => {
for (const transfer of transferNotifications) {
const { fromChainId, status, txHash, toAmount, type } = transfer;
const { fromChainId, status, txHash, toAmount } = transfer;
const isFinished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;
const transferType =
type ?? fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
? TransferNotificationTypes.Withdrawal
: TransferNotificationTypes.Deposit;
const type =
fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
? 'withdrawal'
: 'deposit';
const title = stringGetter({
key: {
deposit: isFinished ? STRING_KEYS.DEPOSIT : STRING_KEYS.DEPOSIT_IN_PROGRESS,
withdrawal: isFinished ? STRING_KEYS.WITHDRAW : STRING_KEYS.WITHDRAW_IN_PROGRESS,
}[transferType],
}[type],
});
const toChainEta = status?.toChain?.chainData?.estimatedRouteDuration || 0;
@@ -217,13 +190,12 @@ export const notificationTypes: NotificationTypeConfig[] = [
slotIcon={icon}
slotTitle={title}
transfer={transfer}
type={transferType}
type={type}
triggeredAt={transfer.triggeredAt}
notification={notification}
/>
),
toastSensitivity: 'foreground',
groupKey: NotificationType.SquidTransfer,
},
[isFinished]
);
@@ -269,7 +241,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
},
}),
toastSensitivity: 'foreground',
groupKey: NotificationType.ReleaseUpdates,
},
[]
);
-44
View File
@@ -1,44 +0,0 @@
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]);
};
+3 -21
View File
@@ -31,7 +31,6 @@ import { log } from '@/lib/telemetry';
import { useAccounts } from './useAccounts';
import { useTokenConfigs } from './useTokenConfigs';
import { useDydxClient } from './useDydxClient';
import { hashFromTx } from '@/lib/hashfromTx';
type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
@@ -294,29 +293,12 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
);
const sendSquidWithdraw = useCallback(
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();
}
async (amount: number, payload: string) => {
if (!subaccountClient) {
return;
}
const tx = await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
return hashFromTx(tx?.hash);
return await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
},
[subaccountClient, sendSquidWithdrawFromSubaccount]
);
+3 -20
View File
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useState, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { EvmDerivedAddresses } from '@/constants/account';
import { LocalStorageKey } from '@/constants/localStorage';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
@@ -105,17 +104,11 @@ export const useWalletConnection = () => {
[walletConnectConfig, walletType, walletConnectionType]
);
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector });
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
const { suggestAndConnect: connectGraz } = useConnectGraz();
const [evmDerivedAddresses] = useLocalStorage({
key: LocalStorageKey.EvmDerivedAddresses,
defaultValue: {} as EvmDerivedAddresses,
});
const connectWallet = useCallback(
async ({ walletType, forceConnect }: { walletType?: WalletType; forceConnect?: boolean }) => {
if (!walletType) return { walletType, walletConnectionType };
async ({ walletType }: { walletType: WalletType }) => {
const walletConnection = getWalletConnection({ walletType });
try {
@@ -139,11 +132,7 @@ export const useWalletConnection = () => {
});
}
} else {
const isAccountConnected = Boolean(
evmAddress && evmDerivedAddresses[evmAddress]?.encryptedSignature
);
// if account connected (via remember me), do not show wagmi popup until forceConnect
if (!isConnectedWagmi && (forceConnect || !isAccountConnected)) {
if (!isConnectedWagmi) {
await connectWagmi({
connector: resolveWagmiConnector({
walletType,
@@ -239,12 +228,6 @@ export const useWalletConnection = () => {
evmAddressWagmi,
signerWagmi,
publicClientWagmi,
isConnectedWagmi,
connectWallet: () =>
connectWallet({
walletType: selectedWalletType,
forceConnect: true,
}),
// Wallet connection (Cosmos)
dydxAddress,
+3 -1
View File
@@ -1 +1,3 @@
<svg fill="none" height="10" viewBox="0 0 15 10" width="15" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m.321829 1.09389c.200562-.192959.469529-.298401.747791-.29315.27827.00525.54307.120763.73621.32115l5.194 5.5132 5.19397-5.5132c.0944-.10408.2088-.188152.3363-.247234.1275-.059081.2655-.091968.406-.096713.1404-.004746.2804.018748.4116.069088s.2509.126504.3522.223979c.1012.09747.1818.21427.2371.34347.0552.12921.084.26819.0845.40871.0006.14052-.0271.27972-.0813.40936s-.1339.24707-.2344.34534l-5.94997 6.3c-.09795.10163-.21538.18245-.34527.23766-.1299.05521-.26959.08367-.41073.08367s-.28083-.02846-.41073-.08367c-.12989-.05521-.24732-.13603-.34527-.23766l-5.950001-6.3c-.192962-.20056-.29840394-.46953-.29315364-.74779.00525031-.27827.12076264-.54307.32115364-.73621z" fill="currentColor" fill-rule="evenodd"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 6L7.83335 10.5L2 6" stroke="currentColor" stroke-width="2.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 205 B

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

After

Width:  |  Height:  |  Size: 969 B

+1 -2
View File
@@ -19,6 +19,7 @@ export { default as CoinMarketCapIcon } from './logos/coinmarketcap.svg';
export { default as CoinsIcon } from './coins.svg';
export { default as CommentIcon } from './comment.svg';
export { default as CopyIcon } from './copy.svg';
export { default as CubeIcon } from './cube.svg';
export { default as DepositIcon } from './deposit.svg';
export { default as DepthChartIcon } from './depth-chart.svg';
export { default as DiscordIcon } from './discord.svg';
@@ -43,14 +44,12 @@ export { default as OrderbookIcon } from './orderbook.svg';
export { default as OverviewIcon } from './overview.svg';
export { default as PencilIcon } from './pencil.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 PositionsIcon } from './positions.svg';
export { default as PriceChartIcon } from './price-chart.svg';
export { default as PrivacyIcon } from './privacy.svg';
export { default as ProfileIcon } from './profile.svg';
export { default as QrIcon } from './qr.svg';
export { default as RewardStarIcon } from './reward-star.svg';
export { default as SearchIcon } from './search.svg';
export { default as SendIcon } from './send.svg';
export { default as ShareIcon } from './share.svg';
-1
View File
@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 731 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 481 KiB

@@ -1,11 +1,7 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import styled, { css } from 'styled-components';
import {
type Notification,
type NotificationDisplayData,
NotificationStatus,
} from '@/constants/notifications';
import { NotificationStatus } from '@/constants/notifications';
import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { useNotifications } from '@/hooks/useNotifications';
import { useBreakpoints } from '@/hooks/useBreakpoints';
@@ -17,31 +13,54 @@ import { Toast } from '@/components/Toast';
import { ToastArea } from '@/components/ToastArea';
import { ToggleButton } from '@/components/ToggleButton';
type ElementProps = {
notifications: {
notification: Notification<any, any>;
key: string;
displayData: NotificationDisplayData;
}[];
};
type StyleProps = {
className?: string;
};
export const NotificationStack = ({ notifications, className }: ElementProps & StyleProps) => {
const MAX_TOASTS = 10;
export const NotificationsToastArea = ({ className }: StyleProps) => {
const [shouldStackNotifications, setshouldStackNotifications] = useState(true);
const { markUnseen, markSeen, onNotificationAction } = useNotifications();
const {
notifications,
getKey,
getDisplayData,
markUnseen,
markSeen,
isMenuOpen,
onNotificationAction,
} = useNotifications();
const { isMobile } = useBreakpoints();
const hasMultipleToasts = notifications.length > 1;
const notificationMap = useMemo(() => {
return (
Object.values(notifications)
// Sort by time of first trigger
.sort(
(n1, n2) =>
n1.timestamps[NotificationStatus.Triggered]! -
n2.timestamps[NotificationStatus.Triggered]!
)
.map((notification) => ({
notification,
key: getKey(notification),
displayData: getDisplayData(notification),
}))
.filter(
({ displayData, notification }) =>
displayData && notification.status < NotificationStatus.Unseen
)
.slice(-MAX_TOASTS)
);
}, [notifications, getKey, getDisplayData]);
if (isMenuOpen) return null;
const hasMultipleToasts = notificationMap.length > 1;
return (
<StyledToastArea
swipeDirection={isMobile ? 'up' : 'right'}
className={className}
size={notifications.length}
>
<StyledToastArea swipeDirection={isMobile ? 'up' : 'right'} className={className}>
{hasMultipleToasts && (
<StyledToggleButton
shape={ButtonShape.Pill}
@@ -53,12 +72,12 @@ export const NotificationStack = ({ notifications, className }: ElementProps & S
</StyledToggleButton>
)}
{notifications.map(({ notification, key, displayData }, idx) => (
{notificationMap.map(({ notification, key, displayData }, idx) => (
<StyledToast
key={key}
isStacked={idx > 0 && shouldStackNotifications}
isStacked={shouldStackNotifications}
isOpen={notification.status < NotificationStatus.Unseen}
layer={idx}
layer={notificationMap.length - 1 - idx}
notification={notification}
slotIcon={displayData.icon}
slotTitle={displayData.title}
@@ -91,13 +110,19 @@ export const NotificationStack = ({ notifications, className }: ElementProps & S
);
};
const StyledToastArea = styled(ToastArea)<{ size: number }>`
position: relative;
${({ size }) =>
size &&
css`
padding-bottom: calc(${size - 1} * 2px);
`}
const StyledToastArea = styled(ToastArea)`
position: absolute;
width: min(17.5rem, 100%);
inset: 0 0 0 auto;
padding: 0.75rem 0.75rem 0.75rem 0;
mask-image: linear-gradient(to left, transparent, white 0.5rem);
@media ${breakpoints.mobile} {
width: 100%;
position: fixed;
}
`;
const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
@@ -108,7 +133,7 @@ const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
position: absolute;
left: 0;
top: calc(${layer} * 2px);
right: 0;
right: calc(${layer} * -2px);
`
: css`
margin-bottom: 0.75rem;
@@ -120,7 +145,7 @@ const StyledToggleButton = styled(ToggleButton)<{ isPressed: boolean }>`
pointer-events: auto;
display: none;
position: absolute;
top: -0.5rem;
top: 4px;
left: calc(50% - 0.75rem);
--button-width: 2rem;
--button-height: 1rem;
@@ -1,71 +0,0 @@
import { useMemo } from 'react';
import { groupBy } from 'lodash';
import styled from 'styled-components';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { NotificationStatus } from '@/constants/notifications';
import { useNotifications } from '@/hooks/useNotifications';
import { NotificationStack } from './NotifcationStack';
type StyleProps = {
className?: string;
};
const MAX_TOASTS = 10;
export const NotificationsToastArea = ({ className }: StyleProps) => {
const { notifications, getKey, getDisplayData, isMenuOpen } = useNotifications();
const notificationMapByType = useMemo(() => {
const notificationMap = Object.values(notifications)
// Sort by time of first trigger
.sort(
(n1, n2) =>
n1.timestamps[NotificationStatus.Triggered]! -
n2.timestamps[NotificationStatus.Triggered]!
)
.map((notification) => ({
notification,
key: getKey(notification),
displayData: getDisplayData(notification),
}))
.filter(
({ displayData, notification }) =>
displayData && notification.status < NotificationStatus.Unseen
)
.slice(-MAX_TOASTS);
return groupBy(notificationMap, (notification) => notification.displayData?.groupKey);
}, [notifications, getKey, getDisplayData]);
if (isMenuOpen) return null;
return (
<StyledToastArea className={className}>
{Object.keys(notificationMapByType).map((groupKey) => (
<NotificationStack notifications={notificationMapByType[groupKey]} key={groupKey} />
))}
</StyledToastArea>
);
};
const StyledToastArea = styled.div`
${layoutMixins.scrollArea}
position: absolute;
width: min(17.5rem, 100%);
inset: 0 0 0 auto;
padding: 0.75rem 0.75rem 0.75rem 0;
mask-image: linear-gradient(to left, transparent, white 0.5rem);
pointer-events: none;
@media ${breakpoints.mobile} {
width: 100%;
position: fixed;
}
`;
+18 -120
View File
@@ -2,7 +2,6 @@ import Abacus, { type Nullable } from '@dydxprotocol/v4-abacus';
import Long from 'long';
import type { IndexedTx } from '@cosmjs/stargate';
import { GAS_MULTIPLIER, encodeJson } from '@dydxprotocol/v4-client-js';
import { EncodeObject } from '@cosmjs/proto-signing';
import {
CompositeClient,
@@ -42,7 +41,6 @@ import { openDialog } from '@/state/dialogs';
import { StatefulOrderError } from '../errors';
import { bytesToBigInt } from '../numbers';
import { log } from '../telemetry';
import { hashFromTx } from '../hashfromTx';
class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
private compositeClient: CompositeClient | undefined;
@@ -56,10 +54,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
this.store = undefined;
}
get isNobleClientConnected(): boolean {
return this.nobleClient?.isConnected ?? false;
}
setStore(store: RootStore): void {
this.store = store;
}
@@ -69,13 +63,9 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
}
setNobleWallet(nobleWallet: LocalWallet) {
try {
this.nobleWallet = nobleWallet;
if (this.nobleClient) {
this.nobleClient.connect(nobleWallet);
}
} catch (e) {
log('DydxChainTransactions/setNobleWallet', e);
this.nobleWallet = nobleWallet;
if (this.nobleClient) {
this.nobleClient.connect(nobleWallet);
}
}
@@ -122,15 +112,10 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
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);
if (nobleValidatorUrl) {
this.nobleClient = new NobleClient(nobleValidatorUrl);
if (this.nobleWallet) await this.nobleClient.connect(this.nobleWallet);
}
// Dispatch custom event to notify other parts of the app that the network has been connected
const customEvent = new CustomEvent('abacus:connectNetwork', {
detail: parsedParams,
@@ -216,8 +201,8 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
timeInForce as OrderTimeInForce,
goodTilTimeInSeconds ?? 0,
execution as OrderExecution,
postOnly ?? undefined,
reduceOnly ?? undefined,
postOnly,
reduceOnly,
triggerPrice ?? undefined
);
@@ -355,7 +340,12 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
}
}
async sendNobleIBC(params: { msgTypeUrl: string; msg: any }): Promise<string> {
async sendNobleIBC(
params: {
msgTypeUrl: string,
msg: any,
}
): Promise<string> {
if (!this.nobleClient?.isConnected) {
throw new Error('Missing nobleClient or localWallet');
}
@@ -366,16 +356,15 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
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) -
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]);
@@ -391,86 +380,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
}
}
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(
type: TransactionTypes,
paramsInJson: Abacus.Nullable<string>,
@@ -505,17 +414,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
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: {
break;
}
+2 -14
View File
@@ -84,7 +84,8 @@ class AbacusStateManager {
);
const appConfigs = AbacusAppConfig.Companion.forWeb;
appConfigs.squidVersion = AbacusAppConfig.SquidVersion.V2;
if (!isMainnet || testFlags.withCCTP)
appConfigs.squidVersion = AbacusAppConfig.SquidVersion.V2DepositOnly;
this.stateManager = new AsyncAbacusStateManager(
'',
@@ -171,15 +172,6 @@ class AbacusStateManager {
this.setTransferValue({ value: null, field: TransferInputField.usdcSize });
};
resetInputState = () => {
this.clearTransferInputValues();
this.setTransferValue({
field: TransferInputField.type,
value: null,
});
this.clearTradeInputValues();
};
// ------ Set Data ------ //
setStore = (store: RootStore) => {
this.store = store;
@@ -270,10 +262,6 @@ class AbacusStateManager {
) => void
) => this.stateManager.cancelOrder(orderId, callback);
cctpWithdraw = (
callback: (success: boolean, parsingError: Nullable<ParsingError>, data: string) => void
): void => this.stateManager.commitCCTPWithdraw(callback);
// ------ Utils ------ //
getHistoricalPnlPeriod = (): Nullable<HistoricalPnlPeriods> =>
this.stateManager.historicalPnlPeriod;
-3
View File
@@ -1,3 +0,0 @@
export const hashFromTx = (
txHash: string | Uint8Array
): string => `0x${Buffer.from(txHash).toString('hex')}`;
+9 -5
View File
@@ -5,22 +5,26 @@ class TestFlags {
this.queryParams = {};
const hash = window.location.hash;
const queryIndex = hash.indexOf('?');
if (queryIndex === -1) return;
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;
this.queryParams[key] = value;
}
}
get displayInitializingMarkets() {
return !!this.queryParams.displayinitializingmarkets;
return !!this.queryParams.displayInitializingMarkets;
}
get showMobileSignInOption() {
return !!this.queryParams.mobilesignin;
get displayAllMarkets() {
return !!this.queryParams.displayAllMarkets;
}
get withCCTP() {
return !!this.queryParams.withCCTP;
}
}
+3 -8
View File
@@ -1,4 +1,4 @@
import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
import { createConfig, configureChains, mainnet, Chain, Connector, usePublicClient } from 'wagmi';
import { goerli } from 'wagmi/chains';
import {
@@ -26,9 +26,6 @@ import {
fantomTestnet,
celo,
celoAlfajores,
scroll,
kava,
sepolia,
} from 'viem/chains';
import { alchemyProvider } from 'wagmi/providers/alchemy';
@@ -56,7 +53,7 @@ import { isTruthy } from './isTruthy';
export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
mainnet,
goerli,
sepolia,
arbitrum,
arbitrumGoerli,
avalanche,
@@ -81,8 +78,6 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
fantomTestnet,
celo,
celoAlfajores,
scroll,
kava,
];
const { chains, publicClient, webSocketPublicClient } = configureChains(
@@ -151,7 +146,7 @@ const getConnectors = (walletConnectConfig: WalletConnectConfig) => [
new CoinbaseWalletConnector({
chains,
options: {
appName: 'dYdX',
appName: 'wagmi',
reloadOnDisconnect: false,
},
}),
+1 -3
View File
@@ -1,6 +1,4 @@
import { DialogPlacement } from '@/components/Dialog';
import { NotificationsMenu } from '@/views/menus/NotificationsMenu';
const AlertsPage = () => <NotificationsMenu placement={DialogPlacement.Inline} />;
export default AlertsPage;
export const AlertsPage = () => <NotificationsMenu placement={DialogPlacement.Inline} />;
+1 -3
View File
@@ -2,7 +2,7 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { articleMixins } from '@/styles/articleMixins';
const PrivacyPolicyPage = () => (
export const PrivacyPolicyPage = () => (
<Styled.Article>
<header>
<h1>Privacy Policy</h1>
@@ -10,8 +10,6 @@ const PrivacyPolicyPage = () => (
</Styled.Article>
);
export default PrivacyPolicyPage;
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Article = styled.article`
+15 -36
View File
@@ -5,7 +5,7 @@ import { useEnsName } from 'wagmi';
import { useNavigate } from 'react-router-dom';
import { ButtonSize } from '@/constants/buttons';
import { TransferType } from '@/constants/abacus';
import { TransferInputField, TransferType } from '@/constants/abacus';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -26,14 +26,10 @@ import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
import { getOnboardingState } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import abacusStateManager from '@/lib/abacus';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
import { MigratePanel } from './rewards/MigratePanel';
import { GovernancePanel } from './rewards/GovernancePanel';
import { StakingPanel } from './rewards/StakingPanel';
const ENS_CHAIN_ID = 1; // Ethereum
const Profile = () => {
@@ -180,26 +176,7 @@ const Profile = () => {
/>
</Styled.EqualGrid>
<MigratePanel />
<DYDXBalancePanel />
<Styled.EqualGrid>
<Styled.RewardsPanel
slotHeaderContent="Trading Rewards"
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{
key: 'week-rewards',
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
value: '-',
},
]}
layout="grid"
/>
</Styled.RewardsPanel>
<Panel
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
@@ -214,6 +191,19 @@ const Profile = () => {
layout="grid"
/>
</Panel>
<Styled.RewardsPanel
slotHeaderContent={stringGetter({ key: STRING_KEYS.REWARDS })}
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.YOU_WILL_EARN }), value: '-' },
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.EPOCH_ENDS_IN }), value: '-' },
]}
layout="grid"
/>
</Styled.RewardsPanel>
</Styled.EqualGrid>
<Styled.TablePanel
@@ -231,9 +221,6 @@ const Profile = () => {
withInnerBorders={false}
/>
</Styled.TablePanel>
<GovernancePanel />
<StakingPanel />
</Styled.MobileProfileLayout>
);
};
@@ -247,7 +234,6 @@ Styled.MobileProfileLayout = styled.div`
gap: 1rem;
padding: 1.25rem 0.9rem;
max-width: 100vw;
`;
Styled.Header = styled.header`
@@ -341,13 +327,6 @@ Styled.Details = styled(Details)`
`;
Styled.RewardsPanel = styled(Panel)`
align-self: flex-start;
&,
> * {
height: 100%;
}
dl {
--details-grid-numColumns: 1;
}
+1 -3
View File
@@ -2,7 +2,7 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { articleMixins } from '@/styles/articleMixins';
const TermsOfUsePage = () => (
export const TermsOfUsePage = () => (
<Styled.Article>
<header>
<h1>Sample Terms of Use</h1>
@@ -1076,8 +1076,6 @@ const TermsOfUsePage = () => (
</Styled.Article>
);
export default TermsOfUsePage;
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Article = styled.article`
+57 -83
View File
@@ -1,40 +1,34 @@
import { lazy, Suspense } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent } from 'styled-components';
import { Navigate, Route, Routes } from 'react-router-dom';
import { OnboardingState } from '@/constants/account';
import { ButtonAction } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { HistoryRoute, PortfolioRoute } from '@/constants/routes';
import { useAccountBalance, useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable';
import { FundingPaymentsTable } from '@/views/tables/FundingPaymentsTable';
import { TransferHistoryTable } from '@/views/tables/TransferHistoryTable';
import { Button } from '@/components/Button';
import { Icon, IconName } from '@/components/Icon';
import { NavigationMenu } from '@/components/NavigationMenu';
import { Tag, TagType } from '@/components/Tag';
import { WithSidebar } from '@/components/WithSidebar';
import { getOnboardingState, getSubaccount, getTradeInfoNumbers } from '@/state/accountSelectors';
import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import { shortenNumberForDisplay } from '@/lib/numbers';
import { PortfolioNavMobile } from './PortfolioNavMobile';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { Overview } from './Overview';
import { Positions } from './Positions';
import { Orders } from './Orders';
import { Fees } from './Fees';
import { History } from './History';
const Overview = lazy(() => import('./Overview').then((module) => ({ default: module.Overview })));
const Positions = lazy(() =>
import('./Positions').then((module) => ({ default: module.Positions }))
);
const Orders = lazy(() => import('./Orders').then((module) => ({ default: module.Orders })));
const Fees = lazy(() => import('./Fees').then((module) => ({ default: module.Fees })));
const History = lazy(() => import('./History').then((module) => ({ default: module.History })));
import { layoutMixins } from '@/styles/layoutMixins';
import { Button } from '@/components/Button';
import { ButtonAction } from '@/constants/buttons';
export default () => {
const dispatch = useDispatch();
@@ -45,60 +39,54 @@ export default () => {
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { nativeTokenBalance } = useAccountBalance();
const { numTotalPositions, numTotalOpenOrders } = useSelector(getTradeInfoNumbers, shallowEqual) || {};
const numPositions = shortenNumberForDisplay(numTotalPositions);
const numOrders = shortenNumberForDisplay(numTotalOpenOrders);
const usdcBalance = freeCollateral?.current || 0;
useDocumentTitle(stringGetter({ key: STRING_KEYS.PORTFOLIO }));
const routesComponent = (
<Suspense fallback={<LoadingSpace id="portfolio" />}>
<Routes>
<Route path={PortfolioRoute.Overview} element={<Overview />} />
<Route path={PortfolioRoute.Positions} element={<Positions />} />
<Route path={PortfolioRoute.Orders} element={<Orders />} />
<Route path={PortfolioRoute.Fees} element={<Fees />} />
<Route path={PortfolioRoute.History} element={<History />}>
<Route index path="*" element={<Navigate to={HistoryRoute.Trades} />} />
<Route
path={HistoryRoute.Trades}
element={
<FillsTable
columnKeys={
isTablet
? [
FillsTableColumnKey.Time,
FillsTableColumnKey.TypeAmount,
FillsTableColumnKey.PriceFee,
]
: [
FillsTableColumnKey.Time,
FillsTableColumnKey.Market,
FillsTableColumnKey.Side,
FillsTableColumnKey.AmountPrice,
FillsTableColumnKey.TotalFee,
FillsTableColumnKey.Type,
FillsTableColumnKey.Liquidity,
]
}
withOuterBorder={isNotTablet}
/>
}
/>
<Route
path={HistoryRoute.Transfers}
element={<TransferHistoryTable withOuterBorder={isNotTablet} />}
/>
<Route
path={HistoryRoute.Payments}
element={<FundingPaymentsTable withOuterBorder={isNotTablet} />}
/>
</Route>
<Route path="*" element={<Navigate to={PortfolioRoute.Overview} replace />} />
</Routes>
</Suspense>
<Routes>
<Route path={PortfolioRoute.Overview} element={<Overview />} />
<Route path={PortfolioRoute.Positions} element={<Positions />} />
<Route path={PortfolioRoute.Orders} element={<Orders />} />
<Route path={PortfolioRoute.Fees} element={<Fees />} />
<Route path={PortfolioRoute.History} element={<History />}>
<Route index path="*" element={<Navigate to={HistoryRoute.Trades} />} />
<Route
path={HistoryRoute.Trades}
element={
<FillsTable
columnKeys={
isTablet
? [
FillsTableColumnKey.Time,
FillsTableColumnKey.TypeAmount,
FillsTableColumnKey.PriceFee,
]
: [
FillsTableColumnKey.Time,
FillsTableColumnKey.Market,
FillsTableColumnKey.Side,
FillsTableColumnKey.AmountPrice,
FillsTableColumnKey.TotalFee,
FillsTableColumnKey.Type,
FillsTableColumnKey.Liquidity,
]
}
withOuterBorder={isNotTablet}
/>
}
/>
<Route
path={HistoryRoute.Transfers}
element={<TransferHistoryTable withOuterBorder={isNotTablet} />}
/>
<Route
path={HistoryRoute.Payments}
element={<FundingPaymentsTable withOuterBorder={isNotTablet} />}
/>
</Route>
<Route path="*" element={<Navigate to={PortfolioRoute.Overview} replace />} />
</Routes>
);
return isTablet ? (
@@ -125,28 +113,14 @@ export default () => {
},
{
value: PortfolioRoute.Positions,
slotBefore: <Styled.Icon iconName={IconName.Positions} />,
label: (
<>
{stringGetter({ key: STRING_KEYS.POSITIONS })}
{numPositions > 0 && (
<Tag type={TagType.Number}> {numPositions} </Tag>
)}
</>
),
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
href: PortfolioRoute.Positions,
},
{
value: PortfolioRoute.Orders,
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
label: (
<>
{stringGetter({ key: STRING_KEYS.ORDERS })}
{numOrders > 0 && (
<Tag type={TagType.Number}> {numOrders} </Tag>
)}
</>
),
label: stringGetter({ key: STRING_KEYS.ORDERS }),
href: PortfolioRoute.Orders,
},
{
@@ -217,7 +191,7 @@ Styled.SideBar = styled.div`
Styled.Footer = styled.div`
${layoutMixins.row}
flex-wrap: wrap;
padding: 1rem;
gap: 0.5rem;
+12 -2
View File
@@ -2,6 +2,7 @@ import type { ElementType } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import breakpoints from '@/styles/breakpoints';
import { layoutMixins } from '@/styles/layoutMixins';
import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
@@ -32,7 +33,7 @@ export const DYDXBalancePanel = () => {
const { chainTokenLabel } = useTokenConfigs();
return (
<Panel
<Styled.Panel
slotHeader={
<Styled.Header>
<Styled.Title>
@@ -108,12 +109,21 @@ export const DYDXBalancePanel = () => {
]}
/>
</Styled.Content>
</Panel>
</Styled.Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
--panel-paddingX: 1.5rem;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Header = styled.div`
${layoutMixins.spacedRow}
gap: 1rem;
-75
View File
@@ -1,75 +0,0 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const GovernancePanel = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { governanceLearnMore } = useURLConfigs();
return (
<Panel
slotHeaderContent={
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
+13 -8
View File
@@ -24,8 +24,6 @@ import { openDialog } from '@/state/dialogs';
import { log } from '@/lib/telemetry';
const SEASON_NUMBER = 2;
export const LaunchIncentivesPanel = () => {
const { isNotTablet } = useBreakpoints();
const dispatch = useDispatch();
@@ -70,12 +68,10 @@ const EstimatedRewards = () => {
const { data, isLoading } = useQuery({
enabled: !!dydxAddress,
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}_${SEASON_NUMBER}`,
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}`,
queryFn: async () => {
if (!dydxAddress) return undefined;
const resp = await fetch(
`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}?n=${SEASON_NUMBER}`
);
const resp = await fetch(`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}`);
return (await resp.json())?.incentivePoints;
},
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
@@ -89,7 +85,9 @@ const EstimatedRewards = () => {
<Styled.Season>
{stringGetter({
key: STRING_KEYS.LAUNCH_INCENTIVES_SEASON_NUM,
params: { SEASON_NUMBER },
params: {
SEASON_NUMBER: 1,
},
})}
</Styled.Season>
</div>
@@ -153,8 +151,15 @@ const LaunchIncentivesContent = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
--panel-paddingY: 1rem;
--panel-paddingX: 1.5rem;
background-color: var(--color-layer-4);
width: 100%;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
}
`;
Styled.ForV4 = styled.span`
@@ -218,7 +223,7 @@ Styled.Column = styled.div`
Styled.EstimatedRewardsCard = styled.div`
${layoutMixins.spacedRow}
padding: 1rem 1.25rem;
min-width: 19rem;
min-width: 21.25rem;
height: calc(100% - calc(1.5rem * 2));
margin: 1.5rem;
+12 -7
View File
@@ -7,6 +7,8 @@ import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { Details } from '@/components/Details';
@@ -26,7 +28,7 @@ import { MustBigNumber } from '@/lib/numbers';
const TOKEN_MIGRATION_LEARN_MORE_LINK =
'https://www.dydx.foundation/blog/update-on-exploring-the-future-of-dydx';
export const MigratePanel = ({ className }: { className?: string }) => {
export const MigratePanel = () => {
const { isNotTablet } = useBreakpoints();
const stringGetter = useStringGetter();
@@ -43,7 +45,6 @@ export const MigratePanel = ({ className }: { className?: string }) => {
return isNotTablet ? (
<Styled.MigratePanel
className={className}
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.MIGRATE })}</Styled.Title>}
slotRight={
<Styled.MigrateAction>
@@ -73,7 +74,6 @@ export const MigratePanel = ({ className }: { className?: string }) => {
</Styled.MigratePanel>
) : (
<Styled.MigratePanel
className={className}
slotHeader={
<Styled.MobileMigrateHeader>
<h3>
@@ -134,11 +134,17 @@ export const MigratePanel = ({ className }: { className?: string }) => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.MigratePanel = styled(Panel)`
--panel-paddingX: 1.5rem;
width: 100%;
background-image: url('/dots-background.svg');
background-position: right;
background-repeat: no-repeat;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Title = styled.h3`
@@ -146,15 +152,14 @@ Styled.Title = styled.h3`
color: var(--color-text-2);
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
margin-bottom: -0.5rem;
`;
Styled.MigrateAction = styled.div`
${layoutMixins.flexEqualColumns}
align-items: center;
margin-right: 1rem;
gap: 1rem;
padding: 1rem;
margin: 1rem;
width: 100%;
background-color: var(--color-layer-2);
@@ -192,7 +197,7 @@ Styled.MobileMigrateHeader = styled.div`
font: var(--font-small-book);
color: var(--color-text-0);
padding: 1rem 1rem 0;
padding: 1rem 1.5rem 0;
h3 {
${layoutMixins.inlineRow}
@@ -234,4 +239,4 @@ Styled.InlineRow = styled.div`
${layoutMixins.inlineRow}
color: var(--color-text-0);
--link-color: var(--color-text-1);
`;
`;
-86
View File
@@ -1,86 +0,0 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { breakpoints } from '@/styles';
import { STRING_KEYS } from '@/constants/localization';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter } from '@/hooks';
import { Accordion } from '@/components/Accordion';
import { Link } from '@/components/Link';
import { Panel } from '@/components/Panel';
const REWARDS_LEARN_MORE_LINK = ''; // to be configured
export const RewardsHelpPanel = () => {
const stringGetter = useStringGetter();
return (
<Styled.HelpCard
slotHeader={
<Styled.Header>
<h3>{stringGetter({ key: STRING_KEYS.HELP })}</h3>
{REWARDS_LEARN_MORE_LINK && (
<Link withIcon href={REWARDS_LEARN_MORE_LINK}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
)}
</Styled.Header>
}
>
<Accordion
items={[
{
header: 'Who is eligible for trading rewards?',
content: 'All traders are eligible for trading rewards.',
},
{
header: 'How do trading rewards work?',
content:
'Immediately after each fill, trading rewards are sent directly to the traders dYdX Chain address, based on the amount of fees paid by the trader.',
},
{
header: 'How do I claim my rewards?',
content:
'Each block, trading rewards are automatically sent directly to the traders dYdX Chain address.',
},
]}
/>
</Styled.HelpCard>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.HelpCard = styled(Panel)`
--panel-content-paddingX: 0;
--panel-content-paddingY: 0;
width: 100%;
height: max-content;
padding: 0;
gap: 0;
text-align: start;
`;
Styled.Header = styled.div`
${layoutMixins.spacedRow}
gap: 1ch;
padding: 1rem 1rem;
border-bottom: var(--border-width) solid var(--border-color);
font: var(--font-small-book);
@media ${breakpoints.notTablet} {
padding: 1.5rem 1.25rem;
}
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
}
`;
Styled.Link = styled(Link)`
display: inline-flex;
`;
+85 -41
View File
@@ -1,43 +1,51 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { STRING_KEYS } from '@/constants/localization';
import { AppRoute } from '@/constants/routes';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { BackButton } from '@/components/BackButton';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
import { DYDXBalancePanel } from './DYDXBalancePanel';
import { MigratePanel } from './MigratePanel';
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
import { RewardsHelpPanel } from './RewardsHelpPanel';
import { GovernancePanel } from './GovernancePanel';
import { StakingPanel } from './StakingPanel';
const RewardsPage = () => {
export const RewardsPage = () => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
const { isTablet, isNotTablet } = useBreakpoints();
const navigate = useNavigate();
const panelArrow = (
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
);
return (
<Styled.Page>
{isTablet && (
<Styled.MobileHeader>
<BackButton onClick={() => navigate(AppRoute.Profile)} />
{stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
</Styled.MobileHeader>
)}
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <MigratePanel />}
{import.meta.env.VITE_V3_TOKEN_ADDRESS && <MigratePanel />}
{isTablet ? (
<LaunchIncentivesPanel />
<>
<LaunchIncentivesPanel />
<DYDXBalancePanel />
</>
) : (
<Styled.PanelRowIncentivesAndBalance>
<LaunchIncentivesPanel />
@@ -45,20 +53,37 @@ const RewardsPage = () => {
</Styled.PanelRowIncentivesAndBalance>
)}
{isNotTablet && (
<Styled.PanelRow>
<GovernancePanel />
<StakingPanel />
</Styled.PanelRow>
)}
<Styled.PanelRow>
<Styled.Panel
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
<RewardsHelpPanel />
<Styled.Panel
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
</Styled.PanelRow>
</Styled.Page>
);
};
export default RewardsPage;
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Page = styled.div`
@@ -68,35 +93,45 @@ Styled.Page = styled.div`
align-items: center;
> * {
--content-max-width: 80rem;
--content-max-width: 70rem;
max-width: min(calc(100vw - 4rem), var(--content-max-width));
}
@media ${breakpoints.tablet} {
--stickyArea-topHeight: var(--page-header-height-mobile);
padding: 0 1rem 1rem;
padding: 1.25rem;
> * {
max-width: calc(100vw - 2rem);
max-width: calc(100vw - 2.5rem);
width: 100%;
}
}
`;
Styled.MobileHeader = styled.header`
${layoutMixins.contentSectionDetachedScrollable}
${layoutMixins.stickyHeader}
z-index: 2;
padding: 1.25rem 0;
margin-bottom: -1.5rem;
Styled.Panel = styled(Panel)`
--panel-paddingX: 1.5rem;
font: var(--font-large-medium);
color: var(--color-text-2);
background-color: var(--color-layer-2);
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Panel = styled(Panel)`
height: fit-content;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
padding: 1rem 1.5rem 0;
`;
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.PanelRow = styled.div`
@@ -112,3 +147,12 @@ Styled.PanelRow = styled.div`
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
grid-template-columns: 2fr 1fr;
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding: 1rem;
`;
-73
View File
@@ -1,73 +0,0 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const StakingPanel = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { stakingLearnMore } = useURLConfigs();
return (
<Panel
slotHeaderContent={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
+1 -3
View File
@@ -25,7 +25,7 @@ import { useNetworks } from '@/views/menus/useNetworks';
import { SettingsHeader } from './SettingsHeader';
import { ComingSoonSpace } from '@/components/ComingSoon';
const SettingsPage = () => {
export const SettingsPage = () => {
const stringGetter = useStringGetter();
const { pathname } = useLocation();
const dispatch = useDispatch();
@@ -105,5 +105,3 @@ const SettingsPage = () => {
</>
);
};
export default SettingsPage;
+116 -130
View File
@@ -1,7 +1,8 @@
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent } from 'styled-components';
import { AbacusOrderStatus } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { useBreakpoints, useStringGetter } from '@/hooks';
@@ -27,6 +28,7 @@ import {
getCurrentMarketTradeInfoNumbers,
getHasUnseenFillUpdates,
getHasUnseenOrderUpdates,
getLatestOrderStatus,
getTradeInfoNumbers,
} from '@/state/accountSelectors';
@@ -80,141 +82,125 @@ export const HorizontalPanel = ({ isOpen = true, setIsOpen }: ElementProps) => {
showCurrentMarket ? numOpenOrders : numTotalOpenOrders
);
const tabItems = useMemo(
() => [
{
value: InfoSection.Position,
label: stringGetter({
key: showCurrentMarket ? STRING_KEYS.POSITION : STRING_KEYS.POSITIONS,
}),
const tabItems = [
{
value: InfoSection.Position,
label: stringGetter({
key: showCurrentMarket ? STRING_KEYS.POSITION : STRING_KEYS.POSITIONS,
}),
tag: showCurrentMarket ? null : shortenNumberForDisplay(numTotalPositions),
tag: showCurrentMarket ? null : shortenNumberForDisplay(numTotalPositions),
content: showCurrentMarket ? (
<PositionInfo showNarrowVariation={isTablet} />
) : (
<PositionsTable
columnKeys={
isTablet
? [
PositionsTableColumnKey.Details,
PositionsTableColumnKey.IndexEntry,
PositionsTableColumnKey.PnL,
]
: [
PositionsTableColumnKey.Market,
PositionsTableColumnKey.Side,
PositionsTableColumnKey.Size,
PositionsTableColumnKey.Leverage,
PositionsTableColumnKey.LiquidationAndOraclePrice,
PositionsTableColumnKey.UnrealizedPnl,
PositionsTableColumnKey.RealizedPnl,
PositionsTableColumnKey.AverageOpenAndClose,
]
}
onNavigate={() => setView(PanelView.CurrentMarket)}
/>
),
},
{
asChild: true,
value: InfoSection.Orders,
label: stringGetter({ key: STRING_KEYS.ORDERS }),
content: showCurrentMarket ? (
<PositionInfo showNarrowVariation={isTablet} />
) : (
<PositionsTable
columnKeys={
isTablet
? [
PositionsTableColumnKey.Details,
PositionsTableColumnKey.IndexEntry,
PositionsTableColumnKey.PnL,
]
: [
PositionsTableColumnKey.Market,
PositionsTableColumnKey.Side,
PositionsTableColumnKey.Size,
PositionsTableColumnKey.Leverage,
PositionsTableColumnKey.LiquidationAndOraclePrice,
PositionsTableColumnKey.UnrealizedPnl,
PositionsTableColumnKey.RealizedPnl,
PositionsTableColumnKey.AverageOpenAndClose,
]
}
onNavigate={() => setView(PanelView.CurrentMarket)}
/>
),
},
{
value: InfoSection.Orders,
label: stringGetter({ key: STRING_KEYS.ORDERS }),
slotRight: isWaitingForOrderToIndex ? (
<Styled.LoadingSpinner />
) : (
ordersTagNumber && (
<Tag type={TagType.Number} isHighlighted={hasUnseenOrderUpdates}>
{ordersTagNumber}
</Tag>
)
),
content: (
<OrdersTable
currentMarket={showCurrentMarket ? currentMarketId : undefined}
columnKeys={
isTablet
? [OrdersTableColumnKey.StatusFill, OrdersTableColumnKey.PriceType]
: [
!showCurrentMarket && OrdersTableColumnKey.Market,
OrdersTableColumnKey.Status,
OrdersTableColumnKey.Side,
OrdersTableColumnKey.AmountFill,
OrdersTableColumnKey.Price,
OrdersTableColumnKey.Trigger,
OrdersTableColumnKey.GoodTil,
!isAccountViewOnly && OrdersTableColumnKey.Actions,
].filter(isTruthy)
}
/>
),
},
{
asChild: true,
value: InfoSection.Fills,
label: stringGetter({ key: STRING_KEYS.FILLS }),
slotRight: fillsTagNumber && (
<Tag type={TagType.Number} isHighlighted={hasUnseenFillUpdates}>
{fillsTagNumber}
slotRight: isWaitingForOrderToIndex ? (
<Styled.LoadingSpinner />
) : (
ordersTagNumber && (
<Tag type={TagType.Number} isHighlighted={hasUnseenOrderUpdates}>
{ordersTagNumber}
</Tag>
),
)
),
content: (
<FillsTable
currentMarket={showCurrentMarket ? currentMarketId : undefined}
columnKeys={
isTablet
? [
FillsTableColumnKey.Time,
FillsTableColumnKey.TypeAmount,
FillsTableColumnKey.PriceFee,
]
: [
!showCurrentMarket && FillsTableColumnKey.Market,
FillsTableColumnKey.Time,
FillsTableColumnKey.Type,
FillsTableColumnKey.Side,
FillsTableColumnKey.AmountTag,
FillsTableColumnKey.Price,
FillsTableColumnKey.TotalFee,
FillsTableColumnKey.Liquidity,
].filter(isTruthy)
}
columnWidths={{
[FillsTableColumnKey.TypeAmount]: '100%',
}}
/>
),
},
// TODO - TRCL-1693 - re-enable when funding payments are supported
// {
// value: InfoSection.Payments,
// label: stringGetter({ key: STRING_KEYS.PAYMENTS }),
content: (
<OrdersTable
currentMarket={showCurrentMarket ? currentMarketId : undefined}
columnKeys={
isTablet
? [OrdersTableColumnKey.StatusFill, OrdersTableColumnKey.PriceType]
: [
!showCurrentMarket && OrdersTableColumnKey.Market,
OrdersTableColumnKey.Status,
OrdersTableColumnKey.Side,
OrdersTableColumnKey.AmountFill,
OrdersTableColumnKey.Price,
OrdersTableColumnKey.Trigger,
OrdersTableColumnKey.GoodTil,
!isAccountViewOnly && OrdersTableColumnKey.Actions,
].filter(isTruthy)
}
/>
),
},
{
value: InfoSection.Fills,
label: stringGetter({ key: STRING_KEYS.FILLS }),
// tag: shortenNumberForDisplay(
// showCurrentMarket ? numFundingPayments : numTotalFundingPayments
// ),
// content: (
// <FundingPaymentsTable currentMarket={showCurrentMarket ? currentMarket?.id : undefined} />
// ),
// },
],
[
stringGetter,
currentMarketId,
showCurrentMarket,
isTablet,
isWaitingForOrderToIndex,
isAccountViewOnly,
ordersTagNumber,
fillsTagNumber,
hasUnseenFillUpdates,
hasUnseenOrderUpdates,
]
);
slotRight: fillsTagNumber && (
<Tag type={TagType.Number} isHighlighted={hasUnseenFillUpdates}>
{fillsTagNumber}
</Tag>
),
content: (
<FillsTable
currentMarket={showCurrentMarket ? currentMarketId : undefined}
columnKeys={
isTablet
? [
FillsTableColumnKey.Time,
FillsTableColumnKey.TypeAmount,
FillsTableColumnKey.PriceFee,
]
: [
!showCurrentMarket && FillsTableColumnKey.Market,
FillsTableColumnKey.Time,
FillsTableColumnKey.Type,
FillsTableColumnKey.Side,
FillsTableColumnKey.AmountTag,
FillsTableColumnKey.Price,
FillsTableColumnKey.TotalFee,
FillsTableColumnKey.Liquidity,
].filter(isTruthy)
}
columnWidths={{
[FillsTableColumnKey.TypeAmount]: '100%',
}}
/>
),
},
// TODO - TRCL-1693 - re-enable when funding payments are supported
// {
// value: InfoSection.Payments,
// label: stringGetter({ key: STRING_KEYS.PAYMENTS }),
// tag: shortenNumberForDisplay(
// showCurrentMarket ? numFundingPayments : numTotalFundingPayments
// ),
// content: (
// <FundingPaymentsTable currentMarket={showCurrentMarket ? currentMarket?.id : undefined} />
// ),
// },
];
return isTablet ? (
<MobileTabs defaultValue={InfoSection.Position} items={tabItems} withBorders={false} />
+8 -1
View File
@@ -1,8 +1,10 @@
import { createSelector } from 'reselect';
import { SubaccountPosition } from '@/constants/abacus';
import { OnboardingState, OnboardingSteps } from '@/constants/account';
import {
getExistingOpenPositions,
getOnboardingGuards,
getOnboardingState,
getSubaccountId,
@@ -58,8 +60,13 @@ export const calculateIsAccountViewOnly = createSelector(
);
/**
* @description calculate whether the subaccount has uncommitted positions
* @description calculate whether the subaccount has open positions
*/
export const calculateHasOpenPositions = createSelector(
[getExistingOpenPositions],
(openPositions?: SubaccountPosition[]) => (openPositions?.length || 0) > 0
);
export const calculateHasUncommittedOrders = createSelector(
[getUncommittedOrderClientIds],
(uncommittedOrderClientIds: number[]) => uncommittedOrderClientIds.length > 0
+5 -11
View File
@@ -2,7 +2,6 @@ import { OrderSide } from '@dydxprotocol/v4-client-js';
import { createSelector } from 'reselect';
import {
type AbacusOrderStatuses,
type SubaccountOrder,
type SubaccountFill,
type SubaccountFundingPayment,
@@ -44,6 +43,7 @@ export const getSubaccountBuyingPower = (state: RootState) => state.account.suba
export const getSubaccountEquity = (state: RootState) => state.account.subaccount?.equity;
export const getSubaccountHistoricalPnl = (state: RootState) => state.account?.historicalPnl;
/**
* @param state
* @returns list of a subaccount's open positions. Each item in the list is an open position in a different market.
@@ -258,14 +258,6 @@ export const getCurrentMarketFundingPayments = createSelector(
!currentMarketId ? [] : marketFundingPayments[currentMarketId]
);
/**
* @param state
* @returns boolean on whether an order status is considered open
*/
const isOpenOrderStatus = (status: AbacusOrderStatuses) => {
return status !== AbacusOrderStatus.filled && status !== AbacusOrderStatus.cancelled;
};
/**
* @param state
* @returns Total numbers of the subaccount's open positions, open orders and fills
@@ -274,7 +266,8 @@ export const getTradeInfoNumbers = createSelector(
[getExistingOpenPositions, getSubaccountOrders, getSubaccountFills, getSubaccountFundingPayments],
(positions, orders, fills, fundingPayments) => ({
numTotalPositions: positions?.length,
numTotalOpenOrders: orders?.filter((order) => isOpenOrderStatus(order.status)).length,
numTotalOpenOrders: orders?.filter((order) => order.status !== AbacusOrderStatus.cancelled)
.length,
numTotalFills: fills?.length,
numTotalFundingPayments: fundingPayments?.length,
})
@@ -288,7 +281,8 @@ export const getCurrentMarketTradeInfoNumbers = createSelector(
[getCurrentMarketOrders, getCurrentMarketFills, getCurrentMarketFundingPayments],
(marketOrders, marketFills, marketFundingPayments) => {
return {
numOpenOrders: marketOrders?.filter((order) => isOpenOrderStatus(order.status)).length,
numOpenOrders: marketOrders?.filter((order) => order.status !== AbacusOrderStatus.cancelled)
.length,
numFills: marketFills?.length,
numFundingPayments: marketFundingPayments?.length,
};
+1 -6
View File
@@ -26,12 +26,7 @@ export const calculateFundingRateHistory = createSelector(
return data.map(({ effectiveAtMilliseconds, rate }) => ({
time: effectiveAtMilliseconds,
fundingRate: rate,
direction:
rate === 0
? FundingDirection.None
: rate < 0
? FundingDirection.ToLong
: FundingDirection.ToShort,
direction: rate < 0 ? FundingDirection.ToLong : FundingDirection.ToShort,
}));
}
);
+5
View File
@@ -877,6 +877,11 @@ export const layoutMixins: Record<
}
`,
perspectiveArea: css`
perspective: 100rem;
transform-style: preserve-3d;
`,
absolute: css`
position: absolute;
top: 0;
+1
View File
@@ -63,6 +63,7 @@ Styled.AccountInfoSectionContainer = styled.div<{ showAccountInfo?: boolean }>`
${layoutMixins.column}
height: var(--account-info-section-height);
min-height: var(--account-info-section-height);
overflow-x: clip;
${({ showAccountInfo }) =>
!showAccountInfo &&
@@ -203,6 +203,7 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.Stack = styled.div`
${layoutMixins.stack}
${layoutMixins.perspectiveArea}
`;
Styled.CornerButton = styled(Button)`
@@ -292,14 +293,7 @@ Styled.TransferButtons = styled.div`
Styled.ConnectedAccountInfoContainer = styled.div<{ $showHeader?: boolean }>`
${layoutMixins.column}
@media ${breakpoints.notTablet} {
${layoutMixins.withOuterAndInnerBorders}
}
@media ${breakpoints.tablet} {
${layoutMixins.withInnerBorder}
}
${layoutMixins.withOuterAndInnerBorders}
${({ $showHeader }) =>
$showHeader &&
+3
View File
@@ -2,7 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { STRING_KEYS } from '@/constants/localization';
import { MARKETS_TO_DISPLAY } from '@/constants/markets';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { testFlags } from '@/lib/testFlags';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -32,6 +34,7 @@ export const ExchangeBillboards: React.FC<ExchangeBillboardsProps> = ({
Object.values(perpetualMarkets)
.filter(Boolean)
.filter(({ id }) => (testFlags.displayAllMarkets ? true : MARKETS_TO_DISPLAY.includes(id)))
.forEach(({ oraclePrice, perpetual }) => {
const { volume24H, trades24H, openInterest = 0 } = perpetual || {};
volume24HUSDC += volume24H ?? 0;
+14 -2
View File
@@ -29,6 +29,7 @@ export const MarketDetails: React.FC = () => {
if (!configs) return null;
const {
basePositionNotional,
tickSize,
stepSize,
initialMarginFraction,
@@ -113,11 +114,17 @@ export const MarketDetails: React.FC = () => {
),
},
{
key: 'initial-margin-fraction',
label: stringGetter({ key: STRING_KEYS.INITIAL_MARGIN_FRACTION }),
key: 'base-initial-margin-fraction',
label: stringGetter({ key: STRING_KEYS.BASE_INITIAL_MARGIN_FRACTION }),
tooltip: 'initial-margin-fraction',
value: <Output useGrouping value={initialMarginFraction} type={OutputType.SmallPercent} />,
},
{
key: 'base-position-notional',
label: stringGetter({ key: STRING_KEYS.BASE_POSITION_NOTIONAL }),
tooltip: 'base-position-notional',
value: <Output useGrouping value={basePositionNotional} type={OutputType.Number} />,
},
];
return (
@@ -206,8 +213,13 @@ Styled.MarketDetails = styled.div`
`;
Styled.Header = styled.header`
/* max-width: fit-content; */
${layoutMixins.column}
gap: 1.25rem;
position: sticky;
bottom: 3rem;
`;
Styled.WrapRow = styled.div`
+7 -1
View File
@@ -453,6 +453,13 @@ Styled.Actions = styled.footer`
> :last-child {
flex: 2;
}
position: sticky;
bottom: clamp(0.5rem, 7.5%, 2rem);
@media ${breakpoints.tablet} {
bottom: 0;
}
`;
Styled.Output = styled(Output)<{ sign: NumberSign; smallText?: boolean; margin?: string }>`
@@ -507,7 +514,6 @@ Styled.PositionInfo = styled.div`
Styled.DetachedSection = styled(DetachedSection)`
padding: 0 1.5rem;
position: relative;
`;
Styled.DetachedScrollableSection = styled(DetachedScrollableSection)`
@@ -1,20 +1,14 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import styled, { AnyStyledComponent, css, keyframes } from 'styled-components';
import { useSelector, shallowEqual } from 'react-redux';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import {
DepthChartDatum,
DepthChartPoint,
DepthChartSeries,
SERIES_KEY_FOR_ORDER_SIDE,
} from '@/constants/charts';
import { StringGetterFunction } from '@/constants/localization';
import { StringGetterFunction, STRING_KEYS } from '@/constants/localization';
import { useBreakpoints } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
import { MustBigNumber } from '@/lib/numbers';
import { getCurrentMarketConfig, getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents';
@@ -27,25 +21,24 @@ import {
darkTheme,
DataProvider,
EventEmitterProvider,
type EventHandlerParams,
} from '@visx/xychart';
import { LinearGradient } from '@visx/gradient';
import { curveStepAfter } from '@visx/curve';
import { Point } from '@visx/point';
import type { Point } from '@visx/point';
import Tooltip from '@/components/visx/XYChartTooltipWithBounds';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
import { Details } from '@/components/Details';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { OutputType } from '@/components/Output';
import { Output, OutputType } from '@/components/Output';
import { MustBigNumber } from '@/lib/numbers';
import { DepthChartTooltipContent } from './Tooltip';
import { OrderSide } from '@dydxprotocol/v4-client-js';
// @ts-ignore
const theme = buildChartTheme({
...darkTheme,
colors: ['var(--color-positive)', 'var(--color-negative)', 'var(--color-layer-6)'], // categorical colors, mapped to series via `dataKey`s
colors: ['var(--color-positive)', 'var(--color-negative)', 'white'], // categorical colors, mapped to series via `dataKey`s
});
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
@@ -65,6 +58,30 @@ const formatNumber = (n: number, selectedLocale: string, isCompact: boolean = n
: formattedNumber;
};
enum DepthChartSeries {
Asks = 'Asks',
Bids = 'Bids',
MidMarket = 'MidMarket',
}
type DepthChartDatum = {
size: number;
price: number;
depth: number;
seriesKey: DepthChartSeries;
};
const seriesKeyForOrderSide = {
[OrderSide.BUY]: DepthChartSeries.Bids,
[OrderSide.SELL]: DepthChartSeries.Asks,
};
type DepthChartPoint = {
side: OrderSide;
price: number;
size: number;
};
export const DepthChart = ({
onChartClick,
stringGetter,
@@ -79,6 +96,8 @@ export const DepthChart = ({
const { isMobile } = useBreakpoints();
// Chart data
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { stepSizeDecimals, tickSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
@@ -93,15 +112,43 @@ export const DepthChart = ({
midMarketPrice,
spread,
spreadPercent,
orderbook,
} = useOrderbookValuesForDepthChart();
} = useMemo(() => {
const bids = (orderbook?.bids?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
const asks = (orderbook?.asks?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
const lowestBid = bids[bids.length - 1];
const highestBid = bids[0];
const lowestAsk = asks[0];
const highestAsk = asks[asks.length - 1];
const midMarketPrice = orderbook?.midPrice;
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
const spreadPercent = orderbook?.spreadPercent;
return {
bids,
asks,
lowestBid,
highestBid,
lowestAsk,
highestAsk,
midMarketPrice,
spread,
spreadPercent,
};
}, [orderbook]);
// Chart state
const [isPointerPressed, setIsPointerPressed] = useState(false);
const [chartPointAtPointer, setChartPointAtPointer] = useState<DepthChartPoint>();
const isEditingOrder = Boolean(isPointerPressed && chartPointAtPointer);
const isEditingOrder = isPointerPressed && chartPointAtPointer;
const [zoomDomain, setZoomDomain] = useState<undefined | number>();
@@ -146,24 +193,12 @@ export const DepthChart = ({
}, [orderbook, zoomDomain]);
const getChartPoint = useCallback(
(point: Point | EventHandlerParams<object>) => {
let price, size;
if (point instanceof Point) {
const { x, y } = point as Point;
price = x;
size = y;
} else {
const { svgPoint: { x, y } = {} } = point as EventHandlerParams<object>;
price = x;
size = y;
}
return {
side: MustBigNumber(price).lt(midMarketPrice!) ? OrderSide.BUY : OrderSide.SELL,
({ x: price, y: size }: Point) =>
({
side: price < midMarketPrice! ? OrderSide.BUY : OrderSide.SELL,
price,
size,
} as DepthChartPoint;
},
} as DepthChartPoint),
[midMarketPrice]
);
@@ -221,7 +256,7 @@ export const DepthChart = ({
}}
onPointerUp={(point) => point && onChartClick?.(getChartPoint(point))}
onPointerMove={(point) => point && setChartPointAtPointer(getChartPoint(point))}
onPointerPressedChange={(isPointerPressed) => setIsPointerPressed(isPointerPressed)}
onPointerPressedChange={setIsPointerPressed}
>
<Axis
orientation="bottom"
@@ -328,7 +363,7 @@ export const DepthChart = ({
<Styled.XAxisLabelOutput
type={OutputType.Fiat}
value={
isEditingOrder && chartPointAtPointer
isEditingOrder
? chartPointAtPointer.price
: tooltipData!.nearestDatum?.datum.price
}
@@ -339,8 +374,8 @@ export const DepthChart = ({
[DepthChartSeries.Bids]: 'var(--color-positive)',
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
}[
isEditingOrder && chartPointAtPointer
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
]
}
@@ -354,7 +389,7 @@ export const DepthChart = ({
<Styled.YAxisLabelOutput
type={OutputType.Asset}
value={
isEditingOrder && chartPointAtPointer
isEditingOrder
? chartPointAtPointer.size
: tooltipData!.nearestDatum?.datum.depth
}
@@ -365,8 +400,8 @@ export const DepthChart = ({
[DepthChartSeries.Bids]: 'var(--color-positive)',
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
}[
isEditingOrder && chartPointAtPointer
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
]
}
@@ -375,22 +410,181 @@ export const DepthChart = ({
}
snapTooltipToDatumX={!isEditingOrder}
snapTooltipToDatumY={isEditingOrder ? false : isMobile}
renderTooltip={({ tooltipData, colorScale }) =>
chartPointAtPointer && (
<DepthChartTooltipContent
{...{
tooltipData,
colorScale,
isEditingOrder,
chartPointAtPointer,
stringGetter,
selectedLocale,
stepSizeDecimals,
tickSizeDecimals,
}}
/>
)
}
renderTooltip={({ tooltipData, colorScale }) => {
const { nearestDatum } = tooltipData || {};
if (!isEditingOrder && !nearestDatum?.datum) return null;
return (
<TooltipContent
accentColor={colorScale?.(
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
: nearestDatum.key
)}
>
<h4>
{isEditingOrder
? 'Release mouse to edit order'
: {
[DepthChartSeries.Bids]: 'Bids',
[DepthChartSeries.Asks]: 'Asks',
[DepthChartSeries.MidMarket]: 'Mid-Market',
}[nearestDatum.key]}
</h4>
<Details
layout="column"
items={
isEditingOrder
? [
{
key: 'side',
label: stringGetter({ key: STRING_KEYS.SIDE }),
value: (
<Output
type={OutputType.Text}
value={
{
[OrderSide.BUY]: stringGetter({
key: STRING_KEYS.BUY,
}),
[OrderSide.SELL]: stringGetter({
key: STRING_KEYS.SELL,
}),
}[chartPointAtPointer.side]
}
/>
),
},
{
key: 'limitPrice',
label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={chartPointAtPointer.price}
useGrouping={false}
/>
),
},
{
key: 'size',
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
value: (
<Output
type={OutputType.Asset}
value={chartPointAtPointer.size}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
]
: nearestDatum?.key === DepthChartSeries.MidMarket
? [
{
key: 'midMarketPrice',
// label: stringGetter({ key: STRING_KEYS.ORDERBOOK_MID_MARKET_PRICE }),
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={midMarketPrice}
useGrouping={false}
/>
),
},
{
key: 'spread',
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }),
value: (
<>
<Output
type={OutputType.Fiat}
value={spread}
fractionDigits={tickSizeDecimals}
useGrouping={false}
/>
<Output
type={OutputType.SmallPercent}
value={spreadPercent}
withParentheses
/>
</>
),
},
]
: [
{
key: 'price',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<>
{nearestDatum &&
{
[DepthChartSeries.Bids]: '≥',
[DepthChartSeries.Asks]: '≤',
}[nearestDatum.key]}
<Output
type={OutputType.Fiat}
value={nearestDatum?.datum.price}
useGrouping={false}
/>
</>
),
},
{
key: 'depth',
label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }),
value: (
<Output
type={OutputType.Asset}
value={nearestDatum?.datum.depth}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
{
key: 'cost',
label: stringGetter({ key: STRING_KEYS.TOTAL_COST }),
value: (
<Output
useGrouping
type={OutputType.Fiat}
value={nearestDatum?.datum.price * nearestDatum?.datum.depth}
/>
),
},
{
key: 'priceImpact',
label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }),
value: (
<Output
useGrouping
type={OutputType.Percent}
value={{
[DepthChartSeries.Asks]: () =>
MustBigNumber(nearestDatum.datum.price)
.minus(lowestAsk.price)
.div(nearestDatum.datum.price),
[DepthChartSeries.Bids]: () =>
MustBigNumber(highestBid.price)
.minus(nearestDatum.datum.price)
.div(highestBid.price),
}[nearestDatum.key]()}
/>
),
},
]
}
/>
</TooltipContent>
);
}}
/>
</XYChartWithPointerEvents>
</EventEmitterProvider>
-220
View File
@@ -1,220 +0,0 @@
import { useMemo } from 'react';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
import { shallowEqual, useSelector } from 'react-redux';
import type { Nullable } from '@/constants/abacus';
import {
DepthChartDatum,
DepthChartPoint,
DepthChartSeries,
SERIES_KEY_FOR_ORDER_SIDE,
} from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { Details } from '@/components/Details';
import { Output, OutputType } from '@/components/Output';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { MustBigNumber } from '@/lib/numbers';
type DepthChartTooltipProps = {
chartPointAtPointer: DepthChartPoint;
isEditingOrder?: boolean;
stepSizeDecimals: Nullable<number>;
tickSizeDecimals: Nullable<number>;
} & Pick<RenderTooltipParams<DepthChartDatum>, 'colorScale' | 'tooltipData'>;
export const DepthChartTooltipContent = ({
chartPointAtPointer,
colorScale,
isEditingOrder,
stepSizeDecimals,
tickSizeDecimals,
tooltipData,
}: DepthChartTooltipProps) => {
const { nearestDatum } = tooltipData || {};
const stringGetter = useStringGetter();
const { spread, spreadPercent, midMarketPrice } = useOrderbookValuesForDepthChart();
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const priceImpact = useMemo(() => {
if (nearestDatum) {
const depthChartSeries = nearestDatum.key as DepthChartSeries;
return {
[DepthChartSeries.Bids]: MustBigNumber(nearestDatum?.datum.price)
.minus(chartPointAtPointer.price)
.div(nearestDatum?.datum.price),
[DepthChartSeries.Asks]: MustBigNumber(chartPointAtPointer.price)
.minus(nearestDatum?.datum.price)
.div(chartPointAtPointer.price),
[DepthChartSeries.MidMarket]: undefined,
}[depthChartSeries];
}
return undefined;
}, [nearestDatum, chartPointAtPointer.price]);
if (!isEditingOrder && !nearestDatum?.datum) return null;
return (
<TooltipContent
accentColor={
nearestDatum?.key &&
colorScale?.(
isEditingOrder ? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side] : nearestDatum.key
)
}
>
<h4>
{isEditingOrder
? stringGetter({ key: STRING_KEYS.RELEASE_TO_EDIT })
: nearestDatum &&
{
[DepthChartSeries.Bids]: stringGetter({ key: STRING_KEYS.BIDS }),
[DepthChartSeries.Asks]: stringGetter({ key: STRING_KEYS.ASKS }),
[DepthChartSeries.MidMarket]: stringGetter({ key: STRING_KEYS.MID_MARKET }),
}[nearestDatum.key]}
</h4>
<Details
layout="column"
items={
isEditingOrder
? [
{
key: 'side',
label: stringGetter({ key: STRING_KEYS.SIDE }),
value: (
<Output
type={OutputType.Text}
value={
{
[OrderSide.BUY]: stringGetter({
key: STRING_KEYS.BUY,
}),
[OrderSide.SELL]: stringGetter({
key: STRING_KEYS.SELL,
}),
}[chartPointAtPointer.side]
}
/>
),
},
{
key: 'limitPrice',
label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={chartPointAtPointer.price}
useGrouping={false}
/>
),
},
{
key: 'size',
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
value: (
<Output
type={OutputType.Asset}
value={chartPointAtPointer.size}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
]
: nearestDatum?.key === DepthChartSeries.MidMarket
? [
{
key: 'midMarketPrice',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<Output type={OutputType.Fiat} value={midMarketPrice} useGrouping={false} />
),
},
{
key: 'spread',
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }),
value: (
<>
<Output
type={OutputType.Fiat}
value={spread}
fractionDigits={tickSizeDecimals}
useGrouping={false}
/>
<Output
type={OutputType.SmallPercent}
value={spreadPercent}
withParentheses
/>
</>
),
},
]
: [
{
key: 'price',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<>
{nearestDatum &&
{
[DepthChartSeries.Bids]: '≥',
[DepthChartSeries.Asks]: '≤',
}[nearestDatum.key]}
<Output
type={OutputType.Fiat}
value={nearestDatum?.datum.price}
useGrouping={false}
/>
</>
),
},
{
key: 'depth',
label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }),
value: (
<Output
type={OutputType.Asset}
value={nearestDatum?.datum.depth}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
{
key: 'cost',
label: stringGetter({ key: STRING_KEYS.TOTAL_COST }),
value: (
<Output
useGrouping
type={OutputType.Fiat}
value={
nearestDatum
? nearestDatum?.datum.price * nearestDatum?.datum.depth
: undefined
}
/>
),
},
{
key: 'priceImpact',
label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }),
value: <Output useGrouping type={OutputType.Percent} value={priceImpact} />,
},
]
}
/>
</TooltipContent>
);
};
@@ -3,27 +3,39 @@ import { shallowEqual, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent, css } from 'styled-components';
import { curveMonotoneX, curveStepAfter } from '@visx/curve';
import { ButtonSize } from '@/constants/buttons';
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { FundingDirection } from '@/constants/markets';
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
import { FundingDirection } from '@/constants/markets';
import { breakpoints } from '@/styles';
import { Output, OutputType } from '@/components/Output';
import { Details, DetailsItem } from '@/components/Details';
import { Output, OutputType, ShowSign } from '@/components/Output';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { ToggleGroup } from '@/components/ToggleGroup';
import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
import { TooltipContent } from '@/components/visx/TooltipContent';
import type { TooltipContextType } from '@visx/xychart';
import { calculateFundingRateHistory } from '@/state/perpetualsCalculators';
import { MustBigNumber } from '@/lib/numbers';
import { FundingChartTooltipContent } from './Tooltip';
enum FundingRateResolution {
OneHour = 'OneHour',
EightHour = 'EightHour',
Annualized = 'Annualized',
}
type FundingChartDatum = {
time: number;
fundingRate: number;
direction: FundingDirection;
};
const FUNDING_RATE_TIME_RESOLUTION = 60 * 60 * 1000; // 1 hour
@@ -104,20 +116,114 @@ export const FundingChart = ({ selectedLocale }: ElementProps) => {
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
[FundingDirection.None]: 'var(--color-layer-6)',
}[tooltipDatum.direction]
}
/>
);
}}
renderTooltip={({ tooltipData }) => (
<FundingChartTooltipContent
fundingRateView={fundingRateView}
tooltipData={tooltipData}
latestDatum={latestDatum}
/>
)}
onTooltipContext={(tooltipContext) => setTooltipContext(tooltipContext)}
renderTooltip={({ tooltipData }) => {
const { nearestDatum } = tooltipData || {};
const tooltipDatum = nearestDatum?.datum ?? latestDatum;
const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
return (
<TooltipContent
accentColor={
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
}[tooltipDatum.direction]
}
>
<h4>
{isShowingCurrentFundingRate
? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE })
: stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })}
</h4>
<Details
layout="column"
items={
[
{
key: 'direction',
label: stringGetter({ key: STRING_KEYS.DIRECTION }),
value: (
<Output
type={OutputType.Text}
value={
{
[FundingDirection.ToLong]: `${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})} ${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})}`,
[FundingDirection.ToShort]: `${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})} ${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})}`,
}[tooltipDatum.direction]
}
/>
),
},
{
key: 'fundingRate1h',
label: stringGetter({ key: STRING_KEYS.RATE_1H }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate}
showSign={ShowSign.Both}
/>
),
},
{
key: 'fundingRate8h',
label: stringGetter({ key: STRING_KEYS.RATE_8H }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate * 8}
// value={
// Math.sign(tooltipDatum.fundingRate) *
// ((Math.abs(tooltipDatum.fundingRate) + 1) ** 8 - 1)
// }
showSign={ShowSign.Both}
/>
),
},
{
key: 'fundingRateAnnualized',
label: stringGetter({ key: STRING_KEYS.ANNUALIZED }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate * (24 * 365)}
// value={
// Math.sign(tooltipDatum.fundingRate) *
// ((Math.abs(tooltipDatum.fundingRate) + 1) ** (24 * 365) - 1)
// }
showSign={ShowSign.Both}
/>
),
},
{
key: 'time',
label: isShowingCurrentFundingRate
? 'Time Remaining'
: stringGetter({ key: STRING_KEYS.TIME }),
value: <Output type={OutputType.DateTime} value={tooltipDatum.time} />,
},
].filter(Boolean) as Array<DetailsItem>
}
/>
</TooltipContent>
);
}}
onTooltipContext={setTooltipContext}
minZoomDomain={FUNDING_RATE_TIME_RESOLUTION * 4}
numGridLines={1}
slotEmpty={<LoadingSpace id="funding-chart-loading" />}
@@ -184,7 +290,6 @@ Styled.FundingRateToggle = styled.div`
Styled.CurrentFundingRate = styled.div<{ isShowing?: boolean }>`
place-self: start center;
padding: clamp(1.5rem, 9rem - 15%, 4rem);
pointer-events: none;
font: var(--font-large-book);
-107
View File
@@ -1,107 +0,0 @@
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { FundingDirection } from '@/constants/markets';
import { useStringGetter } from '@/hooks';
import { Details, DetailsItem } from '@/components/Details';
import { Output, OutputType, ShowSign } from '@/components/Output';
type FundingChartTooltipProps = {
fundingRateView: FundingRateResolution;
latestDatum: FundingChartDatum;
} & Pick<RenderTooltipParams<FundingChartDatum>, 'tooltipData'>;
export const FundingChartTooltipContent = ({
fundingRateView,
latestDatum,
tooltipData,
}: FundingChartTooltipProps) => {
const { nearestDatum } = tooltipData || {};
const stringGetter = useStringGetter();
const tooltipDatum = nearestDatum?.datum ?? latestDatum;
const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
return (
<TooltipContent
accentColor={
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
[FundingDirection.None]: 'var(--color-layer-6)',
}[tooltipDatum.direction]
}
>
<h4>
{isShowingCurrentFundingRate
? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE })
: stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })}
</h4>
<Details
layout="column"
items={
[
{
key: 'direction',
label: stringGetter({ key: STRING_KEYS.DIRECTION }),
value: (
<Output
type={OutputType.Text}
value={
{
[FundingDirection.ToLong]: `${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})} → ${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})}`,
[FundingDirection.ToShort]: `${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})} → ${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})}`,
[FundingDirection.None]: undefined,
}[tooltipDatum.direction]
}
/>
),
},
{
key: 'fundingRate',
label: stringGetter({
key: {
[FundingRateResolution.OneHour]: STRING_KEYS.RATE_1H,
[FundingRateResolution.EightHour]: STRING_KEYS.RATE_8H,
[FundingRateResolution.Annualized]: STRING_KEYS.ANNUALIZED,
}[fundingRateView],
}),
value: (
<Output
type={OutputType.SmallPercent}
value={
{
[FundingRateResolution.OneHour]: tooltipDatum.fundingRate,
[FundingRateResolution.EightHour]: tooltipDatum.fundingRate * 8,
[FundingRateResolution.Annualized]: tooltipDatum.fundingRate * (24 * 365),
}[fundingRateView]
}
showSign={ShowSign.Both}
/>
),
},
{
key: 'time',
label: isShowingCurrentFundingRate
? 'Time Remaining'
: stringGetter({ key: STRING_KEYS.TIME }),
value: <Output type={OutputType.DateTime} value={tooltipDatum.time} />,
},
].filter(Boolean) as Array<DetailsItem>
}
/>
</TooltipContent>
);
};
+1
View File
@@ -82,6 +82,7 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.PriceChart = styled.div<{ isChartReady?: boolean }>`
${layoutMixins.stack}
${layoutMixins.perspectiveArea}
height: 100%;
@@ -94,7 +94,7 @@ export const FillDetailsDialog = ({ fillId, setIsOpen }: ElementProps) => {
return (
<DetailsDialog
slotIcon={<Styled.AssetIcon symbol={asset?.id} />}
slotIcon={<Styled.AssetIcon symbol={asset.id} />}
title={resources.typeStringKey && stringGetter({ key: resources.typeStringKey })}
items={detailItems}
setIsOpen={setIsOpen}
@@ -194,7 +194,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
return (
<DetailsDialog
slotIcon={<Styled.AssetIcon symbol={asset?.id} />}
slotIcon={<Styled.AssetIcon symbol={asset.id} />}
title={!resources.typeStringKey ? '' : stringGetter({ key: resources.typeStringKey })}
slotFooter={
isAccountViewOnly ? null : isOrderStatusClearable(status) ? (
+1 -5
View File
@@ -8,7 +8,6 @@ import { ComboboxDialogMenu } from '@/components/ComboboxDialogMenu';
import { Icon, IconName } from '@/components/Icon';
import { isTruthy } from '@/lib/isTruthy';
import { breakpoints } from '@/styles';
type ElementProps = {
setIsOpen: (open: boolean) => void;
@@ -73,11 +72,8 @@ export const HelpDialog = ({ setIsOpen }: ElementProps) => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.ComboboxDialogMenu = styled(ComboboxDialogMenu)`
--dialog-width: var(--dialog-small-width);
--dialog-content-paddingTop: 1rem;
--dialog-content-paddingBottom: 1rem;
--comboxDialogMenu-item-gap: 1rem;
@media ${breakpoints.notMobile} {
--dialog-width: var(--dialog-small-width);
}
`;
@@ -72,7 +72,6 @@ export const GenerateKeys = ({
if (message) {
log('GenerateKeys/switchNetwork', error, { walletErrorType });
setError(message);
throw error;
}
}
};
@@ -18,7 +18,11 @@ type ElementProps = {
onSelectChain: (chain: string) => void;
};
export const ChainSelectMenu = ({ label, selectedChain, onSelectChain }: ElementProps) => {
export const ChainSelectMenu = ({
label,
selectedChain,
onSelectChain,
}: ElementProps) => {
const stringGetter = useStringGetter();
const { type, depositOptions, withdrawalOptions, resources } =
useSelector(getTransferInputs, shallowEqual) || {};
@@ -34,7 +38,7 @@ export const ChainSelectMenu = ({ label, selectedChain, onSelectChain }: Element
slotBefore: <Styled.Img src={chain.iconUrl} alt="" />,
}));
const selectedOption = chains.find((item) => item.type === selectedChain);
const selectedOption = chains.find((item) => item.type === selectedChain);
return (
<SearchSelectMenu
@@ -77,4 +81,4 @@ Styled.ChainRow = styled.div`
gap: 0.5rem;
color: var(--color-text-2);
font: var(--font-base-book);
`;
`;
@@ -10,14 +10,14 @@ import { TransferInputField, TransferInputTokenResource, TransferType } from '@/
import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP, isMainnet } from '@/constants/networks';
import { MAX_CCTP_TRANSFER_AMOUNT, MAX_PRICE_IMPACT, NumberSign } from '@/constants/numbers';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { NotificationStatus } from '@/constants/notifications';
import { NumberSign } from '@/constants/numbers';
import type { EvmAddress } from '@/constants/wallets';
import { useAccounts, useDebounce, useStringGetter, useSelectedNetwork } from '@/hooks';
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins';
@@ -121,7 +121,11 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
});
return () => {
abacusStateManager.resetInputState();
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
};
}, []);
@@ -232,10 +236,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
throw new Error('Missing request payload');
}
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
throw new Error('Noble RPC endpoint unaccessible');
}
setIsLoading(true);
await validateTokenApproval();
@@ -253,9 +253,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
if (txHash) {
addTransferNotification({
txHash: txHash,
toChainId: !isCctp
? ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
: getNobleChainId(),
toChainId: !isCctp ? ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId : getNobleChainId(),
fromChainId: chainIdStr || undefined,
toAmount: summary?.usdcSize || undefined,
triggeredAt: Date.now(),
@@ -326,21 +324,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
return stringGetter({ key: STRING_KEYS.DEPOSIT_MORE_THAN_BALANCE });
}
if (isCctp) {
if (MustBigNumber(debouncedAmountBN).gte(MAX_CCTP_TRANSFER_AMOUNT)) {
return stringGetter({
key: STRING_KEYS.MAX_CCTP_TRANSFER_LIMIT_EXCEEDED,
params: {
MAX_CCTP_TRANSFER_AMOUNT: MAX_CCTP_TRANSFER_AMOUNT,
},
});
}
}
if (isMainnet && MustBigNumber(summary?.aggregatePriceImpact).gte(MAX_PRICE_IMPACT)) {
return stringGetter({ key: STRING_KEYS.PRICE_IMPACT_TOO_HIGH });
}
return undefined;
}, [
error,
@@ -351,7 +334,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
fromAmount,
sourceToken,
stringGetter,
summary,
]);
const isDisabled =
@@ -365,8 +347,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
return <LoadingSpace id="DepositForm" />;
}
const [requireUserActionInWallet, setRequireUserActionInWallet] = useState(false);
return (
<Styled.Form onSubmit={onSubmit}>
<ChainSelectMenu selectedChain={chainIdStr || undefined} onSelectChain={onSelectChain} />
@@ -385,11 +365,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
/>
</Styled.WithDetailsReceipt>
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
{requireUserActionInWallet && (
<AlertMessage type={AlertType.Warning}>
{stringGetter({ key: STRING_KEYS.CHECK_WALLET_FOR_REQUEST })}
</AlertMessage>
)}
<Styled.Footer>
<DepositButtonAndReceipt
isDisabled={isDisabled}
@@ -398,8 +374,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
setSlippage={onSetSlippage}
slippage={slippage}
sourceToken={sourceToken || undefined}
setRequireUserActionInWallet={setRequireUserActionInWallet}
setError={setError}
/>
</Styled.Footer>
</Styled.Form>
@@ -1,17 +1,22 @@
import { type Dispatch, type SetStateAction, useState, type ReactNode, useEffect } from 'react';
import { type Dispatch, type SetStateAction, useState, type ReactNode } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import type { RouteData } from '@0xsquid/sdk';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
import {
ButtonAction,
ButtonShape,
ButtonSize,
ButtonState,
ButtonType,
} from '@/constants/buttons';
import { TransferInputTokenResource } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { NumberSign, TOKEN_DECIMALS } from '@/constants/numbers';
import { NumberSign } from '@/constants/numbers';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { useStringGetter } from '@/hooks';
import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -29,7 +34,6 @@ import { calculateCanAccountTrade } from '@/state/accountCalculators';
import { getSubaccountBuyingPower, getSubaccountEquity } from '@/state/accountSelectors';
import { getTransferInputs } from '@/state/inputsSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { MustBigNumber } from '@/lib/numbers';
import { SlippageEditor } from '../SlippageEditor';
@@ -39,8 +43,7 @@ type ElementProps = {
isLoading?: boolean;
chainId?: string | number;
setError?: Dispatch<SetStateAction<Error | null>>;
setRequireUserActionInWallet: (val: boolean) => void;
setError?: Dispatch<SetStateAction<Error | undefined>>;
slippage: number;
slotError?: ReactNode;
setSlippage: (slippage: number) => void;
@@ -58,7 +61,6 @@ export const DepositButtonAndReceipt = ({
isDisabled,
isLoading,
slotError,
setRequireUserActionInWallet,
}: ElementProps) => {
const [showFeeBreakdown, setShowFeeBreakdown] = useState(false);
const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
@@ -66,24 +68,6 @@ export const DepositButtonAndReceipt = ({
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { connectWallet, isConnectedWagmi } = useWalletConnection();
const connectWagmi = async () => {
try {
setRequireUserActionInWallet(false);
await connectWallet();
setRequireUserActionInWallet(false);
} catch (e) {
setRequireUserActionInWallet(true);
}
};
useEffect(() => {
if (!isConnectedWagmi && canAccountTrade) {
connectWagmi();
}
}, [isConnectedWagmi, canAccountTrade]);
const {
matchNetwork: switchNetwork,
isSwitchingNetwork,
@@ -99,8 +83,7 @@ export const DepositButtonAndReceipt = ({
const { current: buyingPower, postOrder: newBuyingPower } =
useSelector(getSubaccountBuyingPower, shallowEqual) || {};
const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const { usdcLabel } = useTokenConfigs();
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const feeSubitems: DetailsItem[] = [];
@@ -115,7 +98,7 @@ export const DepositButtonAndReceipt = ({
if (typeof summary?.bridgeFee === 'number') {
feeSubitems.push({
key: 'bridge-fees',
label: <span>{stringGetter({ key: STRING_KEYS.BRIDGE_FEE })}</span>,
label: <span>Bridge Fee</span>,
value: <Output type={OutputType.Fiat} value={summary?.bridgeFee} />,
});
}
@@ -129,59 +112,11 @@ export const DepositButtonAndReceipt = ({
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
const submitButtonReceipt = [
{
key: 'expected-deposit-amount',
label: (
<span>
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
</span>
),
value: (
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />
),
subitems: [
{
key: 'minimum-deposit-amount',
label: (
<span>
{stringGetter({ key: STRING_KEYS.MINIMUM_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
</span>
),
value: (
<Output
type={OutputType.Fiat}
fractionDigits={TOKEN_DECIMALS}
value={summary?.toAmountMin}
/>
),
tooltip: 'minimum-deposit-amount',
},
],
},
{
key: 'exchange-rate',
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
value:
typeof summary?.exchangeRate === 'number' ? (
<Styled.ExchangeRate>
<Output
type={OutputType.Asset}
value={1}
fractionDigits={0}
tag={sourceToken?.symbol}
/>
=
<Output type={OutputType.Asset} value={summary?.exchangeRate} tag={usdcLabel} />
</Styled.ExchangeRate>
) : (
<Output type={OutputType.Asset} />
),
},
{
key: 'equity',
label: (
<span>
{stringGetter({ key: STRING_KEYS.EQUITY })} <Tag>{usdcLabel}</Tag>
{stringGetter({ key: STRING_KEYS.EQUITY })} <Tag>USDC</Tag>
</span>
),
value: (
@@ -198,7 +133,7 @@ export const DepositButtonAndReceipt = ({
key: 'buying-power',
label: (
<span>
{stringGetter({ key: STRING_KEYS.BUYING_POWER })} <Tag>{usdcLabel}</Tag>
{stringGetter({ key: STRING_KEYS.BUYING_POWER })} <Tag>USDC</Tag>
</span>
),
value: (
@@ -211,7 +146,18 @@ export const DepositButtonAndReceipt = ({
/>
),
},
!isCctp && {
{
key: 'exchange-rate',
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
value: typeof summary?.exchangeRate === 'number' && (
<Styled.ExchangeRate>
<Output type={OutputType.Asset} value={1} fractionDigits={0} tag={sourceToken?.symbol} />
=
<Output type={OutputType.Asset} value={summary?.exchangeRate} tag="USDC" />
</Styled.ExchangeRate>
),
},
{
key: 'total-fees',
label: <span>{stringGetter({ key: STRING_KEYS.TOTAL_FEES })}</span>,
value: <Output type={OutputType.Fiat} value={totalFees} />,
@@ -219,7 +165,7 @@ export const DepositButtonAndReceipt = ({
},
{
key: 'slippage',
label: <span>{stringGetter({ key: STRING_KEYS.MAX_SLIPPAGE })}</span>,
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
value: (
<SlippageEditor
disabled
@@ -232,26 +178,22 @@ export const DepositButtonAndReceipt = ({
{
key: 'estimatedRouteDuration',
label: <span>{stringGetter({ key: STRING_KEYS.ESTIMATED_TIME })}</span>,
value: (
value: typeof summary?.estimatedRouteDuration === 'number' && (
<Output
type={OutputType.Text}
value={
typeof summary?.estimatedRouteDuration === 'number'
? stringGetter({
key: STRING_KEYS.X_MINUTES_LOWERCASED,
params: {
X:
summary?.estimatedRouteDuration < 60
? '< 1'
: Math.round(summary?.estimatedRouteDuration / 60),
},
})
: null
}
value={stringGetter({
key: STRING_KEYS.X_MINUTES_LOWERCASED,
params: {
X:
summary?.estimatedRouteDuration < 60
? '< 1'
: Math.round(summary?.estimatedRouteDuration / 60),
},
})}
/>
),
},
].filter(isTruthy);
];
const isFormValid = !isDisabled && !isEditingSlippage;
@@ -279,8 +221,6 @@ export const DepositButtonAndReceipt = ({
>
{!canAccountTrade ? (
<OnboardingTriggerButton size={ButtonSize.Base} />
) : !isConnectedWagmi ? (
<Button action={ButtonAction.Primary} onClick={connectWallet} state={{ isLoading: true }} />
) : !isMatchingNetwork ? (
<Button
action={ButtonAction.Primary}
@@ -34,7 +34,6 @@ export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps)
selectedToken && onSelectToken(selectedToken);
},
slotBefore: <Styled.Img src={token.iconUrl} alt="" />,
tag: resources?.tokenResources?.get(token.type)?.symbol,
}));
return (
@@ -58,6 +57,7 @@ export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps)
<Tag>{type === TransferType.deposit ? 'USDC' : selectedToken?.symbol}</Tag>
</>
),
tooltip: 'swap',
},
]}
>
@@ -9,9 +9,9 @@ import { TransferInputField, TransferInputTokenResource, TransferType } from '@/
import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP, isMainnet } from '@/constants/networks';
import { TransferNotificationTypes } from '@/constants/notifications';
import { MAX_CCTP_TRANSFER_AMOUNT, MAX_PRICE_IMPACT, NumberSign } from '@/constants/numbers';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { NotificationStatus } from '@/constants/notifications';
import { NumberSign } from '@/constants/numbers';
import {
useAccounts,
@@ -45,7 +45,6 @@ import { getTransferInputs } from '@/state/inputsSelectors';
import abacusStateManager from '@/lib/abacus';
import { MustBigNumber } from '@/lib/numbers';
import { getNobleChainId } from '@/lib/squid';
import { TokenSelectMenu } from './TokenSelectMenu';
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
@@ -67,8 +66,7 @@ export const WithdrawForm = () => {
resources,
errors: routeErrors,
errorMessage: routeErrorMessage,
isCctp,
summary,
isCctp
} = useSelector(getTransferInputs, shallowEqual) || {};
// User input
@@ -76,6 +74,7 @@ export const WithdrawForm = () => {
const [slippage, setSlippage] = useState(isCctp ? 0 : 0.01); // 0.1% slippage
const debouncedAmount = useDebounce<string>(withdrawAmount, 500);
const isValidAddress = toAddress && isAddress(toAddress);
const toToken = useMemo(
@@ -101,7 +100,11 @@ export const WithdrawForm = () => {
});
return () => {
abacusStateManager.resetInputState();
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
};
}, []);
@@ -144,10 +147,6 @@ export const WithdrawForm = () => {
throw new Error('Invalid request payload');
}
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
throw new Error('Noble RPC endpoint unaccessible');
}
setIsLoading(true);
setError(undefined);
@@ -168,22 +167,16 @@ export const WithdrawForm = () => {
})
);
} else {
const txHash = await sendSquidWithdraw(
debouncedAmountBN.toNumber(),
requestPayload.data,
isCctp
);
if (txHash) {
const txHash = await sendSquidWithdraw(debouncedAmountBN.toNumber(), requestPayload.data);
if (txHash?.hash) {
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
addTransferNotification({
txHash: txHash,
type: TransferNotificationTypes.Withdrawal,
fromChainId: !isCctp
? ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
: getNobleChainId(),
txHash: hash,
fromChainId: ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId,
toChainId: chainIdStr || undefined,
toAmount: debouncedAmountBN.toNumber(),
triggeredAt: Date.now(),
isCctp,
notificationStatus: NotificationStatus.Triggered,
});
abacusStateManager.clearTransferInputValues();
setWithdrawAmount('');
@@ -289,7 +282,10 @@ export const WithdrawForm = () => {
const errorMessage = useMemo(() => {
if (error) {
return error;
return stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: { ERROR_MESSAGE: error },
});
}
if (routeErrors) {
@@ -320,21 +316,6 @@ export const WithdrawForm = () => {
return stringGetter({ key: STRING_KEYS.WITHDRAW_MORE_THAN_FREE });
}
if (isCctp) {
if (MustBigNumber(debouncedAmountBN).gte(MAX_CCTP_TRANSFER_AMOUNT)) {
return stringGetter({
key: STRING_KEYS.MAX_CCTP_TRANSFER_LIMIT_EXCEEDED,
params: {
MAX_CCTP_TRANSFER_AMOUNT: MAX_CCTP_TRANSFER_AMOUNT,
},
});
}
}
if (isMainnet && MustBigNumber(summary?.aggregatePriceImpact).gte(MAX_PRICE_IMPACT)) {
return stringGetter({ key: STRING_KEYS.PRICE_IMPACT_TOO_HIGH });
}
return undefined;
}, [
error,
@@ -347,7 +328,6 @@ export const WithdrawForm = () => {
toAddress,
sanctionedAddresses,
stringGetter,
summary,
]);
const isDisabled =
@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent } from 'styled-components';
import { formatUnits } from 'viem';
@@ -6,11 +6,14 @@ import { formatUnits } from 'viem';
import { TransferInputTokenResource } from '@/constants/abacus';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { NumberSign, TOKEN_DECIMALS } from '@/constants/numbers';
import { NumberSign } from '@/constants/numbers';
import { formatSeconds } from '@/lib/timeUtils';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { useStringGetter } from '@/hooks';
import { useAccountBalance } from '@/hooks/useAccountBalance';
import { Button } from '@/components/Button';
@@ -57,7 +60,11 @@ export const WithdrawButtonAndReceipt = ({
const { leverage } = useSelector(getSubaccount, shallowEqual) || {};
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { usdcLabel } = useTokenConfigs();
const toAmount =
summary?.toAmount &&
withdrawToken?.decimals &&
formatUnits(BigInt(summary.toAmount), withdrawToken?.decimals);
const feeSubitems: DetailsItem[] = [];
@@ -82,7 +89,7 @@ export const WithdrawButtonAndReceipt = ({
const showSubitemsToggle = showFeeBreakdown
? stringGetter({ key: STRING_KEYS.HIDE_ALL_DETAILS })
: stringGetter({ key: STRING_KEYS.SHOW_ALL_DETAILS });
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
const submitButtonReceipt = [
@@ -93,72 +100,20 @@ export const WithdrawButtonAndReceipt = ({
subitems: feeSubitems,
},
{
key: 'exchange-rate',
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
value: withdrawToken && typeof summary?.exchangeRate === 'number' && (
<Styled.ExchangeRate>
<Output type={OutputType.Asset} value={1} fractionDigits={0} tag={usdcLabel} />
=
<Output
type={OutputType.Asset}
value={summary?.exchangeRate}
tag={withdrawToken?.symbol}
/>
</Styled.ExchangeRate>
),
},
{
key: 'estimated-route-duration',
label: <span>{stringGetter({ key: STRING_KEYS.ESTIMATED_TIME })}</span>,
value: typeof summary?.estimatedRouteDuration === 'number' && (
<Output
type={OutputType.Text}
value={stringGetter({
key: STRING_KEYS.X_MINUTES_LOWERCASED,
params: {
X:
summary?.estimatedRouteDuration < 60
? '< 1'
: Math.round(summary?.estimatedRouteDuration / 60),
},
})}
/>
),
},
{
key: 'expected-amount-received',
key: 'wallet',
label: (
<span>
{stringGetter({ key: STRING_KEYS.EXPECTED_AMOUNT_RECEIVED })}{' '}
{stringGetter({ key: STRING_KEYS.AMOUNT_RECEIVED })}{' '}
{withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
</span>
),
value: <Output type={OutputType.Asset} value={summary?.toAmount} fractionDigits={TOKEN_DECIMALS} />,
subitems: [
{
key: 'minimum-amount-received',
label: (
<span>
{stringGetter({ key: STRING_KEYS.MINIMUM_AMOUNT_RECEIVED })}{' '}
{withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
</span>
),
value: (
<Output type={OutputType.Asset} value={summary?.toAmountMin} fractionDigits={TOKEN_DECIMALS} />
),
tooltip: 'minimum-amount-received',
},
],
},
{
key: 'slippage',
label: <span>{stringGetter({ key: STRING_KEYS.MAX_SLIPPAGE })}</span>,
value: (
<SlippageEditor
disabled
slippage={slippage}
setIsEditing={setIsEditingSlipapge}
setSlippage={setSlippage}
<Styled.DiffOutput
type={OutputType.Asset}
value={'0'}
newValue={toAmount}
sign={NumberSign.Positive}
withDiff={Boolean(toAmount)}
/>
),
},
@@ -175,6 +130,51 @@ export const WithdrawButtonAndReceipt = ({
/>
),
},
{
key: 'exchange-rate',
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
value: withdrawToken && typeof summary?.exchangeRate === 'number' && (
<Styled.ExchangeRate>
<Output type={OutputType.Asset} value={1} fractionDigits={0} tag="USDC" />
=
<Output
type={OutputType.Asset}
value={summary?.exchangeRate}
tag={withdrawToken?.symbol}
/>
</Styled.ExchangeRate>
),
},
{
key: 'slippage',
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
value: (
<SlippageEditor
disabled
slippage={slippage}
setIsEditing={setIsEditingSlipapge}
setSlippage={setSlippage}
/>
),
},
{
key: 'estimatedRouteDuration',
label: <span>{stringGetter({ key: STRING_KEYS.ESTIMATED_TIME })}</span>,
value: typeof summary?.estimatedRouteDuration === 'number' && (
<Output
type={OutputType.Text}
value={stringGetter({
key: STRING_KEYS.X_MINUTES_LOWERCASED,
params: {
X:
summary?.estimatedRouteDuration < 60
? '< 1'
: Math.round(summary?.estimatedRouteDuration / 60),
},
})}
/>
),
},
];
const isFormValid = !isDisabled && !isEditingSlippage;
+7 -8
View File
@@ -1,10 +1,10 @@
import { useCallback, useMemo } from 'react';
import { type Dispatch, type SetStateAction, useCallback, useMemo } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { Root, Thumb, Track } from '@radix-ui/react-slider';
import _ from 'lodash';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { TradeInputField } from '@/constants/abacus';
import { type Nullable, TradeInputField } from '@/constants/abacus';
import { PositionSide } from '@/constants/trade';
import { MustBigNumber, type BigNumberish } from '@/lib/numbers';
@@ -12,11 +12,11 @@ import abacusStateManager from '@/lib/abacus';
type ElementProps = {
leverage?: BigNumberish | null;
leverageInputValue: string;
leverageInputValue: Nullable<number>;
maxLeverage: BigNumberish | null;
orderSide: OrderSide;
positionSide: PositionSide;
setLeverageInputValue: (value: string) => void;
setLeverageInputValue: Dispatch<SetStateAction<Nullable<number>>>;
};
type StyleProps = { className?: string };
@@ -33,7 +33,6 @@ export const LeverageSlider = ({
const leverageBN = MustBigNumber(leverage);
const maxLeverageBN = MustBigNumber(maxLeverage);
const leverageInputBN = MustBigNumber(leverageInputValue || leverage);
const leverageInputNumber = isNaN(leverageInputBN.toNumber()) ? 0 : leverageInputBN.toNumber();
const sliderConfig = useMemo(
() => ({
@@ -80,12 +79,12 @@ export const LeverageSlider = ({
);
const onSliderDrag = ([newLeverage]: number[]) => {
setLeverageInputValue(`${newLeverage}`);
setLeverageInputValue(newLeverage);
debouncedSetAbacusLeverage(newLeverage);
};
const onValueCommit = ([newLeverage]: number[]) => {
setLeverageInputValue(`${newLeverage}`);
setLeverageInputValue(newLeverage);
// Ensure Abacus is updated with the latest, committed value
debouncedSetAbacusLeverage.cancel();
@@ -103,7 +102,7 @@ export const LeverageSlider = ({
min={min}
max={max}
step={0.1}
value={[Math.min(Math.max(leverageInputNumber, min), max)]}
value={[Math.min(Math.max(leverageInputBN.toNumber(), min), max)]}
onValueChange={onSliderDrag}
onValueCommit={onValueCommit}
>
@@ -1,8 +1,9 @@
import type { Dispatch, SetStateAction } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import styled, { AnyStyledComponent } from 'styled-components';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { TradeInputField } from '@/constants/abacus';
import { TradeInputField, Nullable } from '@/constants/abacus';
import { ButtonShape } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { LEVERAGE_DECIMALS } from '@/constants/numbers';
@@ -61,7 +62,21 @@ export const MarketLeverageInput = ({
const leveragePosition = postOrderLeverage ? newPositionSide : currentPositionSide;
const getSignedLeverage = (newLeverage: string | number) => {
const onLeverageInput = ({ value, floatValue }: { value: string; floatValue?: number }) => {
const newLeverage = MustBigNumber(floatValue).toFixed();
if (value === '' || newLeverage === 'NaN' || !floatValue) {
setLeverageInputValue('');
abacusStateManager.setTradeValue({
value: null,
field: TradeInputField.leverage,
});
} else {
updateLeverage(floatValue);
}
};
const updateLeverage = (newLeverage: string | number) => {
const newLeverageBN = MustBigNumber(newLeverage);
const newLeverageSignedBN =
leveragePosition === PositionSide.Short ||
@@ -69,33 +84,10 @@ export const MarketLeverageInput = ({
? newLeverageBN.abs().negated()
: newLeverageBN.abs();
return newLeverageSignedBN.toFixed(LEVERAGE_DECIMALS);
};
const onLeverageInput = ({
floatValue,
formattedValue,
}: {
floatValue?: number;
formattedValue: string;
}) => {
setLeverageInputValue(formattedValue);
const newLeverage = MustBigNumber(floatValue).toFixed();
setLeverageInputValue(newLeverageSignedBN.toString());
abacusStateManager.setTradeValue({
value:
formattedValue === '' || newLeverage === 'NaN' ? null : getSignedLeverage(formattedValue),
field: TradeInputField.leverage,
});
};
const updateLeverage = (newLeverage: string | number) => {
const newLeverageSigned = getSignedLeverage(newLeverage);
setLeverageInputValue(newLeverageSigned);
abacusStateManager.setTradeValue({
value: newLeverageSigned,
value: newLeverageSignedBN.toFixed(LEVERAGE_DECIMALS),
field: TradeInputField.leverage,
});
};
@@ -138,7 +130,7 @@ export const MarketLeverageInput = ({
>
<Styled.LeverageSlider
leverage={currentLeverage}
leverageInputValue={getSignedLeverage(leverageInputValue)}
leverageInputValue={leverageInputValue}
maxLeverage={maxLeverage}
orderSide={orderSide}
positionSide={currentPositionSide}
@@ -77,7 +77,7 @@ export const TradeSizeInputs = () => {
floatValue?: number;
formattedValue: string;
}) => {
dispatch(setTradeFormInputs({ amountInput: formattedValue }));
dispatch(setTradeFormInputs({ amountInput: MustBigNumber(floatValue).toString() }));
const newAmount = MustBigNumber(floatValue).toFixed(decimals);
abacusStateManager.setTradeValue({
@@ -93,7 +93,7 @@ export const TradeSizeInputs = () => {
floatValue?: number;
formattedValue: string;
}) => {
dispatch(setTradeFormInputs({ usdAmountInput: formattedValue }));
dispatch(setTradeFormInputs({ usdAmountInput: MustBigNumber(floatValue).toString() }));
const newUsdcAmount = MustBigNumber(floatValue).toFixed(tickSizeDecimals || USD_DECIMALS);
abacusStateManager.setTradeValue({
+5 -1
View File
@@ -118,7 +118,11 @@ export const TransferForm = ({
onChangeAsset(selectedAsset);
return () => {
abacusStateManager.resetInputState();
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
};
}, []);
+4 -5
View File
@@ -10,9 +10,6 @@ import { STRING_KEYS, StringGetterFunction, TOOLTIP_STRING_KEYS } from '@/consta
import { isMainnet } from '@/constants/networks';
import { DydxChainAsset, wallets } from '@/constants/wallets';
import { layoutMixins } from '@/styles/layoutMixins';
import { headerMixins } from '@/styles/headerMixins';
import {
useAccounts,
useBreakpoints,
@@ -38,8 +35,10 @@ import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { layoutMixins } from '@/styles/layoutMixins';
import { headerMixins } from '@/styles/headerMixins';
import { MustBigNumber } from '@/lib/numbers';
import { testFlags } from '@/lib/testFlags';
export const AccountMenu = () => {
const stringGetter = useStringGetter();
@@ -179,7 +178,7 @@ export const AccountMenu = () => {
},
...(onboardingState === OnboardingState.AccountConnected && hdKey
? [
(!isMainnet || testFlags.showMobileSignInOption) && {
!isMainnet && {
value: 'MobileQrSignIn',
icon: <Icon iconName={IconName.Qr} />,
label: stringGetter({ key: STRING_KEYS.TITLE_SIGN_INTO_MOBILE }),
@@ -1,78 +0,0 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { Details } from '@/components/Details';
import { Notification, NotificationProps } from '@/components/Notification';
import { Output, OutputType } from '@/components/Output';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
data: {
BLOCK_REWARD_AMOUNT: string;
BLOCK_REWARD_HEIGHT: string;
BLOCK_REWARD_TIME_MILLISECONDS: string;
TOKEN_NAME: string;
};
};
export type BlockRewardNotificationProps = NotificationProps & ElementProps;
export const BlockRewardNotification = ({
isToast,
data,
notification,
}: BlockRewardNotificationProps) => {
const stringGetter = useStringGetter();
const { BLOCK_REWARD_AMOUNT, TOKEN_NAME } = data;
return (
<Styled.Notification
isToast={isToast}
notification={notification}
slotIcon={<Icon iconName={IconName.RewardStar} />}
slotTitle={stringGetter({ key: STRING_KEYS.TRADING_REWARD_RECEIVED })}
slotCustomContent={
<Styled.Details
items={[
{
key: 'block_reward',
label: stringGetter({ key: STRING_KEYS.BLOCK_REWARD }),
value: (
<Styled.Output
type={OutputType.Asset}
value={BLOCK_REWARD_AMOUNT}
tag={TOKEN_NAME}
/>
),
},
]}
/>
}
/>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Details = styled(Details)`
--details-item-height: 1.5rem;
dd {
color: var(--color-text-2);
}
`;
Styled.Notification = styled(Notification)`
background-image: url('/dots-background-2.svg');
background-size: cover;
`;
Styled.Output = styled(Output)`
&:before {
content: '+';
color: var(--color-positive);
margin-right: 0.5ch;
}
`;
@@ -12,11 +12,10 @@ import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
import { layoutMixins } from '@/styles/layoutMixins';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { TransferNotificationTypes } from '@/constants/notifications';
type ElementProps = {
status?: StatusResponse;
type: TransferNotificationTypes;
type: 'withdrawal' | 'deposit';
};
type StyleProps = {
@@ -47,13 +46,11 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
{
label: stringGetter({
key:
type === TransferNotificationTypes.Deposit
? STRING_KEYS.INITIATED_DEPOSIT
: STRING_KEYS.INITIATED_WITHDRAWAL,
type === 'deposit' ? STRING_KEYS.INITIATED_DEPOSIT : STRING_KEYS.INITIATED_WITHDRAWAL,
}),
step: TransferStatusStep.FromChain,
link:
type === TransferNotificationTypes.Deposit
link:
type === 'deposit'
? status?.fromChain?.transactionUrl
: routeStatus?.[0]?.chainId === dydxChainId && routeStatus[0].txHash
? `${mintscanTxUrl?.replace('{tx_hash}', routeStatus[0].txHash.replace('0x', ''))}`
@@ -66,20 +63,14 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
},
{
label: stringGetter({
key:
type === TransferNotificationTypes.Deposit
? STRING_KEYS.DEPOSIT_TO_CHAIN
: STRING_KEYS.WITHDRAW_TO_CHAIN,
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_TO_CHAIN : STRING_KEYS.WITHDRAW_TO_CHAIN,
params: {
CHAIN:
type === TransferNotificationTypes.Deposit
? 'dYdX'
: status?.toChain?.chainData?.chainName,
CHAIN: type === 'deposit' ? 'dYdX' : status?.toChain?.chainData?.chainName,
},
}),
step: TransferStatusStep.ToChain,
link:
type === TransferNotificationTypes.Withdrawal
type === 'withdrawal'
? status?.toChain?.transactionUrl
: currentStatus?.chainId === dydxChainId && currentStatus?.txHash
? `${mintscanTxUrl?.replace('{tx_hash}', currentStatus.txHash.replace('0x', ''))}`
@@ -5,7 +5,7 @@ import { useInterval, useStringGetter } from '@/hooks';
import { AlertType } from '@/constants/alerts';
import { STRING_KEYS } from '@/constants/localization';
import { TransferNotifcation, TransferNotificationTypes } from '@/constants/notifications';
import { TransferNotifcation } from '@/constants/notifications';
import { formatSeconds } from '@/lib/timeUtils';
@@ -22,7 +22,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { TransferStatusSteps } from './TransferStatusSteps';
type ElementProps = {
type: TransferNotificationTypes;
type: 'withdrawal' | 'deposit';
transfer: TransferNotifcation;
triggeredAt?: number;
};
@@ -55,7 +55,7 @@ export const TransferStatusNotification = ({
useInterval({ callback: updateSecondsLeft });
const inProgressStatusString =
type === TransferNotificationTypes.Deposit
type === 'deposit'
? secondsLeft > 0
? STRING_KEYS.DEPOSIT_STATUS
: STRING_KEYS.DEPOSIT_STATUS_SHORTLY
@@ -64,7 +64,7 @@ export const TransferStatusNotification = ({
: STRING_KEYS.WITHDRAW_STATUS_SHORTLY;
const statusString =
type === TransferNotificationTypes.Deposit
type === 'deposit'
? status?.squidTransactionStatus === 'success'
? STRING_KEYS.DEPOSIT_COMPLETE
: inProgressStatusString
+2
View File
@@ -36,6 +36,8 @@ import { openDialog } from '@/state/dialogs';
import { MustBigNumber } from '@/lib/numbers';
import { getHydratedTradingData } from '@/lib/orders';
import { tableMixins } from '@/styles/tableMixins';
import { breakpoints } from '@/styles';
const MOBILE_FILLS_PER_PAGE = 50;
+2 -3
View File
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
import { STRING_KEYS } from '@/constants/localization';
import { MarketFilters, type MarketData } from '@/constants/markets';
import { FUNDING_DECIMALS, LARGE_TOKEN_DECIMALS } from '@/constants/numbers';
import { LARGE_TOKEN_DECIMALS } from '@/constants/numbers';
import { AppRoute } from '@/constants/routes';
import { useBreakpoints, useStringGetter } from '@/hooks';
@@ -128,8 +128,7 @@ export const MarketsTable = ({ className }: { className?: string }) => {
label: stringGetter({ key: STRING_KEYS.FUNDING_RATE_1H_SHORT }),
renderCell: (row) => (
<Styled.Output
type={OutputType.Percent}
fractionDigits={FUNDING_DECIMALS}
type={OutputType.SmallPercent}
value={row.nextFundingRate}
isPositive={MustBigNumber(row.nextFundingRate).gt(0)}
isNegative={MustBigNumber(row.nextFundingRate).isNegative()}
+11 -2
View File
@@ -230,7 +230,14 @@ const getOrdersTableColumnDef = ({
label: `${stringGetter({ key: STRING_KEYS.STATUS })} / ${stringGetter({
key: STRING_KEYS.FILL,
})}`,
renderCell: ({ asset, createdAtMilliseconds, size, status, totalFilled, resources }) => {
renderCell: ({
asset,
createdAtMilliseconds,
size,
status,
totalFilled,
resources,
}) => {
const { statusIconColor, statusStringKey } = getStatusIconInfo({
status,
totalFilled,
@@ -338,7 +345,9 @@ export const OrdersTable = ({
if (hasUnseenOrderUpdates) dispatch(viewedOrders());
}, [hasUnseenOrderUpdates]);
const symbol = currentMarket ? allAssets[allPerpetualMarkets[currentMarket]?.assetId]?.id : null;
const symbol = currentMarket
? allAssets[allPerpetualMarkets[currentMarket]?.assetId]?.symbol
: null;
const ordersData = orders.map((order: SubaccountOrder) =>
getHydratedTradingData({
-1
View File
@@ -366,7 +366,6 @@ Styled.InlineRow = styled.div`
Styled.AssetIcon = styled(AssetIcon)`
${layoutMixins.inlineRow}
min-width: unset;
font-size: 2.25rem;
`;