Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
849bda8e8e | ||
|
|
57edb711d5 | ||
|
|
8235c64c93 | ||
|
|
750fb077f5 | ||
|
|
d781cb3199 | ||
|
|
f6bf635b0d | ||
|
|
56187f7a84 | ||
|
|
93c76f1c3f | ||
|
|
521b6d6135 | ||
|
|
94772bf4dc | ||
|
|
149f21e7e0 | ||
|
|
0a3ef3a7ab | ||
|
|
fef9935883 | ||
|
|
c7a5b706e1 | ||
|
|
f304bb069a | ||
|
|
ad6aa1af49 | ||
|
|
55b2c3fb4a | ||
|
|
83bcde00eb | ||
|
|
e8024b9337 | ||
|
|
8280e512bc | ||
|
|
b2234cb969 | ||
|
|
4bcb1f64ac | ||
|
|
4a64d59546 | ||
|
|
3fc43e8a12 | ||
|
|
c24a9229a6 | ||
|
|
90329d44d0 |
@@ -0,0 +1 @@
|
||||
if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })
|
||||
+6
-3
@@ -39,11 +39,13 @@
|
||||
"@cosmjs/proto-signing": "^0.31.0",
|
||||
"@cosmjs/stargate": "^0.31.0",
|
||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||
"@dydxprotocol/v4-abacus": "^1.1.32",
|
||||
"@dydxprotocol/v4-abacus": "^1.2.5",
|
||||
"@dydxprotocol/v4-client-js": "^1.0.11",
|
||||
"@dydxprotocol/v4-localization": "^1.1.5",
|
||||
"@dydxprotocol/v4-localization": "^1.1.11",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@privy-io/react-auth": "^1.53.1",
|
||||
"@privy-io/wagmi-connector": "^0.1.12",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
@@ -111,7 +113,7 @@
|
||||
"reselect": "^4.1.8",
|
||||
"styled-components": "^5.3.11",
|
||||
"use-latest": "^1.2.1",
|
||||
"viem": "^1.12.2",
|
||||
"viem": "^1.20.0",
|
||||
"wagmi": "^1.4.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -151,6 +153,7 @@
|
||||
"url-polyfill": "^1.1.12",
|
||||
"util": "^0.12.5",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-pwa": "^0.17.4",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vitest": "^0.32.2",
|
||||
"w3name": "^1.0.8",
|
||||
|
||||
Generated
+2846
-129
File diff suppressed because it is too large
Load Diff
+26
-26
@@ -39,7 +39,7 @@
|
||||
"environments": {
|
||||
"dydxprotocol-dev": {
|
||||
"name": "v4 Dev",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"dydxprotocol-dev-2": {
|
||||
"name": "v4 Dev 2",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -186,7 +186,7 @@
|
||||
},
|
||||
"dydxprotocol-dev-4": {
|
||||
"name": "v4 Dev 4",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -259,7 +259,7 @@
|
||||
},
|
||||
"dydxprotocol-dev-5": {
|
||||
"name": "v4 Dev 5",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -332,7 +332,7 @@
|
||||
},
|
||||
"dydxprotocol-staging": {
|
||||
"name": "v4 Staging",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -407,7 +407,7 @@
|
||||
},
|
||||
"dydxprotocol-staging-forced-update": {
|
||||
"name": "v4 Staging Forced Update",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -480,7 +480,7 @@
|
||||
},
|
||||
"dydxprotocol-staging-west": {
|
||||
"name": "v4 Staging West",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -555,7 +555,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet": {
|
||||
"name": "v4 Public Testnet",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -579,8 +579,8 @@
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
@@ -634,7 +634,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-dydx": {
|
||||
"name": "v4 Public Testnet/dYdX",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -710,7 +710,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-nodefleet": {
|
||||
"name": "v4 Public Testnet/nodefleet",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -734,8 +734,8 @@
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
@@ -786,7 +786,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-kingnodes": {
|
||||
"name": "v4 Public Testnet/KingNodes",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -810,8 +810,8 @@
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
@@ -862,7 +862,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-liquify": {
|
||||
"name": "v4 Public Testnet/Liquify",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -886,8 +886,8 @@
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
@@ -938,7 +938,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-polkachu": {
|
||||
"name": "v4 Public Testnet/Polkahcu",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -962,8 +962,8 @@
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
@@ -1005,7 +1005,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-bware": {
|
||||
"name": "v4 Public Testnet/BWare",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -1029,8 +1029,8 @@
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
<svg width="254" height="77" viewBox="0 0 254 77" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group 1258">
|
||||
<g id="Ellipse 9" opacity="0.4" filter="url(#filter0_f_20300_14972)">
|
||||
<ellipse cx="86.5425" cy="-3.91219" rx="116.542" ry="20.0878" fill="#7774FF"/>
|
||||
</g>
|
||||
<g id="Ellipse 10" opacity="0.3" filter="url(#filter1_f_20300_14972)">
|
||||
<circle cx="74.5734" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 12" opacity="0.3" filter="url(#filter2_f_20300_14972)">
|
||||
<circle cx="82.1105" cy="7.64179" r="1.00507" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 22" opacity="0.3" filter="url(#filter3_f_20300_14972)">
|
||||
<circle cx="116.785" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 11" opacity="0.3" filter="url(#filter4_f_20300_14972)">
|
||||
<circle cx="64.774" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 18" opacity="0.3" filter="url(#filter5_f_20300_14972)">
|
||||
<circle cx="96.4322" cy="3.87363" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 20" opacity="0.3" filter="url(#filter6_f_20300_14972)">
|
||||
<circle cx="76.0807" cy="14.9361" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 21" opacity="0.3" filter="url(#filter7_f_20300_14972)">
|
||||
<circle cx="104.725" cy="4.87754" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 23" opacity="0.3" filter="url(#filter8_f_20300_14972)">
|
||||
<circle cx="114.021" cy="7.13925" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 24" opacity="0.3" filter="url(#filter9_f_20300_14972)">
|
||||
<circle cx="109.75" cy="15.4361" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 25" opacity="0.3" filter="url(#filter10_f_20300_14972)">
|
||||
<circle cx="119.801" cy="12.6705" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 27" opacity="0.3" filter="url(#filter11_f_20300_14972)">
|
||||
<circle cx="94.0885" cy="11.0182" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 30" opacity="0.3" filter="url(#filter12_f_20300_14972)">
|
||||
<circle cx="154.475" cy="11.6666" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 29" opacity="0.3" filter="url(#filter13_f_20300_14972)">
|
||||
<circle cx="146.435" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 15" opacity="0.3" filter="url(#filter14_f_20300_14972)">
|
||||
<circle cx="24.0103" cy="4.44785" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 33" opacity="0.3" filter="url(#filter15_f_20300_14972)">
|
||||
<circle cx="-1.05899" cy="13.6783" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 16" opacity="0.3" filter="url(#filter16_f_20300_14972)">
|
||||
<circle cx="17.441" cy="16.4908" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 35" opacity="0.3" filter="url(#filter17_f_20300_14972)">
|
||||
<circle cx="36.0553" cy="6.63535" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 32" opacity="0.3" filter="url(#filter18_f_20300_14972)">
|
||||
<circle cx="10.8707" cy="14.2994" r="0.502536" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 13" opacity="0.3" filter="url(#filter19_f_20300_14972)">
|
||||
<circle cx="55.7274" cy="9.65615" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 19" opacity="0.3" filter="url(#filter20_f_20300_14972)">
|
||||
<circle cx="87.3886" cy="20.7108" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 26" opacity="0.3" filter="url(#filter21_f_20300_14972)">
|
||||
<circle cx="123.82" cy="15.1874" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 36" opacity="0.3" filter="url(#filter22_f_20300_14972)">
|
||||
<circle cx="134.876" cy="14.4335" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 28" opacity="0.3" filter="url(#filter23_f_20300_14972)">
|
||||
<circle cx="129.6" cy="6.38662" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 14" opacity="0.3" filter="url(#filter24_f_20300_14972)">
|
||||
<circle cx="41.6571" cy="14.9335" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 31" opacity="0.3" filter="url(#filter25_f_20300_14972)">
|
||||
<circle cx="4.21767" cy="17.9491" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 34" opacity="0.3" filter="url(#filter26_f_20300_14972)">
|
||||
<circle cx="12.2177" cy="6.88662" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
<g id="Ellipse 17" opacity="0.3" filter="url(#filter27_f_20300_14972)">
|
||||
<circle cx="50.2001" cy="3.6249" r="0.753803" fill="#9A9AFF"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_20300_14972" x="-94" y="-88" width="361.085" height="168.176" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="32" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_20300_14972" x="72.5633" y="2.61993" width="4.01991" height="4.02186" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_20300_14972" x="80.6029" y="6.13418" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_20300_14972" x="115.278" y="3.12246" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter4_f_20300_14972" x="63.2664" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter5_f_20300_14972" x="94.9246" y="2.36602" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter6_f_20300_14972" x="74.5731" y="13.4285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter7_f_20300_14972" x="103.218" y="3.36993" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter8_f_20300_14972" x="112.513" y="5.63165" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter9_f_20300_14972" x="108.242" y="13.9285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter10_f_20300_14972" x="118.294" y="11.1629" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter11_f_20300_14972" x="92.5809" y="9.51055" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter12_f_20300_14972" x="153.47" y="10.6615" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter13_f_20300_14972" x="144.928" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter14_f_20300_14972" x="22.5027" y="2.94024" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter15_f_20300_14972" x="-2.56659" y="12.1707" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter16_f_20300_14972" x="15.9334" y="14.9832" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter17_f_20300_14972" x="35.0502" y="5.63028" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter18_f_20300_14972" x="9.86563" y="13.2943" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter19_f_20300_14972" x="53.9686" y="7.89727" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter20_f_20300_14972" x="85.6297" y="18.952" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter21_f_20300_14972" x="122.061" y="13.4285" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter22_f_20300_14972" x="133.117" y="12.6746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter23_f_20300_14972" x="127.842" y="4.62774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter24_f_20300_14972" x="39.8982" y="13.1746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter25_f_20300_14972" x="2.4588" y="16.1902" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter26_f_20300_14972" x="10.4588" y="5.12774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
<filter id="filter27_f_20300_14972" x="48.4412" y="1.86602" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
+8
-1
@@ -2,6 +2,8 @@ import { lazy, Suspense } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||
import { WagmiConfig } from 'wagmi';
|
||||
import { PrivyProvider } from '@privy-io/react-auth';
|
||||
import { PrivyWagmiConnector } from '@privy-io/wagmi-connector';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { GrazProvider } from 'graz';
|
||||
|
||||
@@ -32,7 +34,7 @@ import { NotificationsToastArea } from '@/layout/NotificationsToastArea';
|
||||
import { DialogManager } from '@/layout/DialogManager';
|
||||
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
|
||||
|
||||
import { config } from '@/lib/wagmi';
|
||||
import { config, privyConfig } from '@/lib/wagmi';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
@@ -120,8 +122,13 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
|
||||
};
|
||||
|
||||
const providers = [
|
||||
wrapProvider(PrivyProvider, {
|
||||
appId: import.meta.env.VITE_PRIVY_APP_ID,
|
||||
config: privyConfig,
|
||||
}),
|
||||
wrapProvider(QueryClientProvider, { client: queryClient }),
|
||||
wrapProvider(GrazProvider),
|
||||
wrapProvider(PrivyWagmiConnector, { wagmiChainsConfig: config }),
|
||||
wrapProvider(WagmiConfig, { config }),
|
||||
wrapProvider(LocaleProvider),
|
||||
wrapProvider(RestrictionProvider),
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Story } from '@ladle/react';
|
||||
|
||||
import { Accordion as AccordionComponent, AccordionProps } from '@/components/Accordion';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
export const Accordion: Story<AccordionProps> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<AccordionComponent {...args} />
|
||||
</StoryWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
Accordion.args = {
|
||||
items: [
|
||||
{
|
||||
header: 'Question 1?',
|
||||
content: 'Answer 1.',
|
||||
},
|
||||
{
|
||||
header: 'Question 2?',
|
||||
content: 'Answer 2.',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -3,6 +3,8 @@ 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 = {
|
||||
@@ -10,7 +12,7 @@ export type AccordionItem = {
|
||||
content: React.ReactNode;
|
||||
};
|
||||
|
||||
type AccordionProps = {
|
||||
export type AccordionProps = {
|
||||
items: AccordionItem[];
|
||||
className?: string;
|
||||
};
|
||||
@@ -36,6 +38,13 @@ export const Accordion = ({ items, className }: AccordionProps) => (
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Root = styled(Root)`
|
||||
--accordion-paddingY: 1rem;
|
||||
--accordion-paddingX: 1rem;
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
--accordion-paddingX: 1.5rem;
|
||||
}
|
||||
|
||||
> *:not(:last-child) {
|
||||
border-bottom: var(--border-width) solid var(--border-color);
|
||||
}
|
||||
@@ -67,7 +76,8 @@ Styled.Icon = styled.div`
|
||||
Styled.Trigger = styled(Trigger)`
|
||||
${layoutMixins.spacedRow}
|
||||
width: 100%;
|
||||
padding: 1rem 0.75rem;
|
||||
padding: var(--accordion-paddingY) var(--accordion-paddingX);
|
||||
gap: 0.5rem;
|
||||
|
||||
color: var(--color-text-1);
|
||||
text-align: start;
|
||||
@@ -91,7 +101,7 @@ Styled.Trigger = styled(Trigger)`
|
||||
|
||||
Styled.Content = styled(Content)`
|
||||
overflow: hidden;
|
||||
margin: 0 0.75rem 1rem;
|
||||
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY);
|
||||
|
||||
color: var(--color-text-0);
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
CoinsIcon,
|
||||
CommentIcon,
|
||||
CopyIcon,
|
||||
CubeIcon,
|
||||
DepositIcon,
|
||||
DepthChartIcon,
|
||||
DiscordIcon,
|
||||
@@ -60,6 +59,7 @@ import {
|
||||
PriceChartIcon,
|
||||
PrivacyIcon,
|
||||
QrIcon,
|
||||
RewardStarIcon,
|
||||
SearchIcon,
|
||||
SendIcon,
|
||||
ShareIcon,
|
||||
@@ -99,7 +99,6 @@ export enum IconName {
|
||||
Coins = 'Coins',
|
||||
Comment = 'Comment',
|
||||
Copy = 'Copy',
|
||||
Cube = 'Cube',
|
||||
Deposit = 'Deposit',
|
||||
DepthChart = 'DepthChart',
|
||||
Discord = 'Discord',
|
||||
@@ -136,6 +135,7 @@ export enum IconName {
|
||||
PriceChart = 'PriceChart',
|
||||
Privacy = 'Privacy',
|
||||
Qr = 'Qr',
|
||||
RewardStar = 'RewardStar',
|
||||
Search = 'Search',
|
||||
Send = 'Send',
|
||||
Share = 'Share',
|
||||
@@ -175,7 +175,6 @@ const icons = {
|
||||
[IconName.Coins]: CoinsIcon,
|
||||
[IconName.Comment]: CommentIcon,
|
||||
[IconName.Copy]: CopyIcon,
|
||||
[IconName.Cube]: CubeIcon,
|
||||
[IconName.Deposit]: DepositIcon,
|
||||
[IconName.DepthChart]: DepthChartIcon,
|
||||
[IconName.Discord]: DiscordIcon,
|
||||
@@ -211,6 +210,7 @@ const icons = {
|
||||
[IconName.PriceChart]: PriceChartIcon,
|
||||
[IconName.Privacy]: PrivacyIcon,
|
||||
[IconName.Qr]: QrIcon,
|
||||
[IconName.RewardStar]: RewardStarIcon,
|
||||
[IconName.Search]: SearchIcon,
|
||||
[IconName.Send]: SendIcon,
|
||||
[IconName.Share]: ShareIcon,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { breakpoints } from '@/styles';
|
||||
|
||||
type PanelProps = {
|
||||
slotHeaderContent?: string;
|
||||
@@ -67,6 +68,12 @@ 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);
|
||||
|
||||
@@ -27,9 +27,8 @@ 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,26 +1,33 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import React, { PropsWithChildren, useContext, useState } from 'react';
|
||||
|
||||
import { Point } from '@visx/point';
|
||||
import { localPoint } from '@visx/event';
|
||||
import { XYChart, DataContext } from '@visx/xychart';
|
||||
import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart';
|
||||
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
|
||||
|
||||
export const XYChartWithPointerEvents = ({
|
||||
onPointerMove, onPointerUp, onPointerPressedChange, ...props
|
||||
onPointerMove,
|
||||
onPointerUp,
|
||||
onPointerPressedChange,
|
||||
...props
|
||||
}: {
|
||||
onPointerMove?: (point: Point) => void;
|
||||
onPointerUp?: (point: Point) => void;
|
||||
onPointerMove?: (point: Point | EventHandlerParams<object>) => void;
|
||||
onPointerUp?: (point: Point | EventHandlerParams<object>) => void;
|
||||
onPointerPressedChange?: (isPointerPressed: boolean) => void;
|
||||
} & React.PropsWithChildren<Parameters<typeof XYChart>>) => {
|
||||
} & PropsWithChildren<Parameters<typeof XYChart>[0]>) => {
|
||||
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),
|
||||
});
|
||||
|
||||
@@ -29,15 +36,13 @@ 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}
|
||||
|
||||
@@ -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.modal.TransferInputField;
|
||||
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.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.modal.TradeInputField;
|
||||
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.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.modal.ClosePositionInputField;
|
||||
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
|
||||
|
||||
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
|
||||
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
|
||||
|
||||
@@ -96,6 +96,7 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
elapsedTime?: number;
|
||||
blockHeight?: number;
|
||||
indexerBlockHeight?: number;
|
||||
trailingBlocks?: number;
|
||||
}
|
||||
: // Navigation
|
||||
T extends AnalyticsEvent.NavigatePage
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
import { FundingDirection } from './markets';
|
||||
|
||||
// ------ Depth Chart ------ //
|
||||
export enum DepthChartSeries {
|
||||
Asks = 'Asks',
|
||||
Bids = 'Bids',
|
||||
MidMarket = 'MidMarket',
|
||||
}
|
||||
|
||||
export type DepthChartDatum = {
|
||||
size: number;
|
||||
price: number;
|
||||
depth: number;
|
||||
seriesKey: DepthChartSeries;
|
||||
};
|
||||
|
||||
export type DepthChartPoint = {
|
||||
side: OrderSide;
|
||||
price: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
export const SERIES_KEY_FOR_ORDER_SIDE = {
|
||||
[OrderSide.BUY]: DepthChartSeries.Bids,
|
||||
[OrderSide.SELL]: DepthChartSeries.Asks,
|
||||
};
|
||||
|
||||
// ------ Funding Chart ------ //
|
||||
export enum FundingRateResolution {
|
||||
OneHour = 'OneHour',
|
||||
EightHour = 'EightHour',
|
||||
Annualized = 'Annualized',
|
||||
}
|
||||
|
||||
export type FundingChartDatum = {
|
||||
time: number;
|
||||
fundingRate: number;
|
||||
direction: FundingDirection;
|
||||
};
|
||||
@@ -25,4 +25,5 @@ export const DEFAULT_MARKETID = 'ETH-USD';
|
||||
export enum FundingDirection {
|
||||
ToShort = 'ToShort',
|
||||
ToLong = 'ToLong',
|
||||
None = 'None',
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ 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,
|
||||
|
||||
@@ -31,6 +31,7 @@ import { DydxNetwork, ENVIRONMENT_CONFIG_MAP } from './networks';
|
||||
export enum WalletConnectionType {
|
||||
CoinbaseWalletSdk = 'coinbaseWalletSdk',
|
||||
CosmosSigner = 'CosmosSigner',
|
||||
Privy = 'Privy',
|
||||
InjectedEip1193 = 'injectedEip1193',
|
||||
WalletConnect2 = 'walletConnect2',
|
||||
}
|
||||
@@ -68,6 +69,9 @@ export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectio
|
||||
[WalletConnectionType.CosmosSigner]: {
|
||||
name: 'CosmosSigner',
|
||||
},
|
||||
[WalletConnectionType.Privy]: {
|
||||
name: 'Privy',
|
||||
},
|
||||
};
|
||||
|
||||
// Wallets
|
||||
@@ -90,6 +94,7 @@ export enum WalletType {
|
||||
WalletConnect2 = 'WALLETCONNECT_2',
|
||||
// TestWallet = 'TEST_WALLET',
|
||||
OtherWallet = 'OTHER_WALLET',
|
||||
Privy = 'PRIVY',
|
||||
}
|
||||
|
||||
const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
|
||||
@@ -243,6 +248,12 @@ export const wallets: Record<WalletType, WalletConfig> = {
|
||||
icon: KeplrIcon,
|
||||
connectionTypes: [WalletConnectionType.CosmosSigner],
|
||||
},
|
||||
[WalletType.Privy]: {
|
||||
type: WalletType.Privy,
|
||||
stringKey: STRING_KEYS.KEPLR,
|
||||
icon: GenericWalletIcon,
|
||||
connectionTypes: [WalletConnectionType.Privy],
|
||||
},
|
||||
};
|
||||
|
||||
// Injected EIP-1193 Providers
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
||||
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets';
|
||||
import {
|
||||
DydxAddress,
|
||||
EvmAddress,
|
||||
PrivateInformation,
|
||||
WalletConnectionType,
|
||||
getSignTypedData,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
import { setOnboardingState, setOnboardingGuard } from '@/state/account';
|
||||
import { forceOpenDialog } from '@/state/dialogs';
|
||||
@@ -20,6 +26,11 @@ import { useLocalStorage } from './useLocalStorage';
|
||||
import { useRestrictions } from './useRestrictions';
|
||||
import { useWalletConnection } from './useWalletConnection';
|
||||
|
||||
import { useSignTypedData } from 'wagmi';
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { usePrivy } from '@privy-io/react-auth';
|
||||
|
||||
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
|
||||
|
||||
AccountsContext.displayName = 'Accounts';
|
||||
@@ -153,6 +164,7 @@ const useAccountsContext = () => {
|
||||
|
||||
// dYdX wallet / onboarding state
|
||||
const [localDydxWallet, setLocalDydxWallet] = useState<LocalWallet>();
|
||||
const [localNobleWallet, setLocalNobleWallet] = useState<LocalWallet>();
|
||||
const [hdKey, setHdKey] = useState<PrivateInformation>();
|
||||
|
||||
const dydxAccounts = useMemo(() => localDydxWallet?.accounts, [localDydxWallet]);
|
||||
@@ -162,6 +174,11 @@ const useAccountsContext = () => {
|
||||
[localDydxWallet]
|
||||
);
|
||||
|
||||
const nobleAddress = useMemo(
|
||||
() => localNobleWallet?.address,
|
||||
[localNobleWallet]
|
||||
);
|
||||
|
||||
const setWalletFromEvmSignature = async (signature: string) => {
|
||||
const { wallet, mnemonic, privateKey, publicKey } = await getWalletFromEvmSignature({
|
||||
signature,
|
||||
@@ -176,6 +193,20 @@ const useAccountsContext = () => {
|
||||
}
|
||||
}, [evmAddress, dydxAddress]);
|
||||
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
|
||||
const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
|
||||
|
||||
const signTypedData = getSignTypedData(selectedNetwork);
|
||||
const { signTypedDataAsync } = useSignTypedData({
|
||||
...signTypedData,
|
||||
domain: {
|
||||
...signTypedData.domain,
|
||||
chainId,
|
||||
},
|
||||
});
|
||||
const { ready, authenticated } = usePrivy();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (connectedDydxAddress && signerGraz) {
|
||||
@@ -192,7 +223,17 @@ const useAccountsContext = () => {
|
||||
|
||||
const evmDerivedAccount = evmDerivedAddresses[evmAddress];
|
||||
|
||||
if (evmDerivedAccount?.encryptedSignature) {
|
||||
if (walletConnectionType === WalletConnectionType.Privy && authenticated && ready) {
|
||||
try {
|
||||
const signature = await signTypedDataAsync();
|
||||
|
||||
await setWalletFromEvmSignature(signature);
|
||||
dispatch(setOnboardingState(OnboardingState.AccountConnected));
|
||||
} catch (error) {
|
||||
log('useAccounts/decryptSignature', error);
|
||||
forgetEvmSignature();
|
||||
}
|
||||
} else if (evmDerivedAccount?.encryptedSignature) {
|
||||
try {
|
||||
const signature = decryptSignature(evmDerivedAccount.encryptedSignature);
|
||||
|
||||
@@ -211,7 +252,15 @@ const useAccountsContext = () => {
|
||||
dispatch(setOnboardingState(OnboardingState.Disconnected));
|
||||
}
|
||||
})();
|
||||
}, [evmAddress, evmDerivedAddresses, signerWagmi, connectedDydxAddress, signerGraz]);
|
||||
}, [
|
||||
evmAddress,
|
||||
evmDerivedAddresses,
|
||||
signerWagmi,
|
||||
connectedDydxAddress,
|
||||
signerGraz,
|
||||
authenticated,
|
||||
ready,
|
||||
]);
|
||||
|
||||
// abacus
|
||||
useEffect(() => {
|
||||
@@ -224,6 +273,7 @@ const useAccountsContext = () => {
|
||||
if (hdKey?.mnemonic) {
|
||||
const nobleWallet = await LocalWallet.fromMnemonic(hdKey.mnemonic, NOBLE_BECH32_PREFIX);
|
||||
abacusStateManager.setNobleWallet(nobleWallet);
|
||||
setLocalNobleWallet(nobleWallet);
|
||||
}
|
||||
};
|
||||
setNobleWallet();
|
||||
@@ -322,6 +372,7 @@ const useAccountsContext = () => {
|
||||
localDydxWallet,
|
||||
dydxAccounts,
|
||||
dydxAddress,
|
||||
nobleAddress,
|
||||
|
||||
// Onboarding state
|
||||
saveHasAcknowledgedTerms,
|
||||
|
||||
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
|
||||
}, []);
|
||||
|
||||
// AnalyticsEvent.NetworkStatus
|
||||
const { height, indexerHeight, status } = useApiState();
|
||||
const { height, indexerHeight, status, trailingBlocks} = useApiState();
|
||||
|
||||
useEffect(() => {
|
||||
if (status) {
|
||||
@@ -114,6 +114,7 @@ export const useAnalytics = () => {
|
||||
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
|
||||
blockHeight: height ?? undefined,
|
||||
indexerBlockHeight: indexerHeight ?? undefined,
|
||||
trailingBlocks: trailingBlocks ?? undefined
|
||||
});
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export const useDisplayedWallets = () => {
|
||||
// WalletType.BitKeep,
|
||||
// WalletType.Coin98,
|
||||
|
||||
WalletType.Privy,
|
||||
WalletType.OtherWallet,
|
||||
].filter(isTruthy);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useNetwork, useSwitchNetwork } from 'wagmi';
|
||||
import { useSwitchNetwork as useSwitchNetworkPrivy } from '@privy-io/wagmi-connector';
|
||||
import { WalletConnectionType } from '@/constants/wallets';
|
||||
|
||||
import { useWalletConnection } from './useWalletConnection';
|
||||
|
||||
export const useMatchingEvmNetwork = ({
|
||||
chainId,
|
||||
@@ -11,7 +15,10 @@ export const useMatchingEvmNetwork = ({
|
||||
onError?: (error: Error) => void;
|
||||
}) => {
|
||||
const { chain } = useNetwork();
|
||||
const { walletConnectionType } = useWalletConnection();
|
||||
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
|
||||
const { isLoading: isLoadingPrivy, switchNetworkAsync: switchNetworkAsyncPrivy } =
|
||||
useSwitchNetworkPrivy({ onError });
|
||||
|
||||
// If chainId is not a number, we can assume it is a non EVM compatible chain
|
||||
const isMatchingNetwork = useMemo(
|
||||
@@ -21,7 +28,11 @@ export const useMatchingEvmNetwork = ({
|
||||
|
||||
const matchNetwork = useCallback(async () => {
|
||||
if (!isMatchingNetwork) {
|
||||
await switchNetworkAsync?.(Number(chainId));
|
||||
if (walletConnectionType === WalletConnectionType.Privy) {
|
||||
await switchNetworkAsyncPrivy?.(Number(chainId));
|
||||
} else {
|
||||
await switchNetworkAsync?.(Number(chainId));
|
||||
}
|
||||
}
|
||||
}, [chainId, chain]);
|
||||
|
||||
@@ -34,6 +45,6 @@ export const useMatchingEvmNetwork = ({
|
||||
return {
|
||||
isMatchingNetwork,
|
||||
matchNetwork,
|
||||
isSwitchingNetwork: isLoading,
|
||||
isSwitchingNetwork: isLoading || isLoadingPrivy,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ 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';
|
||||
|
||||
@@ -80,6 +81,7 @@ 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}
|
||||
@@ -93,6 +95,29 @@ 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,
|
||||
@@ -102,6 +127,7 @@ 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]
|
||||
);
|
||||
@@ -197,6 +223,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
|
||||
/>
|
||||
),
|
||||
toastSensitivity: 'foreground',
|
||||
groupKey: NotificationType.SquidTransfer,
|
||||
},
|
||||
[isFinished]
|
||||
);
|
||||
@@ -242,6 +269,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
|
||||
},
|
||||
}),
|
||||
toastSensitivity: 'foreground',
|
||||
groupKey: NotificationType.ReleaseUpdates,
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMemo } from 'react';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
|
||||
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
|
||||
|
||||
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
||||
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
|
||||
export const useOrderbookValuesForDepthChart = () => {
|
||||
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
||||
|
||||
return useMemo(() => {
|
||||
const bids = (orderbook?.bids?.toArray() ?? [])
|
||||
.filter(Boolean)
|
||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
|
||||
|
||||
const asks = (orderbook?.asks?.toArray() ?? [])
|
||||
.filter(Boolean)
|
||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
|
||||
|
||||
const lowestBid = bids[bids.length - 1];
|
||||
const highestBid = bids[0];
|
||||
const lowestAsk = asks[0];
|
||||
const highestAsk = asks[asks.length - 1];
|
||||
|
||||
const midMarketPrice = orderbook?.midPrice;
|
||||
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
|
||||
const spreadPercent = orderbook?.spreadPercent;
|
||||
|
||||
return {
|
||||
bids,
|
||||
asks,
|
||||
lowestBid,
|
||||
highestBid,
|
||||
lowestAsk,
|
||||
highestAsk,
|
||||
midMarketPrice,
|
||||
spread,
|
||||
spreadPercent,
|
||||
orderbook,
|
||||
};
|
||||
}, [orderbook]);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { usePrivy, useLogout } from '@privy-io/react-auth';
|
||||
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
@@ -104,8 +105,10 @@ export const useWalletConnection = () => {
|
||||
[walletConnectConfig, walletType, walletConnectionType]
|
||||
);
|
||||
|
||||
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
|
||||
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector });
|
||||
const { suggestAndConnect: connectGraz } = useConnectGraz();
|
||||
const { login, ready, authenticated } = usePrivy();
|
||||
const { logout } = useLogout();
|
||||
|
||||
const connectWallet = useCallback(
|
||||
async ({ walletType }: { walletType: WalletType }) => {
|
||||
@@ -114,6 +117,11 @@ export const useWalletConnection = () => {
|
||||
try {
|
||||
if (!walletConnection) {
|
||||
throw new Error('Onboarding: No wallet connection found.');
|
||||
} else if (walletConnection.type === WalletConnectionType.Privy) {
|
||||
|
||||
if (!isConnectedWagmi && !authenticated && ready) {
|
||||
login();
|
||||
}
|
||||
} else if (walletConnection.type === WalletConnectionType.CosmosSigner) {
|
||||
const cosmosWalletType = {
|
||||
[WalletType.Keplr as string]: CosmosWalletType.KEPLR,
|
||||
@@ -156,7 +164,7 @@ export const useWalletConnection = () => {
|
||||
walletConnectionType: walletConnection.type,
|
||||
};
|
||||
},
|
||||
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi]
|
||||
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi, login, ready, authenticated]
|
||||
);
|
||||
|
||||
const disconnectWallet = useCallback(async () => {
|
||||
@@ -165,6 +173,7 @@ export const useWalletConnection = () => {
|
||||
|
||||
if (isConnectedWagmi) await disconnectWagmi();
|
||||
if (isConnectedGraz) await disconnectGraz();
|
||||
if (authenticated) await logout();
|
||||
}, [isConnectedGraz, isConnectedWagmi]);
|
||||
|
||||
// Wallet selection
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
<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>
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 840 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.95513 1.00169C7.89506 1.00645 7.83628 1.02054 7.78124 1.04337L1.31971 3.71042C1.2243 3.74983 1.14329 3.81414 1.08653 3.89553C1.02977 3.97691 0.999705 4.07186 1 4.16883V11.8366C1.00078 11.9327 1.03135 12.0265 1.08805 12.1069C1.14474 12.1873 1.22517 12.2508 1.31971 12.2898L7.78124 14.9568C7.85011 14.9853 7.92463 15 8 15C8.07536 15 8.14989 14.9853 8.21876 14.9568L14.6803 12.2898C14.7748 12.2508 14.8552 12.1873 14.9119 12.1069C14.9686 12.0265 14.9992 11.9327 15 11.8366V4.16883C15.0003 4.07186 14.9702 3.97691 14.9134 3.89553C14.8567 3.81414 14.7756 3.74983 14.6803 3.71042L8.21876 1.04337C8.13605 1.00911 8.04538 0.994778 7.95513 1.00169ZM8 2.04351L13.1378 4.16883L8 6.28894L2.86219 4.16883L8 2.04351ZM2.07693 4.93457L7.46153 7.15886V13.7328L2.07693 11.5084V4.93457ZM13.923 4.93457V11.5084L8.53846 13.7328V7.15886L13.923 4.93457Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 969 B |
+1
-1
@@ -19,7 +19,6 @@ 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';
|
||||
@@ -51,6 +50,7 @@ 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';
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 481 KiB |
+34
-59
@@ -1,7 +1,11 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { NotificationStatus } from '@/constants/notifications';
|
||||
import {
|
||||
type Notification,
|
||||
type NotificationDisplayData,
|
||||
NotificationStatus,
|
||||
} from '@/constants/notifications';
|
||||
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import { useBreakpoints } from '@/hooks/useBreakpoints';
|
||||
@@ -13,54 +17,31 @@ 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;
|
||||
};
|
||||
|
||||
const MAX_TOASTS = 10;
|
||||
|
||||
export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
export const NotificationStack = ({ notifications, className }: ElementProps & StyleProps) => {
|
||||
const [shouldStackNotifications, setshouldStackNotifications] = useState(true);
|
||||
|
||||
const {
|
||||
notifications,
|
||||
getKey,
|
||||
getDisplayData,
|
||||
markUnseen,
|
||||
markSeen,
|
||||
isMenuOpen,
|
||||
onNotificationAction,
|
||||
} = useNotifications();
|
||||
|
||||
const { markUnseen, markSeen, onNotificationAction } = useNotifications();
|
||||
const { isMobile } = useBreakpoints();
|
||||
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;
|
||||
const hasMultipleToasts = notifications.length > 1;
|
||||
|
||||
return (
|
||||
<StyledToastArea swipeDirection={isMobile ? 'up' : 'right'} className={className}>
|
||||
<StyledToastArea
|
||||
swipeDirection={isMobile ? 'up' : 'right'}
|
||||
className={className}
|
||||
size={notifications.length}
|
||||
>
|
||||
{hasMultipleToasts && (
|
||||
<StyledToggleButton
|
||||
shape={ButtonShape.Pill}
|
||||
@@ -72,12 +53,12 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
</StyledToggleButton>
|
||||
)}
|
||||
|
||||
{notificationMap.map(({ notification, key, displayData }, idx) => (
|
||||
{notifications.map(({ notification, key, displayData }, idx) => (
|
||||
<StyledToast
|
||||
key={key}
|
||||
isStacked={shouldStackNotifications}
|
||||
isStacked={idx > 0 && shouldStackNotifications}
|
||||
isOpen={notification.status < NotificationStatus.Unseen}
|
||||
layer={notificationMap.length - 1 - idx}
|
||||
layer={idx}
|
||||
notification={notification}
|
||||
slotIcon={displayData.icon}
|
||||
slotTitle={displayData.title}
|
||||
@@ -110,19 +91,13 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
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 StyledToastArea = styled(ToastArea)<{ size: number }>`
|
||||
position: relative;
|
||||
${({ size }) =>
|
||||
size &&
|
||||
css`
|
||||
padding-bottom: calc(${size - 1} * 2px);
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
|
||||
@@ -133,7 +108,7 @@ const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(${layer} * 2px);
|
||||
right: calc(${layer} * -2px);
|
||||
right: 0;
|
||||
`
|
||||
: css`
|
||||
margin-bottom: 0.75rem;
|
||||
@@ -145,7 +120,7 @@ const StyledToggleButton = styled(ToggleButton)<{ isPressed: boolean }>`
|
||||
pointer-events: auto;
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
top: -0.5rem;
|
||||
left: calc(50% - 0.75rem);
|
||||
--button-width: 2rem;
|
||||
--button-height: 1rem;
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useMemo } from 'react';
|
||||
import { groupBy } from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { NotificationStatus } from '@/constants/notifications';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
|
||||
import { NotificationStack } from './NotifcationStack';
|
||||
|
||||
type StyleProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const MAX_TOASTS = 10;
|
||||
|
||||
export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
const { notifications, getKey, getDisplayData, isMenuOpen } = useNotifications();
|
||||
|
||||
const notificationMapByType = useMemo(() => {
|
||||
const notificationMap = Object.values(notifications)
|
||||
// Sort by time of first trigger
|
||||
.sort(
|
||||
(n1, n2) =>
|
||||
n1.timestamps[NotificationStatus.Triggered]! -
|
||||
n2.timestamps[NotificationStatus.Triggered]!
|
||||
)
|
||||
.map((notification) => ({
|
||||
notification,
|
||||
key: getKey(notification),
|
||||
displayData: getDisplayData(notification),
|
||||
}))
|
||||
.filter(
|
||||
({ displayData, notification }) =>
|
||||
displayData && notification.status < NotificationStatus.Unseen
|
||||
)
|
||||
.slice(-MAX_TOASTS);
|
||||
return groupBy(notificationMap, (notification) => notification.displayData?.groupKey);
|
||||
}, [notifications, getKey, getDisplayData]);
|
||||
|
||||
if (isMenuOpen) return null;
|
||||
|
||||
return (
|
||||
<StyledToastArea className={className}>
|
||||
{Object.keys(notificationMapByType).map((groupKey) => (
|
||||
<NotificationStack notifications={notificationMapByType[groupKey]} key={groupKey} />
|
||||
))}
|
||||
</StyledToastArea>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledToastArea = styled.div`
|
||||
${layoutMixins.scrollArea}
|
||||
|
||||
position: absolute;
|
||||
width: min(17.5rem, 100%);
|
||||
inset: 0 0 0 auto;
|
||||
|
||||
padding: 0.75rem 0.75rem 0.75rem 0;
|
||||
|
||||
mask-image: linear-gradient(to left, transparent, white 0.5rem);
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
@media ${breakpoints.mobile} {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
`;
|
||||
@@ -11,16 +11,16 @@ class TestFlags {
|
||||
const params = new URLSearchParams(queryParamsString);
|
||||
|
||||
for (const [key, value] of params) {
|
||||
this.queryParams[key] = value;
|
||||
this.queryParams[key.toLowerCase()] = value;
|
||||
}
|
||||
}
|
||||
|
||||
get displayInitializingMarkets() {
|
||||
return !!this.queryParams.displayInitializingMarkets;
|
||||
return !!this.queryParams.displayinitializingmarkets;
|
||||
}
|
||||
|
||||
get withCCTP() {
|
||||
return !!this.queryParams.withCCTP;
|
||||
get showMobileSignInOption() {
|
||||
return !!this.queryParams.mobilesignin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-2
@@ -1,5 +1,6 @@
|
||||
import { createConfig, configureChains, mainnet, Chain, Connector, usePublicClient } from 'wagmi';
|
||||
import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
|
||||
import { goerli } from 'wagmi/chains';
|
||||
import type { PrivyClientConfig } from '@privy-io/react-auth';
|
||||
|
||||
import {
|
||||
arbitrum,
|
||||
@@ -26,6 +27,9 @@ import {
|
||||
fantomTestnet,
|
||||
celo,
|
||||
celoAlfajores,
|
||||
scroll,
|
||||
kava,
|
||||
sepolia,
|
||||
} from 'viem/chains';
|
||||
|
||||
import { alchemyProvider } from 'wagmi/providers/alchemy';
|
||||
@@ -53,7 +57,7 @@ import { isTruthy } from './isTruthy';
|
||||
export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
|
||||
mainnet,
|
||||
goerli,
|
||||
|
||||
sepolia,
|
||||
arbitrum,
|
||||
arbitrumGoerli,
|
||||
avalanche,
|
||||
@@ -78,8 +82,23 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
|
||||
fantomTestnet,
|
||||
celo,
|
||||
celoAlfajores,
|
||||
scroll,
|
||||
kava,
|
||||
];
|
||||
|
||||
export const privyConfig: PrivyClientConfig = {
|
||||
embeddedWallets: {
|
||||
createOnLogin: 'users-without-wallets',
|
||||
requireUserPasswordOnCreate: true,
|
||||
noPromptOnSignature: true,
|
||||
},
|
||||
loginMethods: ['email', 'sms', 'twitter', 'google', 'apple'],
|
||||
appearance: {
|
||||
theme: '#28283c',
|
||||
},
|
||||
defaultChain: sepolia
|
||||
};
|
||||
|
||||
const { chains, publicClient, webSocketPublicClient } = configureChains(
|
||||
WAGMI_SUPPORTED_CHAINS,
|
||||
[
|
||||
|
||||
@@ -62,6 +62,12 @@ export const getWalletConnection = ({
|
||||
type: WalletConnectionType.CosmosSigner,
|
||||
};
|
||||
}
|
||||
|
||||
case WalletConnectionType.Privy: {
|
||||
return {
|
||||
type: WalletConnectionType.Privy,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+29
-13
@@ -30,6 +30,9 @@ 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';
|
||||
|
||||
const ENS_CHAIN_ID = 1; // Ethereum
|
||||
|
||||
const Profile = () => {
|
||||
@@ -175,8 +178,27 @@ const Profile = () => {
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
|
||||
/>
|
||||
</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}`}
|
||||
@@ -191,19 +213,6 @@ 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
|
||||
@@ -327,6 +336,13 @@ Styled.Details = styled(Details)`
|
||||
`;
|
||||
|
||||
Styled.RewardsPanel = styled(Panel)`
|
||||
align-self: flex-start;
|
||||
|
||||
&,
|
||||
> * {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
dl {
|
||||
--details-grid-numColumns: 1;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,14 @@ 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 } from '@/state/accountSelectors';
|
||||
import { getOnboardingState, getSubaccount, getTradeInfoNumbers } from '@/state/accountSelectors';
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
import { shortenNumberForDisplay } from '@/lib/numbers';
|
||||
|
||||
import { PortfolioNavMobile } from './PortfolioNavMobile';
|
||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||
|
||||
@@ -42,6 +45,10 @@ 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 }));
|
||||
@@ -118,14 +125,28 @@ export default () => {
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Positions,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
|
||||
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
|
||||
slotBefore: <Styled.Icon iconName={IconName.Positions} />,
|
||||
label: (
|
||||
<>
|
||||
{stringGetter({ key: STRING_KEYS.POSITIONS })}
|
||||
{numPositions > 0 && (
|
||||
<Tag type={TagType.Number}> {numPositions} </Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
href: PortfolioRoute.Positions,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Orders,
|
||||
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
|
||||
label: stringGetter({ key: STRING_KEYS.ORDERS }),
|
||||
label: (
|
||||
<>
|
||||
{stringGetter({ key: STRING_KEYS.ORDERS })}
|
||||
{numOrders > 0 && (
|
||||
<Tag type={TagType.Number}> {numOrders} </Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
href: PortfolioRoute.Orders,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@ 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';
|
||||
|
||||
@@ -33,7 +32,7 @@ export const DYDXBalancePanel = () => {
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
return (
|
||||
<Styled.Panel
|
||||
<Panel
|
||||
slotHeader={
|
||||
<Styled.Header>
|
||||
<Styled.Title>
|
||||
@@ -109,21 +108,12 @@ export const DYDXBalancePanel = () => {
|
||||
]}
|
||||
/>
|
||||
</Styled.Content>
|
||||
</Styled.Panel>
|
||||
</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;
|
||||
|
||||
@@ -24,6 +24,8 @@ import { openDialog } from '@/state/dialogs';
|
||||
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
const SEASON_NUMBER = 2;
|
||||
|
||||
export const LaunchIncentivesPanel = () => {
|
||||
const { isNotTablet } = useBreakpoints();
|
||||
const dispatch = useDispatch();
|
||||
@@ -68,10 +70,12 @@ const EstimatedRewards = () => {
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
enabled: !!dydxAddress,
|
||||
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}`,
|
||||
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}_${SEASON_NUMBER}`,
|
||||
queryFn: async () => {
|
||||
if (!dydxAddress) return undefined;
|
||||
const resp = await fetch(`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}`);
|
||||
const resp = await fetch(
|
||||
`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}?n=${SEASON_NUMBER}`
|
||||
);
|
||||
return (await resp.json())?.incentivePoints;
|
||||
},
|
||||
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
|
||||
@@ -85,9 +89,7 @@ const EstimatedRewards = () => {
|
||||
<Styled.Season>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.LAUNCH_INCENTIVES_SEASON_NUM,
|
||||
params: {
|
||||
SEASON_NUMBER: 1,
|
||||
},
|
||||
params: { SEASON_NUMBER },
|
||||
})}
|
||||
</Styled.Season>
|
||||
</div>
|
||||
@@ -151,15 +153,8 @@ 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`
|
||||
@@ -223,7 +218,7 @@ Styled.Column = styled.div`
|
||||
Styled.EstimatedRewardsCard = styled.div`
|
||||
${layoutMixins.spacedRow}
|
||||
padding: 1rem 1.25rem;
|
||||
min-width: 21.25rem;
|
||||
min-width: 19rem;
|
||||
height: calc(100% - calc(1.5rem * 2));
|
||||
margin: 1.5rem;
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ 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';
|
||||
@@ -28,7 +26,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 = () => {
|
||||
export const MigratePanel = ({ className }: { className?: string }) => {
|
||||
const { isNotTablet } = useBreakpoints();
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
@@ -45,6 +43,7 @@ export const MigratePanel = () => {
|
||||
|
||||
return isNotTablet ? (
|
||||
<Styled.MigratePanel
|
||||
className={className}
|
||||
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.MIGRATE })}</Styled.Title>}
|
||||
slotRight={
|
||||
<Styled.MigrateAction>
|
||||
@@ -74,6 +73,7 @@ export const MigratePanel = () => {
|
||||
</Styled.MigratePanel>
|
||||
) : (
|
||||
<Styled.MigratePanel
|
||||
className={className}
|
||||
slotHeader={
|
||||
<Styled.MobileMigrateHeader>
|
||||
<h3>
|
||||
@@ -134,17 +134,11 @@ export const MigratePanel = () => {
|
||||
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`
|
||||
@@ -157,7 +151,7 @@ Styled.Title = styled.h3`
|
||||
Styled.MigrateAction = styled.div`
|
||||
${layoutMixins.flexEqualColumns}
|
||||
align-items: center;
|
||||
margin-right: 1rem;
|
||||
margin: 1rem;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
@@ -197,7 +191,7 @@ Styled.MobileMigrateHeader = styled.div`
|
||||
font: var(--font-small-book);
|
||||
color: var(--color-text-0);
|
||||
|
||||
padding: 1rem 1.5rem 0;
|
||||
padding: 1rem 1rem 0;
|
||||
|
||||
h3 {
|
||||
${layoutMixins.inlineRow}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -51,6 +52,7 @@ export const RewardsHelpPanel = () => {
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.HelpCard = styled(Panel)`
|
||||
--panel-content-paddingX: 0;
|
||||
--panel-content-paddingY: 0;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
@@ -64,11 +66,15 @@ Styled.Header = styled.div`
|
||||
${layoutMixins.spacedRow}
|
||||
gap: 1ch;
|
||||
|
||||
padding: 1.25rem 1.5rem;
|
||||
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);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
|
||||
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';
|
||||
@@ -20,12 +23,14 @@ import { openDialog } from '@/state/dialogs';
|
||||
import { DYDXBalancePanel } from './DYDXBalancePanel';
|
||||
import { MigratePanel } from './MigratePanel';
|
||||
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
|
||||
import { RewardsHelpPanel } from './RewardsHelpPanel';
|
||||
|
||||
const RewardsPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const stringGetter = useStringGetter();
|
||||
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
|
||||
const { isTablet, isNotTablet } = useBreakpoints();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const panelArrow = (
|
||||
<Styled.Arrow>
|
||||
@@ -39,13 +44,16 @@ const RewardsPage = () => {
|
||||
|
||||
return (
|
||||
<Styled.Page>
|
||||
{import.meta.env.VITE_V3_TOKEN_ADDRESS && <MigratePanel />}
|
||||
{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 />}
|
||||
|
||||
{isTablet ? (
|
||||
<>
|
||||
<LaunchIncentivesPanel />
|
||||
<DYDXBalancePanel />
|
||||
</>
|
||||
<LaunchIncentivesPanel />
|
||||
) : (
|
||||
<Styled.PanelRowIncentivesAndBalance>
|
||||
<LaunchIncentivesPanel />
|
||||
@@ -55,7 +63,9 @@ const RewardsPage = () => {
|
||||
|
||||
<Styled.PanelRow>
|
||||
<Styled.Panel
|
||||
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
|
||||
slotHeaderContent={
|
||||
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
|
||||
}
|
||||
slotRight={panelArrow}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||
>
|
||||
@@ -68,7 +78,9 @@ const RewardsPage = () => {
|
||||
</Styled.Panel>
|
||||
|
||||
<Styled.Panel
|
||||
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
|
||||
slotHeaderContent={
|
||||
<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>
|
||||
}
|
||||
slotRight={panelArrow}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||
>
|
||||
@@ -80,6 +92,8 @@ const RewardsPage = () => {
|
||||
</Styled.Description>
|
||||
</Styled.Panel>
|
||||
</Styled.PanelRow>
|
||||
|
||||
<RewardsHelpPanel />
|
||||
</Styled.Page>
|
||||
);
|
||||
};
|
||||
@@ -95,33 +109,41 @@ Styled.Page = styled.div`
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
--content-max-width: 70rem;
|
||||
--content-max-width: 80rem;
|
||||
max-width: min(calc(100vw - 4rem), var(--content-max-width));
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
padding: 1.25rem;
|
||||
--stickyArea-topHeight: var(--page-header-height-mobile);
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
> * {
|
||||
max-width: calc(100vw - 2.5rem);
|
||||
max-width: calc(100vw - 2rem);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
--panel-paddingX: 1.5rem;
|
||||
Styled.MobileHeader = styled.header`
|
||||
${layoutMixins.contentSectionDetachedScrollable}
|
||||
${layoutMixins.stickyHeader}
|
||||
z-index: 2;
|
||||
padding: 1.25rem 0;
|
||||
margin-bottom: -1.5rem;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
--panel-paddingY: 1.5rem;
|
||||
--panel-content-paddingY: 1rem;
|
||||
}
|
||||
font: var(--font-large-medium);
|
||||
color: var(--color-text-2);
|
||||
background-color: var(--color-layer-2);
|
||||
`;
|
||||
|
||||
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;
|
||||
margin-bottom: -1rem;
|
||||
`;
|
||||
|
||||
Styled.Description = styled.div`
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { SubaccountPosition } from '@/constants/abacus';
|
||||
import { OnboardingState, OnboardingSteps } from '@/constants/account';
|
||||
|
||||
import {
|
||||
getExistingOpenPositions,
|
||||
getOnboardingGuards,
|
||||
getOnboardingState,
|
||||
getSubaccountId,
|
||||
@@ -60,13 +58,8 @@ export const calculateIsAccountViewOnly = createSelector(
|
||||
);
|
||||
|
||||
/**
|
||||
* @description calculate whether the subaccount has open positions
|
||||
* @description calculate whether the subaccount has uncommitted positions
|
||||
*/
|
||||
export const calculateHasOpenPositions = createSelector(
|
||||
[getExistingOpenPositions],
|
||||
(openPositions?: SubaccountPosition[]) => (openPositions?.length || 0) > 0
|
||||
);
|
||||
|
||||
export const calculateHasUncommittedOrders = createSelector(
|
||||
[getUncommittedOrderClientIds],
|
||||
(uncommittedOrderClientIds: number[]) => uncommittedOrderClientIds.length > 0
|
||||
|
||||
@@ -2,6 +2,7 @@ import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import {
|
||||
type AbacusOrderStatuses,
|
||||
type SubaccountOrder,
|
||||
type SubaccountFill,
|
||||
type SubaccountFundingPayment,
|
||||
@@ -43,7 +44,6 @@ 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,6 +258,14 @@ 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
|
||||
@@ -266,8 +274,7 @@ export const getTradeInfoNumbers = createSelector(
|
||||
[getExistingOpenPositions, getSubaccountOrders, getSubaccountFills, getSubaccountFundingPayments],
|
||||
(positions, orders, fills, fundingPayments) => ({
|
||||
numTotalPositions: positions?.length,
|
||||
numTotalOpenOrders: orders?.filter((order) => order.status !== AbacusOrderStatus.cancelled)
|
||||
.length,
|
||||
numTotalOpenOrders: orders?.filter((order) => isOpenOrderStatus(order.status)).length,
|
||||
numTotalFills: fills?.length,
|
||||
numTotalFundingPayments: fundingPayments?.length,
|
||||
})
|
||||
@@ -281,8 +288,7 @@ export const getCurrentMarketTradeInfoNumbers = createSelector(
|
||||
[getCurrentMarketOrders, getCurrentMarketFills, getCurrentMarketFundingPayments],
|
||||
(marketOrders, marketFills, marketFundingPayments) => {
|
||||
return {
|
||||
numOpenOrders: marketOrders?.filter((order) => order.status !== AbacusOrderStatus.cancelled)
|
||||
.length,
|
||||
numOpenOrders: marketOrders?.filter((order) => isOpenOrderStatus(order.status)).length,
|
||||
numFills: marketFills?.length,
|
||||
numFundingPayments: marketFundingPayments?.length,
|
||||
};
|
||||
|
||||
@@ -26,7 +26,12 @@ export const calculateFundingRateHistory = createSelector(
|
||||
return data.map(({ effectiveAtMilliseconds, rate }) => ({
|
||||
time: effectiveAtMilliseconds,
|
||||
fundingRate: rate,
|
||||
direction: rate < 0 ? FundingDirection.ToLong : FundingDirection.ToShort,
|
||||
direction:
|
||||
rate === 0
|
||||
? FundingDirection.None
|
||||
: rate < 0
|
||||
? FundingDirection.ToLong
|
||||
: FundingDirection.ToShort,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -29,7 +29,6 @@ export const MarketDetails: React.FC = () => {
|
||||
if (!configs) return null;
|
||||
|
||||
const {
|
||||
basePositionNotional,
|
||||
tickSize,
|
||||
stepSize,
|
||||
initialMarginFraction,
|
||||
@@ -114,17 +113,11 @@ export const MarketDetails: React.FC = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'base-initial-margin-fraction',
|
||||
label: stringGetter({ key: STRING_KEYS.BASE_INITIAL_MARGIN_FRACTION }),
|
||||
key: 'initial-margin-fraction',
|
||||
label: stringGetter({ key: STRING_KEYS.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 (
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,20 @@
|
||||
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 { StringGetterFunction, STRING_KEYS } from '@/constants/localization';
|
||||
import {
|
||||
DepthChartDatum,
|
||||
DepthChartPoint,
|
||||
DepthChartSeries,
|
||||
SERIES_KEY_FOR_ORDER_SIDE,
|
||||
} from '@/constants/charts';
|
||||
import { StringGetterFunction } from '@/constants/localization';
|
||||
|
||||
import { useBreakpoints } from '@/hooks';
|
||||
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
|
||||
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
|
||||
import { getCurrentMarketConfig, getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
||||
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
|
||||
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
||||
|
||||
import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents';
|
||||
@@ -21,24 +27,25 @@ import {
|
||||
darkTheme,
|
||||
DataProvider,
|
||||
EventEmitterProvider,
|
||||
type EventHandlerParams,
|
||||
} from '@visx/xychart';
|
||||
import { LinearGradient } from '@visx/gradient';
|
||||
import { curveStepAfter } from '@visx/curve';
|
||||
import type { Point } from '@visx/point';
|
||||
import { 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 { Output, OutputType } from '@/components/Output';
|
||||
import { OutputType } from '@/components/Output';
|
||||
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
|
||||
import { DepthChartTooltipContent } from './Tooltip';
|
||||
|
||||
// @ts-ignore
|
||||
const theme = buildChartTheme({
|
||||
...darkTheme,
|
||||
colors: ['var(--color-positive)', 'var(--color-negative)', 'white'], // categorical colors, mapped to series via `dataKey`s
|
||||
colors: ['var(--color-positive)', 'var(--color-negative)', 'var(--color-layer-6)'], // categorical colors, mapped to series via `dataKey`s
|
||||
});
|
||||
|
||||
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
|
||||
@@ -58,30 +65,6 @@ 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,
|
||||
@@ -96,8 +79,6 @@ export const DepthChart = ({
|
||||
const { isMobile } = useBreakpoints();
|
||||
|
||||
// Chart data
|
||||
|
||||
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
||||
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
|
||||
const { stepSizeDecimals, tickSizeDecimals } =
|
||||
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
|
||||
@@ -112,43 +93,15 @@ export const DepthChart = ({
|
||||
midMarketPrice,
|
||||
spread,
|
||||
spreadPercent,
|
||||
} = useMemo(() => {
|
||||
const bids = (orderbook?.bids?.toArray() ?? [])
|
||||
.filter(Boolean)
|
||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
|
||||
|
||||
const asks = (orderbook?.asks?.toArray() ?? [])
|
||||
.filter(Boolean)
|
||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
|
||||
|
||||
const lowestBid = bids[bids.length - 1];
|
||||
const highestBid = bids[0];
|
||||
const lowestAsk = asks[0];
|
||||
const highestAsk = asks[asks.length - 1];
|
||||
|
||||
const midMarketPrice = orderbook?.midPrice;
|
||||
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
|
||||
const spreadPercent = orderbook?.spreadPercent;
|
||||
|
||||
return {
|
||||
bids,
|
||||
asks,
|
||||
lowestBid,
|
||||
highestBid,
|
||||
lowestAsk,
|
||||
highestAsk,
|
||||
midMarketPrice,
|
||||
spread,
|
||||
spreadPercent,
|
||||
};
|
||||
}, [orderbook]);
|
||||
orderbook,
|
||||
} = useOrderbookValuesForDepthChart();
|
||||
|
||||
// Chart state
|
||||
|
||||
const [isPointerPressed, setIsPointerPressed] = useState(false);
|
||||
const [chartPointAtPointer, setChartPointAtPointer] = useState<DepthChartPoint>();
|
||||
|
||||
const isEditingOrder = isPointerPressed && chartPointAtPointer;
|
||||
const isEditingOrder = Boolean(isPointerPressed && chartPointAtPointer);
|
||||
|
||||
const [zoomDomain, setZoomDomain] = useState<undefined | number>();
|
||||
|
||||
@@ -193,12 +146,24 @@ export const DepthChart = ({
|
||||
}, [orderbook, zoomDomain]);
|
||||
|
||||
const getChartPoint = useCallback(
|
||||
({ x: price, y: size }: Point) =>
|
||||
({
|
||||
side: price < midMarketPrice! ? OrderSide.BUY : OrderSide.SELL,
|
||||
(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,
|
||||
price,
|
||||
size,
|
||||
} as DepthChartPoint),
|
||||
} as DepthChartPoint;
|
||||
},
|
||||
[midMarketPrice]
|
||||
);
|
||||
|
||||
@@ -256,7 +221,7 @@ export const DepthChart = ({
|
||||
}}
|
||||
onPointerUp={(point) => point && onChartClick?.(getChartPoint(point))}
|
||||
onPointerMove={(point) => point && setChartPointAtPointer(getChartPoint(point))}
|
||||
onPointerPressedChange={setIsPointerPressed}
|
||||
onPointerPressedChange={(isPointerPressed) => setIsPointerPressed(isPointerPressed)}
|
||||
>
|
||||
<Axis
|
||||
orientation="bottom"
|
||||
@@ -363,7 +328,7 @@ export const DepthChart = ({
|
||||
<Styled.XAxisLabelOutput
|
||||
type={OutputType.Fiat}
|
||||
value={
|
||||
isEditingOrder
|
||||
isEditingOrder && chartPointAtPointer
|
||||
? chartPointAtPointer.price
|
||||
: tooltipData!.nearestDatum?.datum.price
|
||||
}
|
||||
@@ -374,8 +339,8 @@ export const DepthChart = ({
|
||||
[DepthChartSeries.Bids]: 'var(--color-positive)',
|
||||
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
|
||||
}[
|
||||
isEditingOrder
|
||||
? seriesKeyForOrderSide[chartPointAtPointer.side]
|
||||
isEditingOrder && chartPointAtPointer
|
||||
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
|
||||
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
|
||||
]
|
||||
}
|
||||
@@ -389,7 +354,7 @@ export const DepthChart = ({
|
||||
<Styled.YAxisLabelOutput
|
||||
type={OutputType.Asset}
|
||||
value={
|
||||
isEditingOrder
|
||||
isEditingOrder && chartPointAtPointer
|
||||
? chartPointAtPointer.size
|
||||
: tooltipData!.nearestDatum?.datum.depth
|
||||
}
|
||||
@@ -400,8 +365,8 @@ export const DepthChart = ({
|
||||
[DepthChartSeries.Bids]: 'var(--color-positive)',
|
||||
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
|
||||
}[
|
||||
isEditingOrder
|
||||
? seriesKeyForOrderSide[chartPointAtPointer.side]
|
||||
isEditingOrder && chartPointAtPointer
|
||||
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
|
||||
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
|
||||
]
|
||||
}
|
||||
@@ -410,181 +375,22 @@ export const DepthChart = ({
|
||||
}
|
||||
snapTooltipToDatumX={!isEditingOrder}
|
||||
snapTooltipToDatumY={isEditingOrder ? false : isMobile}
|
||||
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>
|
||||
);
|
||||
}}
|
||||
renderTooltip={({ tooltipData, colorScale }) =>
|
||||
chartPointAtPointer && (
|
||||
<DepthChartTooltipContent
|
||||
{...{
|
||||
tooltipData,
|
||||
colorScale,
|
||||
isEditingOrder,
|
||||
chartPointAtPointer,
|
||||
stringGetter,
|
||||
selectedLocale,
|
||||
stepSizeDecimals,
|
||||
tickSizeDecimals,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</XYChartWithPointerEvents>
|
||||
</EventEmitterProvider>
|
||||
@@ -0,0 +1,107 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -3,39 +3,27 @@ import { shallowEqual, useSelector } from 'react-redux';
|
||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||
import { curveMonotoneX, curveStepAfter } from '@visx/curve';
|
||||
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
|
||||
import { FundingDirection } from '@/constants/markets';
|
||||
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
|
||||
import { Details, DetailsItem } from '@/components/Details';
|
||||
import { Output, OutputType, ShowSign } from '@/components/Output';
|
||||
import { Output, OutputType } 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';
|
||||
|
||||
enum FundingRateResolution {
|
||||
OneHour = 'OneHour',
|
||||
EightHour = 'EightHour',
|
||||
Annualized = 'Annualized',
|
||||
}
|
||||
|
||||
type FundingChartDatum = {
|
||||
time: number;
|
||||
fundingRate: number;
|
||||
direction: FundingDirection;
|
||||
};
|
||||
import { FundingChartTooltipContent } from './Tooltip';
|
||||
|
||||
const FUNDING_RATE_TIME_RESOLUTION = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
@@ -116,114 +104,20 @@ 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 }) => {
|
||||
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}
|
||||
renderTooltip={({ tooltipData }) => (
|
||||
<FundingChartTooltipContent
|
||||
fundingRateView={fundingRateView}
|
||||
tooltipData={tooltipData}
|
||||
latestDatum={latestDatum}
|
||||
/>
|
||||
)}
|
||||
onTooltipContext={(tooltipContext) => setTooltipContext(tooltipContext)}
|
||||
minZoomDomain={FUNDING_RATE_TIME_RESOLUTION * 4}
|
||||
numGridLines={1}
|
||||
slotEmpty={<LoadingSpace id="funding-chart-loading" />}
|
||||
@@ -290,6 +184,7 @@ 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);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
@@ -13,7 +11,7 @@ import { Button } from '@/components/Button';
|
||||
import { Icon } from '@/components/Icon';
|
||||
import { Link } from '@/components/Link';
|
||||
|
||||
import { useAccounts, useStringGetter, useURLConfigs } from '@/hooks';
|
||||
import { useAccounts, useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
|
||||
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
@@ -27,6 +25,8 @@ export const ChooseWallet = () => {
|
||||
|
||||
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts();
|
||||
|
||||
const { isMobile } = useBreakpoints();
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedWalletType && selectedWalletError && (
|
||||
@@ -56,7 +56,11 @@ export const ChooseWallet = () => {
|
||||
slotLeft={<Styled.Icon iconComponent={wallets[walletType].icon} />}
|
||||
size={ButtonSize.Small}
|
||||
>
|
||||
<div>{stringGetter({ key: wallets[walletType].stringKey })}</div>
|
||||
<div>
|
||||
{walletType !== WalletType.Privy && import.meta.env.VITE_PRIVY_APP_ID
|
||||
? stringGetter({ key: wallets[walletType].stringKey })
|
||||
: `Socials ${isMobile ? '' : '(Email, SMS, etc.)'}`}
|
||||
</div>
|
||||
</Styled.WalletButton>
|
||||
))}
|
||||
</Styled.Wallets>
|
||||
|
||||
@@ -34,11 +34,30 @@ export const ChainSelectMenu = ({ label, selectedChain, onSelectChain }: Element
|
||||
slotBefore: <Styled.Img src={chain.iconUrl} alt="" />,
|
||||
}));
|
||||
|
||||
const selectedOption = chains.find((item) => item.type === selectedChain);
|
||||
const selectedOption = selectedChain === 'coinbase' ? {
|
||||
stringKey: 'Coinbase',
|
||||
iconUrl: '/wallets/coinbase-wallet.png',
|
||||
} : chains.find((item) => item.type === selectedChain);
|
||||
|
||||
const exchanges = [
|
||||
{
|
||||
value: 'coinbase',
|
||||
label: 'Coinbase',
|
||||
onSelect: () => {
|
||||
onSelectChain('coinbase');
|
||||
},
|
||||
slotBefore: <Styled.Img src="/wallets/coinbase-wallet.png" alt="" />,
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<SearchSelectMenu
|
||||
items={[
|
||||
{
|
||||
group: 'exchanges',
|
||||
groupLabel: 'Exchanges',
|
||||
items: exchanges,
|
||||
},
|
||||
{
|
||||
group: 'chains',
|
||||
groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }),
|
||||
|
||||
@@ -38,12 +38,14 @@ import abacusStateManager from '@/lib/abacus';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
import { getNobleChainId, NATIVE_TOKEN_ADDRESS } from '@/lib/squid';
|
||||
import { log } from '@/lib/telemetry';
|
||||
import { parseWalletError } from '@/lib/wallet';
|
||||
import { parseWalletError, truncateAddress } from '@/lib/wallet';
|
||||
|
||||
import { ChainSelectMenu } from './ChainSelectMenu';
|
||||
import { TokenSelectMenu } from './TokenSelectMenu';
|
||||
|
||||
import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt';
|
||||
import { QrCode } from '@/components/QrCode';
|
||||
import { CopyButton } from '@/components/CopyButton';
|
||||
|
||||
type DepositFormProps = {
|
||||
onDeposit?: () => void;
|
||||
@@ -55,8 +57,9 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const [isCoinbase, setIsCoinbase] = useState(false);
|
||||
|
||||
const { evmAddress, signerWagmi, publicClientWagmi } = useAccounts();
|
||||
const { evmAddress, signerWagmi, publicClientWagmi, nobleAddress } = useAccounts();
|
||||
|
||||
const { addTransferNotification } = useLocalNotifications();
|
||||
|
||||
@@ -133,7 +136,10 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
}, [error]);
|
||||
|
||||
const onSelectChain = useCallback((chain: string) => {
|
||||
if (chain) {
|
||||
if (chain === 'coinbase') {
|
||||
setIsCoinbase(true);
|
||||
} else if (chain) {
|
||||
setIsCoinbase(false);
|
||||
abacusStateManager.clearTransferInputValues();
|
||||
abacusStateManager.setTransferValue({
|
||||
field: TransferInputField.chain,
|
||||
@@ -364,9 +370,43 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
return <LoadingSpace id="DepositForm" />;
|
||||
}
|
||||
|
||||
if (isCoinbase) {
|
||||
return (
|
||||
<Styled.Form onSubmit={onSubmit}>
|
||||
<ChainSelectMenu
|
||||
selectedChain={isCoinbase ? 'coinbase' : chainIdStr || undefined}
|
||||
onSelectChain={onSelectChain}
|
||||
/>
|
||||
{nobleAddress && (
|
||||
<>
|
||||
<Styled.ReceiveQr
|
||||
side="bottom"
|
||||
detailItems={[
|
||||
{
|
||||
key: 'nobleAddress',
|
||||
label: "Noble Address",
|
||||
value: nobleAddress,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<QrCode size={432} value={nobleAddress} />
|
||||
</Styled.ReceiveQr>
|
||||
<p>
|
||||
* This address only supports transfers of USDC to the Noble chain.
|
||||
</p>
|
||||
<CopyButton value={nobleAddress} />
|
||||
</>
|
||||
)}
|
||||
</Styled.Form>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Styled.Form onSubmit={onSubmit}>
|
||||
<ChainSelectMenu selectedChain={chainIdStr || undefined} onSelectChain={onSelectChain} />
|
||||
<ChainSelectMenu
|
||||
selectedChain={isCoinbase ? 'coinbase' : chainIdStr || undefined}
|
||||
onSelectChain={onSelectChain}
|
||||
/>
|
||||
<TokenSelectMenu selectedToken={sourceToken || undefined} onSelectToken={onSelectToken} />
|
||||
<Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}>
|
||||
<FormInput
|
||||
@@ -427,3 +467,13 @@ Styled.TransactionInfo = styled.span`
|
||||
Styled.FormInputButton = styled(Button)`
|
||||
${formMixins.inputInnerButton}
|
||||
`;
|
||||
|
||||
Styled.Exchange = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1rem;
|
||||
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
Styled.ReceiveQr = styled(WithDetailsReceipt)`
|
||||
`;
|
||||
|
||||
@@ -108,20 +108,6 @@ export const DepositButtonAndReceipt = ({
|
||||
|
||||
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
|
||||
|
||||
const { toAmount, toAmountMin } = useMemo(() => {
|
||||
if (isCctp) {
|
||||
return {
|
||||
toAmount: summary?.toAmount,
|
||||
toAmountMin: summary?.toAmountMin,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
toAmount: summary?.toAmount && formatUnits(BigInt(summary.toAmount), usdcDecimals),
|
||||
toAmountMin: summary?.toAmountMin && formatUnits(BigInt(summary.toAmountMin), usdcDecimals),
|
||||
};
|
||||
}
|
||||
}, [isCctp, summary]);
|
||||
|
||||
const submitButtonReceipt = [
|
||||
{
|
||||
key: 'expected-deposit-amount',
|
||||
@@ -130,7 +116,7 @@ export const DepositButtonAndReceipt = ({
|
||||
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
|
||||
</span>
|
||||
),
|
||||
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmount} />,
|
||||
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />,
|
||||
subitems: [
|
||||
{
|
||||
key: 'minimum-deposit-amount',
|
||||
@@ -140,7 +126,7 @@ export const DepositButtonAndReceipt = ({
|
||||
</span>
|
||||
),
|
||||
value: (
|
||||
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmountMin} />
|
||||
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmountMin} />
|
||||
),
|
||||
tooltip: 'minimum-deposit-amount',
|
||||
},
|
||||
|
||||
@@ -34,6 +34,7 @@ export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps)
|
||||
selectedToken && onSelectToken(selectedToken);
|
||||
},
|
||||
slotBefore: <Styled.Img src={token.iconUrl} alt="" />,
|
||||
tag: resources?.tokenResources?.get(token.type)?.symbol,
|
||||
}));
|
||||
|
||||
return (
|
||||
|
||||
@@ -55,7 +55,7 @@ export const WithdrawButtonAndReceipt = ({
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const { leverage } = useSelector(getSubaccount, shallowEqual) || {};
|
||||
const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
|
||||
const { usdcLabel } = useTokenConfigs();
|
||||
|
||||
@@ -85,26 +85,6 @@ export const WithdrawButtonAndReceipt = ({
|
||||
|
||||
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
|
||||
|
||||
const { toAmount, toAmountMin } = useMemo(() => {
|
||||
if (isCctp) {
|
||||
return {
|
||||
toAmount: summary?.toAmount,
|
||||
toAmountMin: summary?.toAmountMin,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
toAmount:
|
||||
summary?.toAmount &&
|
||||
withdrawToken?.decimals &&
|
||||
formatUnits(BigInt(summary.toAmount), withdrawToken.decimals),
|
||||
toAmountMin:
|
||||
summary?.toAmountMin &&
|
||||
withdrawToken?.decimals &&
|
||||
formatUnits(BigInt(summary.toAmountMin), withdrawToken.decimals),
|
||||
};
|
||||
}
|
||||
}, [isCctp, summary, withdrawToken]);
|
||||
|
||||
const submitButtonReceipt = [
|
||||
{
|
||||
key: 'total-fees',
|
||||
@@ -153,7 +133,7 @@ export const WithdrawButtonAndReceipt = ({
|
||||
{withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
|
||||
</span>
|
||||
),
|
||||
value: <Output type={OutputType.Asset} value={toAmount} fractionDigits={TOKEN_DECIMALS} />,
|
||||
value: <Output type={OutputType.Asset} value={summary?.toAmount} fractionDigits={TOKEN_DECIMALS} />,
|
||||
subitems: [
|
||||
{
|
||||
key: 'minimum-amount-received',
|
||||
@@ -164,7 +144,7 @@ export const WithdrawButtonAndReceipt = ({
|
||||
</span>
|
||||
),
|
||||
value: (
|
||||
<Output type={OutputType.Asset} value={toAmountMin} fractionDigits={TOKEN_DECIMALS} />
|
||||
<Output type={OutputType.Asset} value={summary?.toAmountMin} fractionDigits={TOKEN_DECIMALS} />
|
||||
),
|
||||
tooltip: 'minimum-amount-received',
|
||||
},
|
||||
|
||||
@@ -10,6 +10,9 @@ 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,
|
||||
@@ -35,10 +38,8 @@ 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();
|
||||
@@ -178,7 +179,7 @@ export const AccountMenu = () => {
|
||||
},
|
||||
...(onboardingState === OnboardingState.AccountConnected && hdKey
|
||||
? [
|
||||
!isMainnet && {
|
||||
(!isMainnet || testFlags.showMobileSignInOption) && {
|
||||
value: 'MobileQrSignIn',
|
||||
icon: <Icon iconName={IconName.Qr} />,
|
||||
label: stringGetter({ key: STRING_KEYS.TITLE_SIGN_INTO_MOBILE }),
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
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;
|
||||
}
|
||||
`;
|
||||
@@ -2,6 +2,7 @@ import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => ({
|
||||
@@ -50,6 +51,12 @@ export default defineConfig(({ mode }) => ({
|
||||
svgr({
|
||||
exportAsDefault: true,
|
||||
}),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
publicDir: 'public',
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user