Compare commits
No commits in common. "main" and "extension-integration" have entirely different histories.
main
...
extension-
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ghost-dao-interface",
|
||||
"private": true,
|
||||
"version": "0.5.32",
|
||||
"version": "0.2.15",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -16,7 +16,6 @@
|
||||
"@ethersproject/bignumber": "^5.8.0",
|
||||
"@ethersproject/units": "^5.8.0",
|
||||
"@mui/icons-material": "^6.4.7",
|
||||
"@mui/lab": "6.0.1-beta.36",
|
||||
"@mui/material": "^6.4.7",
|
||||
"@mui/utils": "^6.4.6",
|
||||
"@polkadot-api/metadata-builders": "0.13.0",
|
||||
|
||||
218
pnpm-lock.yaml
218
pnpm-lock.yaml
@ -26,9 +26,6 @@ importers:
|
||||
'@mui/icons-material':
|
||||
specifier: ^6.4.7
|
||||
version: 6.4.7(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||
'@mui/lab':
|
||||
specifier: 6.0.1-beta.36
|
||||
version: 6.0.1-beta.36(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@mui/material':
|
||||
specifier: ^6.4.7
|
||||
version: 6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@ -528,21 +525,6 @@ packages:
|
||||
'@ethersproject/units@5.8.0':
|
||||
resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==}
|
||||
|
||||
'@floating-ui/core@1.7.4':
|
||||
resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==}
|
||||
|
||||
'@floating-ui/dom@1.7.5':
|
||||
resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==}
|
||||
|
||||
'@floating-ui/react-dom@2.1.7':
|
||||
resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@humanfs/core@0.19.1':
|
||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@ -656,18 +638,6 @@ packages:
|
||||
resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@mui/base@5.0.0-beta.70':
|
||||
resolution: {integrity: sha512-Tb/BIhJzb0pa5zv/wu7OdokY9ZKEDqcu1BDFnohyvGCoHuSXbEr90rPq1qeNW3XvTBIbNWHEF7gqge+xpUo6tQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
deprecated: This package has been replaced by @base-ui/react
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/core-downloads-tracker@6.4.7':
|
||||
resolution: {integrity: sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==}
|
||||
|
||||
@ -682,27 +652,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/lab@6.0.1-beta.36':
|
||||
resolution: {integrity: sha512-af9lDmA9SZGEWF1XXk0EVBpfCITk9IKsvh9lLOZGdYaaHfQeCsqxGEDMvNO66j0P8EYoxpyry84LFCJYuLVtVw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@mui/material': ^6.5.0
|
||||
'@mui/material-pigment-css': ^6.5.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@mui/material-pigment-css':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/material@6.4.7':
|
||||
resolution: {integrity: sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@ -733,16 +682,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/private-theming@6.4.9':
|
||||
resolution: {integrity: sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/styled-engine@6.4.6':
|
||||
resolution: {integrity: sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@ -756,19 +695,6 @@ packages:
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
|
||||
'@mui/styled-engine@6.5.0':
|
||||
resolution: {integrity: sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.4.1
|
||||
'@emotion/styled': ^11.3.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
|
||||
'@mui/system@6.4.7':
|
||||
resolution: {integrity: sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@ -785,22 +711,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/system@6.5.0':
|
||||
resolution: {integrity: sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/types@7.2.21':
|
||||
resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==}
|
||||
peerDependencies:
|
||||
@ -809,14 +719,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/types@7.2.24':
|
||||
resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/utils@6.4.6':
|
||||
resolution: {integrity: sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@ -827,16 +729,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@mui/utils@6.4.9':
|
||||
resolution: {integrity: sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@noble/ciphers@1.2.1':
|
||||
resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
@ -3531,23 +3423,6 @@ snapshots:
|
||||
'@ethersproject/constants': 5.8.0
|
||||
'@ethersproject/logger': 5.8.0
|
||||
|
||||
'@floating-ui/core@1.7.4':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/dom@1.7.5':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.4
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/react-dom@2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.5
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
'@humanfs/node@0.16.6':
|
||||
@ -3740,20 +3615,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@mui/base@5.0.0-beta.70(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@floating-ui/react-dom': 2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@mui/types': 7.2.24(@types/react@19.0.10)
|
||||
'@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||
'@popperjs/core': 2.11.8
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/core-downloads-tracker@6.4.7': {}
|
||||
|
||||
'@mui/icons-material@6.4.7(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)':
|
||||
@ -3764,23 +3625,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/lab@6.0.1-beta.36(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@mui/base': 5.0.0-beta.70(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@mui/material': 6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||
'@mui/types': 7.2.24(@types/react@19.0.10)
|
||||
'@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0)
|
||||
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.9
|
||||
@ -3804,38 +3648,16 @@ snapshots:
|
||||
|
||||
'@mui/private-theming@6.4.6(@types/react@19.0.10)(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@babel/runtime': 7.26.9
|
||||
'@mui/utils': 6.4.6(@types/react@19.0.10)(react@19.0.0)
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/private-theming@6.4.9(@types/react@19.0.10)(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/styled-engine@6.4.6(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@emotion/cache': 11.14.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/sheet': 1.4.0
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0)
|
||||
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||
|
||||
'@mui/styled-engine@6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@babel/runtime': 7.26.9
|
||||
'@emotion/cache': 11.14.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/sheet': 1.4.0
|
||||
@ -3862,30 +3684,10 @@ snapshots:
|
||||
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@mui/private-theming': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||
'@mui/styled-engine': 6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)
|
||||
'@mui/types': 7.2.24(@types/react@19.0.10)
|
||||
'@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0)
|
||||
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/types@7.2.21(@types/react@19.0.10)':
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/types@7.2.24(@types/react@19.0.10)':
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/utils@6.4.6(@types/react@19.0.10)(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.9
|
||||
@ -3898,18 +3700,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@mui/utils@6.4.9(@types/react@19.0.10)(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@mui/types': 7.2.24(@types/react@19.0.10)
|
||||
'@types/prop-types': 15.7.14
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
react-is: 19.0.0
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@noble/ciphers@1.2.1': {}
|
||||
|
||||
'@noble/ciphers@1.3.0': {}
|
||||
@ -5252,7 +5042,7 @@ snapshots:
|
||||
|
||||
babel-plugin-macros@3.1.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@babel/runtime': 7.26.9
|
||||
cosmiconfig: 7.1.0
|
||||
resolve: 1.22.10
|
||||
|
||||
@ -5448,7 +5238,7 @@ snapshots:
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@babel/runtime': 7.26.9
|
||||
csstype: 3.1.3
|
||||
|
||||
dot-case@3.0.4:
|
||||
|
||||
14
src/App.jsx
14
src/App.jsx
@ -14,7 +14,7 @@ import Sidebar from "./components/Sidebar/Sidebar";
|
||||
import TopBar from "./components/TopBar/TopBar";
|
||||
|
||||
import { shouldTriggerSafetyCheck } from "./helpers";
|
||||
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "./constants";
|
||||
import { isNetworkAvailable } from "./constants";
|
||||
import useTheme from "./hooks/useTheme";
|
||||
import { useUnstableProvider } from "./hooks/ghost";
|
||||
import { dark as darkTheme } from "./themes/dark.js";
|
||||
@ -27,13 +27,9 @@ const BondModalContainer = lazy(() => import("./containers/Bond/BondModal"));
|
||||
const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer"));
|
||||
const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard"));
|
||||
const Faucet = lazy(() => import("./containers/Faucet/Faucet"));
|
||||
const Wrapper = lazy(() => import("./containers/WethWrapper/WethWrapper"));
|
||||
const Dex = lazy(() => import("./containers/Dex/Dex"));
|
||||
const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
|
||||
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
|
||||
const Governance = lazy(() => import("./containers/Governance/Governance"));
|
||||
const ProposalDetails = lazy(() => import("./containers/Governance/ProposalDetails"));
|
||||
const NewProposal = lazy(() => import("./containers/Governance/NewProposal"));
|
||||
|
||||
const PREFIX = "App";
|
||||
|
||||
@ -210,15 +206,9 @@ function App() {
|
||||
<Route path="/bonds" element={<Bonds connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
<Route path="/bonds/:id" element={<BondModalContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
<Route path="/stake" element={<StakeContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId}/>} />
|
||||
{isNetworkLegacy(chainId)
|
||||
? <Route path="/faucet" element={<Faucet config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
: <Route path="/wrapper" element={<Wrapper config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
}
|
||||
<Route path="/faucet" element={<Faucet config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
<Route path="/bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
<Route path="/dex/:name" element={<Dex connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance/create" element={<NewProposal config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||
</>
|
||||
}
|
||||
<Route path="/empty" element={<NotFound
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{"abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"guy","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"guy","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]}
|
||||
@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="uuid-61b3c585-44d6-4fd8-a0e7-6a5e76478eb1" data-name="uuid-70ba15fb-c44e-4945-a02a-8d07f94a9abb" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 505 505">
|
||||
<defs>
|
||||
<style>
|
||||
.uuid-6437a6f0-42e0-4c9c-b0ef-b632253e989a {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.uuid-f6980b42-338a-476b-b4b3-c6f8474bfe6a {
|
||||
fill: #0b8311;
|
||||
}
|
||||
|
||||
.uuid-cee5d69f-ecfa-41d4-b22b-62a3555cf216 {
|
||||
fill: #3ab83a;
|
||||
}
|
||||
|
||||
.uuid-8c3bab1c-21e8-4834-aebc-42a28aaec68e {
|
||||
fill: #146714;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g fill="#222" data-name="uuid-3af91251-9afa-43cd-b331-1e6ceef8228b">
|
||||
<g id="uuid-38146a20-6896-4919-8709-3b8aa9b9b9de" data-name="uuid-bca144dc-fff0-427e-b9b1-33b0d4491804">
|
||||
<g id="uuid-01777163-9cb4-4f63-8018-462f63cda4ae" data-name="uuid-d1a1e453-da74-40e2-920d-7662cd9352f5">
|
||||
<path d="m245.2,485.4c-64.14,0-124.44-24.99-169.8-70.36C30.05,369.68,5.05,309.36,5,245.2c0-64.15,24.99-124.46,70.36-169.84C120.73,29.98,181.05,5,245.2,5s124.47,24.99,169.84,70.36c45.37,45.37,70.36,105.69,70.36,169.84s-24.99,124.47-70.36,169.84-105.69,70.36-169.84,70.36h0Z"/>
|
||||
<path class="uuid-6437a6f0-42e0-4c9c-b0ef-b632253e989a" d="m245.2,10c31.75,0,62.55,6.22,91.54,18.48,28.01,11.85,53.16,28.81,74.77,50.41,21.61,21.61,38.57,46.76,50.41,74.77,12.26,28.99,18.48,59.79,18.48,91.54s-6.22,62.55-18.48,91.54c-11.85,28.01-28.81,53.16-50.41,74.77-21.61,21.61-46.76,38.57-74.77,50.41-28.99,12.26-59.79,18.48-91.54,18.48s-62.54-6.22-91.52-18.48c-28-11.85-53.14-28.81-74.74-50.41s-38.56-46.76-50.41-74.77c-12.27-28.99-18.5-59.79-18.52-91.54,0-31.75,6.22-62.55,18.48-91.54,11.85-28.01,28.81-53.16,50.41-74.77,21.61-21.61,46.76-38.57,74.77-50.41,28.99-12.26,59.79-18.48,91.54-18.48M245.21,0C109.8,0,0,109.8,0,245.2c.1,135.4,109.8,245.2,245.2,245.2s245.2-109.8,245.2-245.2S380.6,0,245.2,0h0Z"/>
|
||||
</g>
|
||||
<g id="uuid-91c9d373-1cb9-4018-9a6a-66437f262f1c" data-name="uuid-863b3aee-71cf-488d-8b5a-a2d97fac93c4">
|
||||
<path id="uuid-538e9787-13a3-4271-8587-11f8527f9a71" data-name="uuid-10bd56be-0db7-4e24-bb20-a24d1c3cbcb7" class="uuid-cee5d69f-ecfa-41d4-b22b-62a3555cf216" d="m144.29,259.73c35.56,18.89,72.67,38.65,101.33,53.95l100.5-53.95c-36.39,54.06-66.71,99.06-100.5,148.87-33.85-49.7-71.23-104.54-101.33-148.87Zm3.87-14.91l97.57-52.07,96.3,51.69-96.25,52.12-97.63-51.74h.01Zm97.46-68.75l-101.33,53.34,100.89-147.71,100.94,148.04-100.5-53.67h0Z"/>
|
||||
<path id="uuid-4aa91baa-53f1-4f63-abbe-cf753f8525a0" data-name="uuid-4c5fd78b-71fb-4d56-83d4-a2022fef9f50" class="uuid-f6980b42-338a-476b-b4b3-c6f8474bfe6a" d="m245.61,313.68l100.5-53.95c-36.39,54.06-100.5,148.87-100.5,148.87v-94.92h0Zm.11-120.93l96.3,51.69-96.25,52.12-.05-103.81h0Zm-.11-16.68l-.44-94.37,100.94,148.04-100.5-53.67h0Z"/>
|
||||
<path id="uuid-3febb173-485e-4b4e-92b6-b05cc7b0412b" data-name="uuid-3f7cae2c-d7bb-44d6-a6e9-cd80adc2aeac" class="uuid-f6980b42-338a-476b-b4b3-c6f8474bfe6a" d="m148.15,244.82l97.58,8.01,96.3-8.34-96.25,52.13-97.63-51.8h0Z"/>
|
||||
<path id="uuid-df565d0e-a3cf-452f-80f6-2e6ea5ca4f56" data-name="uuid-e69b7837-4cf3-4793-a865-a26ad4121390" class="uuid-8c3bab1c-21e8-4834-aebc-42a28aaec68e" d="m245.72,252.83l96.3-8.34-96.25,52.13-.05-43.79h0Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
@ -1,36 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="uuid-39ebc51d-db02-45d2-ad4b-e7ae31bb915b" data-name="Ethereum" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||
<defs>
|
||||
<style>
|
||||
.uuid-e080659c-91a4-4965-9905-603394020b2c {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.uuid-ca9afedd-40fd-4006-99f9-d2c3cf46bc21 {
|
||||
fill: #627eea;
|
||||
}
|
||||
|
||||
.uuid-bb37a35d-b2ce-440e-86f6-0142a60ec718 {
|
||||
fill: rgba(255, 255, 255, .2);
|
||||
}
|
||||
|
||||
.uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50 {
|
||||
fill: rgba(255, 255, 255, .6);
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="uuid-c49657cc-831e-4816-83c0-b3ceca5eead0" data-name="Ethereum">
|
||||
<g id="uuid-559a2235-ac8a-4942-be49-ac3193ebd3cd" data-name="EthereumBackground">
|
||||
<circle class="uuid-ca9afedd-40fd-4006-99f9-d2c3cf46bc21" cx="125" cy="125" r="122.5"/>
|
||||
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.67,22.66,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85-52.8,35.15-84.85,35.15-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||
</g>
|
||||
<g id="uuid-0bd28738-f2c4-4934-8365-8668de7b64ac" data-name="EthereumIcon">
|
||||
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m128.89,31.25v69.3l58.57,26.17-58.57-95.47Z"/>
|
||||
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m128.89,31.25l-58.58,95.47,58.58-26.17V31.25Z"/>
|
||||
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m128.89,171.62v47.09l58.61-81.09-58.61,34Z"/>
|
||||
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m128.89,218.71v-47.09l-58.58-33.99,58.58,81.09Z"/>
|
||||
<path class="uuid-bb37a35d-b2ce-440e-86f6-0142a60ec718" d="m128.89,160.73l58.57-34.01-58.57-26.16v60.16Z"/>
|
||||
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m70.31,126.72l58.58,34.01v-60.16l-58.58,26.16Z"/>
|
||||
</g>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 32 32">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<circle cx="16" cy="16" r="16" fill="#627EEA"/>
|
||||
<g fill="#FFF" fill-rule="nonzero">
|
||||
<path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/>
|
||||
<path d="M16.498 4L9 16.22l7.498-3.35z"/>
|
||||
<path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/>
|
||||
<path d="M16.498 27.995v-6.028L9 17.616z"/>
|
||||
<path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/>
|
||||
<path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 617 B |
36
src/assets/tokens/wETH.svg
Normal file
36
src/assets/tokens/wETH.svg
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="uuid-39ebc51d-db02-45d2-ad4b-e7ae31bb915b" data-name="Ethereum" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||
<defs>
|
||||
<style>
|
||||
.uuid-e080659c-91a4-4965-9905-603394020b2c {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.uuid-ca9afedd-40fd-4006-99f9-d2c3cf46bc21 {
|
||||
fill: #627eea;
|
||||
}
|
||||
|
||||
.uuid-bb37a35d-b2ce-440e-86f6-0142a60ec718 {
|
||||
fill: rgba(255, 255, 255, .2);
|
||||
}
|
||||
|
||||
.uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50 {
|
||||
fill: rgba(255, 255, 255, .6);
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="uuid-c49657cc-831e-4816-83c0-b3ceca5eead0" data-name="Ethereum">
|
||||
<g id="uuid-559a2235-ac8a-4942-be49-ac3193ebd3cd" data-name="EthereumBackground">
|
||||
<circle class="uuid-ca9afedd-40fd-4006-99f9-d2c3cf46bc21" cx="125" cy="125" r="122.5"/>
|
||||
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.67,22.66,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85-52.8,35.15-84.85,35.15-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||
</g>
|
||||
<g id="uuid-0bd28738-f2c4-4934-8365-8668de7b64ac" data-name="EthereumIcon">
|
||||
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m128.89,31.25v69.3l58.57,26.17-58.57-95.47Z"/>
|
||||
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m128.89,31.25l-58.58,95.47,58.58-26.17V31.25Z"/>
|
||||
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m128.89,171.62v47.09l58.61-81.09-58.61,34Z"/>
|
||||
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m128.89,218.71v-47.09l-58.58-33.99,58.58,81.09Z"/>
|
||||
<path class="uuid-bb37a35d-b2ce-440e-86f6-0142a60ec718" d="m128.89,160.73l58.57-34.01-58.57-26.16v60.16Z"/>
|
||||
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m70.31,126.72l58.58,34.01v-60.16l-58.58,26.16Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@ -9,18 +9,18 @@ const classes = {
|
||||
|
||||
const StyledMuiChip = styled(MuiChip, {
|
||||
shouldForwardProp: prop => prop !== "template" && prop !== "strong",
|
||||
})(({ theme, template, background, strong }) => {
|
||||
})(({ theme, template, strong }) => {
|
||||
return {
|
||||
[`&.${classes.chip}`]: {
|
||||
height: "21px",
|
||||
borderRadius: "16px",
|
||||
backgroundColor: background ? background : template
|
||||
backgroundColor: template
|
||||
? template === "purple"
|
||||
? theme.colors.special["olyZaps"]
|
||||
: template === "gray"
|
||||
? theme.colors.gray[500]
|
||||
: template === "darkGray"
|
||||
? theme.colors.primary[300]
|
||||
? theme.colors.gray[600]
|
||||
: theme.colors.feedback[template]
|
||||
: theme.palette.mode === "light"
|
||||
? theme.colors.gray[90]
|
||||
@ -34,7 +34,7 @@ const StyledMuiChip = styled(MuiChip, {
|
||||
: template === "gray"
|
||||
? theme.colors.gray[10]
|
||||
: template === "darkGray"
|
||||
? theme.colors.gray[800]
|
||||
? theme.colors.gray[90]
|
||||
: theme.colors.gray[600],
|
||||
fontWeight: strong ? 700 : 450,
|
||||
},
|
||||
@ -42,8 +42,8 @@ const StyledMuiChip = styled(MuiChip, {
|
||||
};
|
||||
});
|
||||
|
||||
const Chip = ({ background, template, strong = false, ...props }) => {
|
||||
return <StyledMuiChip className={classes.chip} background={background} template={template} strong={strong} {...props} />;
|
||||
const Chip = ({ template, strong = false, ...props }) => {
|
||||
return <StyledMuiChip className={classes.chip} template={template} strong={strong} {...props} />;
|
||||
};
|
||||
|
||||
export default Chip;
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
import { Box, LinearProgress as MuiLinearProgress } from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
const PREFIX = "MuiLinearProgress";
|
||||
|
||||
const classes = {
|
||||
chip: `${PREFIX}-bar`,
|
||||
};
|
||||
|
||||
const StyledMuiLinearProgress = styled(MuiLinearProgress, {
|
||||
shouldForwardProp: (prop) => prop !== "barBackground" && prop !== 'barColor' && prop !== 'height'
|
||||
})(({ theme, barColor, barBackground, height }) => ({
|
||||
height: height || 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: barBackground || theme.palette.grey[300],
|
||||
'& .MuiLinearProgress-bar': {
|
||||
backgroundColor: barColor || theme.palette.primary.main
|
||||
}
|
||||
}));
|
||||
|
||||
const LinearProgressBar = (props) => {
|
||||
return (
|
||||
<Box sx={{ position: 'relative', width: '100%', py: 1 }}>
|
||||
<StyledMuiLinearProgress {...props} />
|
||||
{props.target && <Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: `${Math.min(Math.max(props.target, 0), 100)}%`,
|
||||
top: props.targetTop || 0,
|
||||
bottom: props.targetBottom || 0,
|
||||
width: props.targetWidth || "2px",
|
||||
backgroundColor: props.targetBackgroundColor || 'white',
|
||||
}}
|
||||
></Box>}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default LinearProgressBar;
|
||||
@ -1,74 +0,0 @@
|
||||
import { Box, MenuItem, Select as MuiSelect, Typography, useTheme } from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
|
||||
const StyledSelectInput = styled(MuiSelect, {
|
||||
shouldForwardProp: prop => prop !== "inputWidth"
|
||||
})(({ theme, inputWidth }) => ({
|
||||
width: "100%",
|
||||
"& .MuiSelect-select": {
|
||||
padding: 0,
|
||||
height: "24px !important",
|
||||
minHeight: "24px !important",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontSize: "18px",
|
||||
fontWeight: 500,
|
||||
paddingRight: "24px",
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: "none",
|
||||
},
|
||||
"& .MuiSelect-icon": {
|
||||
right: 0,
|
||||
position: "absolute",
|
||||
color: theme.colors.gray[500],
|
||||
}
|
||||
}));
|
||||
|
||||
const Select = ({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
inputWidth,
|
||||
renderValue = null,
|
||||
width = "100%",
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
width={width}
|
||||
sx={{ backgroundColor: theme.colors.gray[750] }}
|
||||
borderRadius="12px"
|
||||
padding="15px"
|
||||
>
|
||||
{label && (
|
||||
<Typography color={theme.colors.gray[500]} fontSize="12px" marginBottom="8px">
|
||||
{label}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<StyledSelectInput
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
inputWidth={inputWidth}
|
||||
IconComponent={KeyboardArrowDownIcon}
|
||||
renderValue={renderValue}
|
||||
displayEmpty
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<MenuItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</StyledSelectInput>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@ -24,8 +24,6 @@ import TelegramIcon from '@mui/icons-material/Telegram';
|
||||
import HowToVoteIcon from '@mui/icons-material/HowToVote';
|
||||
import HubIcon from '@mui/icons-material/Hub';
|
||||
import PublicIcon from '@mui/icons-material/Public';
|
||||
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
||||
import GavelIcon from '@mui/icons-material/Gavel';
|
||||
import ForumIcon from '@mui/icons-material/Forum';
|
||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
||||
import BookIcon from '@mui/icons-material/Book';
|
||||
@ -39,7 +37,7 @@ import BondIcon from "../Icon/BondIcon";
|
||||
import StakeIcon from "../Icon/StakeIcon";
|
||||
import WrapIcon from "../Icon/WrapIcon";
|
||||
|
||||
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "../../constants";
|
||||
import { isNetworkAvailable } from "../../constants";
|
||||
import { AVAILABLE_DEXES } from "../../constants/dexes";
|
||||
import { ECOSYSTEM } from "../../constants/ecosystem";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
@ -48,7 +46,6 @@ import BondDiscount from "../../containers/Bond/components/BondDiscount";
|
||||
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
import ShowerIcon from '@mui/icons-material/Shower';
|
||||
import WifiProtectedSetupIcon from '@mui/icons-material/WifiProtectedSetup';
|
||||
|
||||
import { useTokenSymbol } from "../../hooks/tokens";
|
||||
import { useFtsoPrice, useGhstPrice, useGhostedSupplyPrice } from "../../hooks/prices";
|
||||
@ -114,10 +111,6 @@ const NavContent = ({ chainId, addressChainId }) => {
|
||||
{isNetworkAvailable(chainId, addressChainId) &&
|
||||
<>
|
||||
<NavItem icon={DashboardIcon} label={`Dashboard`} to="/dashboard" />
|
||||
{isNetworkLegacy(chainId)
|
||||
? <NavItem icon={ShowerIcon} label={`Faucet`} to="/faucet" />
|
||||
: <NavItem icon={WifiProtectedSetupIcon} label={`Wrapper`} to="/wrapper" />
|
||||
}
|
||||
<NavItem
|
||||
defaultExpanded
|
||||
icon={BondIcon}
|
||||
@ -154,6 +147,9 @@ const NavContent = ({ chainId, addressChainId }) => {
|
||||
</AccordionDetails>
|
||||
}
|
||||
/>
|
||||
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
|
||||
<NavItem icon={ShowerIcon} label={`Faucet`} to="/faucet" />
|
||||
<NavItem icon={PublicIcon} label={`Bridge`} to="/bridge" />
|
||||
<NavItem
|
||||
icon={CurrencyExchangeIcon}
|
||||
label={`Dex`}
|
||||
@ -178,20 +174,47 @@ const NavContent = ({ chainId, addressChainId }) => {
|
||||
</AccordionDetails>
|
||||
}
|
||||
/>
|
||||
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
|
||||
<NavItem icon={ForkRightIcon} label={`Bridge`} to="/bridge" />
|
||||
{isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to="/governance" />}
|
||||
<Box className="menu-divider">
|
||||
<Divider />
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
|
||||
<NavItem
|
||||
to=''
|
||||
icon={PublicIcon}
|
||||
label={`Ecosystem`}
|
||||
children={
|
||||
<AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||
{ECOSYSTEM.map((item, index) => {
|
||||
return (
|
||||
<Link
|
||||
href={item.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key={index}
|
||||
style={{ textDecoration: "none" }}
|
||||
>
|
||||
<Box display="flex" justifyContent="end">
|
||||
<Typography style={{ display: "flex", alignItems: "center" }} >
|
||||
{item.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</AccordionDetails>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Box className="menu-divider">
|
||||
<Divider />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<NavItem href="http://ecosystem.ghostchain.io" icon={PublicIcon} label={`Ecosystem`} />
|
||||
<NavItem href="http://ghostchain.io/builders" icon={ForumIcon} label={`Forum`} />
|
||||
<NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Docs`} />
|
||||
<NavItem href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts/issues" icon={ErrorOutlineIcon} label={`Git Issues`} />
|
||||
|
||||
@ -7,7 +7,7 @@ import Token from "../Token/Token";
|
||||
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
|
||||
export const StyledInputBase = styled(InputBase, { shouldForwardProp: prop => prop !== "inputWidth" })(
|
||||
const StyledInputBase = styled(InputBase, { shouldForwardProp: prop => prop !== "inputWidth" })(
|
||||
({ inputWidth, inputFontSize }) => ({
|
||||
"& .MuiInputBase-input": {
|
||||
padding: 0,
|
||||
@ -90,7 +90,7 @@ const SwapCard = ({
|
||||
</Box>
|
||||
)}
|
||||
<Box display="flex" flexDirection="row" marginTop="12px" justifyContent="space-between" alignItems="center">
|
||||
<Box display="flex" flexDirection="row" alignItems="center" width={!info && !endString && !usdValue ? "100%" : inputWidth ? inputWidth : "136px"}>
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
<StyledInputBase
|
||||
id={id}
|
||||
sx={{
|
||||
@ -99,7 +99,6 @@ const SwapCard = ({
|
||||
padding: 0,
|
||||
height: "24px",
|
||||
maxWidth: inputWidth || "136px",
|
||||
width: !info && !endString && !usdValue ? "100%" : inputWidth ? inputWidth : "136px",
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
type={inputType}
|
||||
|
||||
@ -28,10 +28,11 @@ const StyledArrow = styled(Box)(
|
||||
},
|
||||
);
|
||||
|
||||
const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick, iconNotNeeded, maxWidth}) => {
|
||||
const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick, iconNotNeeded }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" maxWidth={maxWidth ? maxWidth : "476px"}>
|
||||
<Box display="flex" flexDirection="column" maxWidth="476px">
|
||||
{UpperSwapCard}
|
||||
<Box display="flex" flexDirection="row" justifyContent="center">
|
||||
{!iconNotNeeded && (<StyledArrow
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { SvgIcon, Box } from "@mui/material";
|
||||
import { SvgIcon } from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
import FtsoIcon from "../../assets/tokens/FTSO.svg?react";
|
||||
import StnkIcon from "../../assets/tokens/STNK.svg?react";
|
||||
import GhstIcon from "../../assets/tokens/GHST.svg?react";
|
||||
import DaiIcon from "../../assets/tokens/DAI.svg?react";
|
||||
import EthIcon from "../../assets/tokens/ETH.svg?react";
|
||||
import EtcIcon from "../../assets/tokens/ETC.svg?react";
|
||||
import WethIcon from "../../assets/tokens/wETH.svg?react";
|
||||
import UnknownIcon from "../../assets/tokens/Unknown.svg?react";
|
||||
|
||||
const PREFIX = "Token";
|
||||
@ -24,76 +23,53 @@ const StyledSvgIcon = styled(SvgIcon)(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
export const parseKnownToken = (name) => {
|
||||
let icon;
|
||||
switch (name?.toUpperCase()) {
|
||||
case "FTSO":
|
||||
icon = FtsoIcon;
|
||||
break;
|
||||
case "ECSPR":
|
||||
icon = FtsoIcon;
|
||||
break;
|
||||
case "STNK":
|
||||
icon = StnkIcon;
|
||||
break;
|
||||
case "SCSPR":
|
||||
icon = StnkIcon;
|
||||
break;
|
||||
case "GHST":
|
||||
icon = GhstIcon;
|
||||
break;
|
||||
case "CSPR":
|
||||
icon = GhstIcon;
|
||||
break;
|
||||
case "GDAI":
|
||||
icon = DaiIcon;
|
||||
break;
|
||||
case "DAI":
|
||||
icon = DaiIcon;
|
||||
break;
|
||||
case "ETH":
|
||||
icon = EthIcon;
|
||||
break;
|
||||
case "WETH":
|
||||
icon = EthIcon;
|
||||
break;
|
||||
case "METC":
|
||||
icon = EtcIcon;
|
||||
break;
|
||||
case "WMETC":
|
||||
icon = EtcIcon;
|
||||
break;
|
||||
default:
|
||||
icon = UnknownIcon;
|
||||
const Token = ({ name, viewBox = "0 0 260 260", fontSize = "large", ...props }) => {
|
||||
const parseKnownToken = (name) => {
|
||||
let icon;
|
||||
switch (name?.toUpperCase()) {
|
||||
case "FTSO":
|
||||
icon = FtsoIcon;
|
||||
break;
|
||||
case "ECSPR":
|
||||
icon = FtsoIcon;
|
||||
break;
|
||||
case "STNK":
|
||||
icon = StnkIcon;
|
||||
break;
|
||||
case "SCSPR":
|
||||
icon = StnkIcon;
|
||||
break;
|
||||
case "GHST":
|
||||
icon = GhstIcon;
|
||||
break;
|
||||
case "CSPR":
|
||||
icon = GhstIcon;
|
||||
break;
|
||||
case "GDAI":
|
||||
icon = DaiIcon;
|
||||
break;
|
||||
case "DAI":
|
||||
icon = DaiIcon;
|
||||
break;
|
||||
case "ETH":
|
||||
icon = WethIcon;
|
||||
break;
|
||||
case "WETH":
|
||||
icon = WethIcon;
|
||||
break;
|
||||
default:
|
||||
icon = UnknownIcon;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
const Token = ({ chainTokenName, name, viewBox = "0 0 260 260", fontSize = "large", ...props }) => {
|
||||
return (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" position="relative">
|
||||
<StyledSvgIcon
|
||||
inheritViewBox
|
||||
fontSize={fontSize}
|
||||
component={parseKnownToken(name)}
|
||||
{...props}
|
||||
></StyledSvgIcon>
|
||||
{chainTokenName && (
|
||||
<StyledSvgIcon
|
||||
inheritViewBox
|
||||
component={parseKnownToken(chainTokenName)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
marginLeft: "70%",
|
||||
marginTop: "45%",
|
||||
width: "65%",
|
||||
height: "65%",
|
||||
border: "1px solid #fff",
|
||||
borderRadius: "100%"
|
||||
}}
|
||||
></StyledSvgIcon>
|
||||
)}
|
||||
</Box>
|
||||
<StyledSvgIcon
|
||||
viewBox={viewBox}
|
||||
fontSize={fontSize}
|
||||
component={parseKnownToken(name)}
|
||||
{...props}
|
||||
></StyledSvgIcon>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -38,8 +38,8 @@ const Tooltip = ({ message, children }) => {
|
||||
onResizeCapture={undefined}
|
||||
slotProps={undefined}
|
||||
slots={undefined}
|
||||
>
|
||||
<Paper style={{ boxShadow: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px" }} className="info-tooltip">
|
||||
>
|
||||
<Paper className="info-tooltip">
|
||||
{typeof message === "string" ? (
|
||||
<Typography variant="body1" className="info-tooltip-text">
|
||||
{message}
|
||||
|
||||
@ -9,7 +9,7 @@ import FormControl from '@mui/material/FormControl';
|
||||
import Select from '@mui/material/Select';
|
||||
|
||||
import { isNetworkAvailable } from "../../constants";
|
||||
import { parseKnownToken } from "../../components/Token/Token";
|
||||
import EthIcon from "../../assets/tokens/ETH.svg?react";
|
||||
|
||||
import { useSwitchChain } from 'wagmi';
|
||||
import toast from "react-hot-toast";
|
||||
@ -53,11 +53,11 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
|
||||
}
|
||||
}}
|
||||
>
|
||||
{chains.map((chain, i) => {
|
||||
{chains.map(chain => {
|
||||
return (
|
||||
<MenuItem key={chain.name} value={chain.id}>
|
||||
<Box gap="10px" display="flex" flexDirection="row" alignItems="center">
|
||||
<SvgIcon component={parseKnownToken(chain?.nativeCurrency?.symbol)} inheritViewBox />
|
||||
<SvgIcon component={EthIcon} viewBox="0 0 32 32" />
|
||||
{!small && <Typography>{chain.name}</Typography>}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
|
||||
@ -22,7 +22,7 @@ import { PrimaryButton, SecondaryButton } from "../../Button";
|
||||
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
|
||||
import { formatCurrency, shorten } from "../../../helpers";
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
|
||||
import { DAI_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
|
||||
|
||||
import { useAccount, useDisconnect } from "wagmi";
|
||||
|
||||
@ -156,9 +156,9 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
||||
>
|
||||
<SecondaryButton
|
||||
fullWidth
|
||||
onClick={() => onBtnClick("uniswap", RESERVE_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])}
|
||||
onClick={() => onBtnClick("uniswap", DAI_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])}
|
||||
>
|
||||
<Typography>{`${tokens?.ftso?.symbol}-${tokens?.reserve?.symbol} on Uniswap`}</Typography>
|
||||
<Typography>{`${tokens?.ftso?.symbol}-${tokens?.dai?.symbol} on Uniswap`}</Typography>
|
||||
</SecondaryButton>
|
||||
</Box>
|
||||
|
||||
|
||||
@ -13,24 +13,15 @@ import { ChangeEvent, useState, useEffect } from "react";
|
||||
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||
import { useQuery } from "react-query";
|
||||
import { formatCurrency, formatNumber } from "../../../helpers";
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"
|
||||
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
||||
import { isNetworkLegacy } from "../../../constants";
|
||||
|
||||
import GhostStyledIcon from "../../Icon/GhostIcon";
|
||||
import TokenStack from "../../TokenStack/TokenStack";
|
||||
import { PrimaryButton, SecondaryButton } from "../../Button";
|
||||
|
||||
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
|
||||
import {
|
||||
useNativePrice,
|
||||
useReservePrice,
|
||||
useFtsoPrice,
|
||||
useStnkPrice,
|
||||
useGhstPrice
|
||||
} from "../../../hooks/prices";
|
||||
import { useDaiPrice, useFtsoPrice, useStnkPrice, useGhstPrice } from "../../../hooks/prices";
|
||||
import { useLpValuation } from "../../../hooks/treasury";
|
||||
import { useAccount, useBalance as useNativeBalance, useConfig } from "wagmi";
|
||||
import { useAccount } from "wagmi";
|
||||
|
||||
const addTokenToWallet = async (token, userAddress) => {
|
||||
if (!window.ethereum) return;
|
||||
@ -69,7 +60,6 @@ const BalanceValue = ({
|
||||
|
||||
export const Token = (props) => {
|
||||
const {
|
||||
isNative,
|
||||
symbol,
|
||||
icons,
|
||||
address,
|
||||
@ -78,7 +68,7 @@ export const Token = (props) => {
|
||||
onAddTokenToWallet,
|
||||
expanded,
|
||||
onChangeExpanded,
|
||||
reserveAddress,
|
||||
daiAddress,
|
||||
onClose,
|
||||
isPool
|
||||
} = props;
|
||||
@ -86,7 +76,7 @@ export const Token = (props) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const useLink = (symbol, fromAddress, toAddress, isPool) => {
|
||||
if (symbol.toUpperCase() === "RESERVE") {
|
||||
if (symbol.toUpperCase() === "GDAI") {
|
||||
navigate({ pathname: "/faucet" })
|
||||
} else {
|
||||
navigate({
|
||||
@ -108,11 +98,8 @@ export const Token = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion expanded={isNative ? false : expanded} onChange={onChangeExpanded}>
|
||||
<AccordionSummary
|
||||
sx={{ paddingRight: isNative ? "37.43px" : "" }}
|
||||
expandIcon={isNative ? null : <GhostStyledIcon component={ExpandMoreIcon} color="disabled" />}
|
||||
>
|
||||
<Accordion expanded={expanded} onChange={onChangeExpanded}>
|
||||
<AccordionSummary expandIcon={<GhostStyledIcon component={ExpandMoreIcon} color="disabled" />}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", width: "100%", marginRight: "10px" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
||||
<TokenStack
|
||||
@ -130,7 +117,7 @@ export const Token = (props) => {
|
||||
/>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
{!isNative && <AccordionDetails style={{ margin: "auto", padding: theme.spacing(1, 0) }}>
|
||||
<AccordionDetails style={{ margin: "auto", padding: theme.spacing(1, 0) }}>
|
||||
<Box
|
||||
sx={{ display: "flex", flexDirection: "column", flex: 1, mx: "32px", justifyContent: "center" }}
|
||||
style={{ gap: theme.spacing(1) }}
|
||||
@ -143,14 +130,14 @@ export const Token = (props) => {
|
||||
<Typography>Add to Wallet</Typography>
|
||||
</PrimaryButton>
|
||||
<SecondaryButton
|
||||
onClick={() => useLink(symbol, reserveAddress, address, isPool)}
|
||||
onClick={() => useLink(symbol, daiAddress, address, isPool)}
|
||||
fullWidth
|
||||
>
|
||||
<Typography>Get on {symbol?.toUpperCase() === "RESERVE" ? "Faucet" : "Uniswap"}</Typography>
|
||||
<Typography>Get on {symbol.toUpperCase() === "GDAI" ? "Faucet" : "Uniswap"}</Typography>
|
||||
</SecondaryButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionDetails>}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
@ -160,15 +147,10 @@ const sumObjValues = (obj: Record<string, string> = {}) =>
|
||||
|
||||
export const useWallet = (chainId, userAddress) => {
|
||||
const {
|
||||
data: nativeBalanceRaw,
|
||||
refetch: nativeBalanceRefetch
|
||||
} = useNativeBalance({ address: userAddress });
|
||||
const nativeBalance = new DecimalBigNumber(nativeBalanceRaw?.value ?? 0n, 18);
|
||||
const {
|
||||
balance: reserveBalance,
|
||||
refetch: reserveRefetch,
|
||||
contractAddress: reserveAddress,
|
||||
} = useBalance(chainId, "RESERVE", userAddress);
|
||||
balance: daiBalance,
|
||||
refetch: daiRefetch,
|
||||
contractAddress: daiAddress,
|
||||
} = useBalance(chainId, "GDAI", userAddress);
|
||||
const {
|
||||
balance: ftsoBalance,
|
||||
refetch: ftsoRefetch,
|
||||
@ -185,45 +167,32 @@ export const useWallet = (chainId, userAddress) => {
|
||||
contractAddress: ghstAddress,
|
||||
} = useBalance(chainId, "GHST", userAddress);
|
||||
const {
|
||||
balance: lpReserveFtsoBalance,
|
||||
refetch: lpReserveFtsoRefetch,
|
||||
contractAddress: lpReserveFtsoBalanceAddress,
|
||||
} = useBalance(chainId, "RESERVE_FTSO", userAddress);
|
||||
balance: lpDaiFtsoBalance,
|
||||
refetch: lpDaiFtsoRefetch,
|
||||
contractAddress: lpDaiFtsoBalanceAddress,
|
||||
} = useBalance(chainId, "GDAI_FTSO", userAddress);
|
||||
|
||||
const nativePrice = useNativePrice(chainId);
|
||||
const reservePrice = useReservePrice(chainId);
|
||||
const daiPrice = useDaiPrice(chainId);
|
||||
const ftsoPrice = useFtsoPrice(chainId);
|
||||
const stnkPrice = useStnkPrice(chainId);
|
||||
const ghstPrice = useGhstPrice(chainId);
|
||||
const lpReserveFtsoPrice = useLpValuation(chainId, "RESERVE_FTSO", 1000000000000000000n);
|
||||
const lpDaiFtsoPrice = useLpValuation(chainId, "GDAI_FTSO", 1000000000000000000n);
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||
const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
const { symbol: lpReserveFtsoSymbol } = useTokenSymbol(chainId, "RESERVE_FTSO");
|
||||
const { symbol: lpDaiFtsoSymbol } = useTokenSymbol(chainId, "GDAI_FTSO");
|
||||
|
||||
const tokens = {
|
||||
native: {
|
||||
symbol: nativeSymbol,
|
||||
icons: [nativeSymbol],
|
||||
balance: nativeBalance,
|
||||
price: nativePrice,
|
||||
refetch: nativeBalanceRefetch
|
||||
},
|
||||
reserve: {
|
||||
symbol: reserveSymbol,
|
||||
address: reserveAddress,
|
||||
balance: reserveBalance,
|
||||
price: reservePrice,
|
||||
icons: isNetworkLegacy(chainId) ? ["GDAI"] : [tokenNameConverter(chainId, reserveSymbol)],
|
||||
externalUrl: isNetworkLegacy(chainId)
|
||||
? "https://ghostchain.io/wp-content/uploads/2025/03/gDAI.svg"
|
||||
: "https://ghostchain.io/wp-content/uploads/2025/11/6A-Classic-ETC-Token.svg",
|
||||
refetch: reserveRefetch,
|
||||
dai: {
|
||||
symbol: daiSymbol,
|
||||
address: daiAddress,
|
||||
balance: daiBalance,
|
||||
price: daiPrice,
|
||||
icons: ["GDAI"],
|
||||
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/gDAI.svg",
|
||||
refetch: daiRefetch,
|
||||
},
|
||||
ftso: {
|
||||
symbol: ftsoSymbol,
|
||||
@ -252,15 +221,15 @@ export const useWallet = (chainId, userAddress) => {
|
||||
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/GHST.svg",
|
||||
refetch: ghstRefetch,
|
||||
},
|
||||
reserveFtso: {
|
||||
daiFtso: {
|
||||
isPool: true,
|
||||
symbol: lpReserveFtsoSymbol,
|
||||
address: lpReserveFtsoBalanceAddress,
|
||||
balance: lpReserveFtsoBalance,
|
||||
price: lpReserveFtsoPrice,
|
||||
icons: ["FTSO", isNetworkLegacy(chainId) ? "GDAI" : tokenNameConverter(chainId, reserveSymbol)],
|
||||
symbol: lpDaiFtsoSymbol,
|
||||
address: lpDaiFtsoBalanceAddress,
|
||||
balance: lpDaiFtsoBalance,
|
||||
price: lpDaiFtsoPrice,
|
||||
icons: ["GDAI", "FTSO"],
|
||||
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/uni-v2.svg",
|
||||
refetch: lpReserveFtsoRefetch,
|
||||
refetch: lpDaiFtsoRefetch,
|
||||
}
|
||||
};
|
||||
|
||||
@ -277,12 +246,12 @@ export const useWallet = (chainId, userAddress) => {
|
||||
|
||||
export const Tokens = ({ address, tokens, onClose }) => {
|
||||
const [expanded, setExpanded] = useState(null);
|
||||
const alwaysShowTokens = [tokens.native, tokens.reserve, tokens.ftso, tokens.stnk, tokens.ghst, tokens.reserveFtso];
|
||||
const alwaysShowTokens = [tokens.dai, tokens.ftso, tokens.stnk, tokens.ghst, tokens.daiFtso];
|
||||
|
||||
const tokenProps = (token) => ({
|
||||
...token,
|
||||
expanded: expanded === token.symbol,
|
||||
reserveAddress: tokens.reserve.address,
|
||||
daiAddress: tokens.dai.address,
|
||||
onChangeExpanded: (e, isExpanded) => setExpanded(isExpanded ? token.symbol : null),
|
||||
onAddTokenToWallet: () => addTokenToWallet(token, address),
|
||||
onClose: () => onClose(),
|
||||
@ -291,7 +260,7 @@ export const Tokens = ({ address, tokens, onClose }) => {
|
||||
return (
|
||||
<>
|
||||
{alwaysShowTokens.map((token, i) => (
|
||||
<Token key={i} isNative={i === 0} {...tokenProps(token)} />
|
||||
<Token key={i} {...tokenProps(token)} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,28 +1,8 @@
|
||||
import { defineChain } from 'viem'
|
||||
import { http, fallback, createConfig } from 'wagmi'
|
||||
import { sepolia, hoodi } from 'wagmi/chains'
|
||||
|
||||
const mordor = defineChain({
|
||||
id: 63,
|
||||
name: 'Mordor',
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: 'METC',
|
||||
symbol: 'mETC',
|
||||
},
|
||||
rpcUrls: {
|
||||
default: { http: ['https://rpc.mordor.etccooperative.org'] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: 'Blockscout',
|
||||
url: 'https://etc-mordor.blockscout.com/'
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const config = createConfig({
|
||||
chains: [sepolia, hoodi, mordor],
|
||||
chains: [sepolia, hoodi],
|
||||
transports: {
|
||||
[sepolia.id]: fallback([
|
||||
http('https://ethereum-sepolia-rpc.publicnode.com'),
|
||||
@ -36,10 +16,6 @@ export const config = createConfig({
|
||||
[hoodi.id]: fallback([
|
||||
http('https://rpc.hoodi.ethpandaops.io'),
|
||||
http('https://0xrpc.io/hoodi'),
|
||||
]),
|
||||
[mordor.id]: fallback([
|
||||
http('https://rpc.mordor.etccooperative.org'),
|
||||
http('https://geth-mordor.etc-network.info'),
|
||||
])
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,20 +1,6 @@
|
||||
export enum NetworkId {
|
||||
TESTNET_SEPOLIA = 11155111,
|
||||
TESTNET_HOODI = 560048,
|
||||
TESTNET_MORDOR = 63,
|
||||
}
|
||||
|
||||
export const isGovernanceAvailable = (chainId, addressChainId) => {
|
||||
chainId = addressChainId ? addressChainId : chainId;
|
||||
let exists = false;
|
||||
switch (chainId) {
|
||||
case 11155111:
|
||||
exists = true
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
export const isNetworkAvailable = (chainId, addressChainId) => {
|
||||
@ -27,41 +13,8 @@ export const isNetworkAvailable = (chainId, addressChainId) => {
|
||||
case 560048:
|
||||
exists = true
|
||||
break;
|
||||
case 63:
|
||||
exists = true
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
export const isNetworkLegacy = (chainId) => {
|
||||
let exists = false;
|
||||
switch (chainId) {
|
||||
case 560048:
|
||||
exists = true
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
export const networkAvgBlockSpeed = (chainId) => {
|
||||
let blockSpeed = 12n;
|
||||
switch (chainId) {
|
||||
case 11155111:
|
||||
blockSpeed = 12n
|
||||
break;
|
||||
case 560048:
|
||||
blockSpeed = 12n
|
||||
break;
|
||||
case 63:
|
||||
blockSpeed = 13n
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return blockSpeed
|
||||
}
|
||||
|
||||
@ -1,120 +1,80 @@
|
||||
import { NetworkId } from "../constants";
|
||||
|
||||
export const STAKING_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xd90E63E88282596E1ea33765b41Ba3d650f4aD52",
|
||||
[NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86",
|
||||
[NetworkId.TESTNET_MORDOR]: "0xC25C9C56a89ebd6ef291b415d00ACfa7913c55e7",
|
||||
};
|
||||
|
||||
export const BOND_DEPOSITORY_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xdcE486113280e49ca2fB200258E5Ee1B2D21D495",
|
||||
[NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x7C85cDEddBAd0f50453d373F7332BEa11ECa7BAf",
|
||||
};
|
||||
|
||||
export const DAO_TREASURY_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x93dd30f819403710de7933B79A74C4A42438458D",
|
||||
[NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x5883C8e2259556B534036c7fDF4555E09dE9f243",
|
||||
};
|
||||
|
||||
export const FTSO_DAI_LP_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x1394dC3f7bABaa2F0CA80353648087DAB1BF3fd6",
|
||||
[NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50",
|
||||
[NetworkId.TESTNET_MORDOR]: "0xE6546D12665dB5B22Cb92FB9e0221aE51A57aeaa",
|
||||
};
|
||||
|
||||
export const FTSO_STNK_LP_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", // TBD
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x0000000000000000000000000000000000000000",
|
||||
}
|
||||
|
||||
export const RESERVE_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
|
||||
export const DAI_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x5f63a27a9214a0352F2EF8dAF1eD4974d713192B",
|
||||
[NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
|
||||
};
|
||||
|
||||
export const WETH_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
|
||||
[NetworkId.TESTNET_HOODI]: "0xE69a5c6dd88cA798b93c3C92fc50c51Fd5305eB4",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
|
||||
};
|
||||
|
||||
export const GHST_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xdf2e5306A3dCcfA4e21bbF4226C17Ff5B008dDC4",
|
||||
[NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x14b5787F8a1E62786F50A7998A9b14aa24298423",
|
||||
};
|
||||
|
||||
export const STNK_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x02C296A27eA779d5a16F934337c12062C5E3c0D9",
|
||||
[NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x137bA9403885D8ECEa95AaFBb8734F5a16121bAC",
|
||||
};
|
||||
|
||||
export const FTSO_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xcFedFFEB3FdeCd2196820Ba3b71f3F84A1255f93",
|
||||
[NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033",
|
||||
[NetworkId.TESTNET_MORDOR]: "0xeA170CC0faceC531a6a9e93a28C4330Ac50343a1",
|
||||
};
|
||||
|
||||
export const DISTRIBUTOR_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x8fbF8eB4Fcd451EF62Aee33508D46FE120963194",
|
||||
[NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842",
|
||||
[NetworkId.TESTNET_MORDOR]: "0xaf5e76706520db7fb01096E322940206bf3fce57",
|
||||
};
|
||||
|
||||
export const GHOST_GOVERNANCE_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xDab0c51918E6990d8763FAC8a04AE159e44e0c4f",
|
||||
[NetworkId.TESTNET_HOODI]: "0x1B96B792840d4d19d5097ee007392Ed4d851e64F",
|
||||
};
|
||||
|
||||
export const BONDING_CALCULATOR_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0x4896bFc6256A57Df826d7144E48c9633d51d6319",
|
||||
[NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x0c4C7C49a173E2a3f9Eed93125F3F146D8e17bCb",
|
||||
}
|
||||
|
||||
export const GATEKEEPER_ADDRESSES = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xc85129A097773B7F8970a7364c928C05f265E6A1",
|
||||
[NetworkId.TESTNET_MORDOR]: "0xA59cB4ff90bE2206121aE61eEB68d0AeC7BA095f",
|
||||
}
|
||||
|
||||
export const UNISWAP_V2_ROUTER = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xee567fe1712faf6149d80da1e6934e354124cfe3",
|
||||
[NetworkId.TESTNET_HOODI]: "0xD41daF947c6FFEf344754B99ad09466FBCBb7583",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x90ecf6a29798E3cf31EB7DCE64a372AC40d83F83",
|
||||
};
|
||||
|
||||
export const UNISWAP_V2_FACTORY = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: "0xF62c03E08ada871A0bEb309762E260a7a6a880E6",
|
||||
[NetworkId.TESTNET_HOODI]: "0xF140342cB5C29C1468d91Aee408d7b7271C48b5A",
|
||||
[NetworkId.TESTNET_MORDOR]: "0x909f96C1a436B3386E9962e30f3Ce753070ff524",
|
||||
};
|
||||
|
||||
export const NATIVE_TICKERS = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: [
|
||||
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
|
||||
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
|
||||
],
|
||||
[NetworkId.TESTNET_HOODI]: [
|
||||
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
|
||||
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
|
||||
],
|
||||
[NetworkId.TESTNET_MORDOR]: [
|
||||
"https://api.binance.com/api/v3/ticker/price?symbol=ETCUSDT",
|
||||
"https://api.coinbase.com/v2/prices/ETC-USDT/spot",
|
||||
],
|
||||
}
|
||||
|
||||
export const CEX_TICKERS = {
|
||||
[NetworkId.TESTNET_SEPOLIA]: [
|
||||
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
|
||||
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
|
||||
],
|
||||
[NetworkId.TESTNET_MORDOR]: [
|
||||
"https://api.binance.com/api/v3/ticker/price?symbol=ETCUSDT",
|
||||
"https://api.coinbase.com/v2/prices/ETC-USDT/spot",
|
||||
],
|
||||
}
|
||||
|
||||
@ -18,11 +18,4 @@ export const AVAILABLE_DEXES = {
|
||||
viewBox: "0 0 195 230",
|
||||
},
|
||||
],
|
||||
[NetworkId.TESTNET_MORDOR]: [
|
||||
{
|
||||
name: "Uniswap",
|
||||
icon: UniswapIcon,
|
||||
viewBox: "0 0 195 230",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Box, Tab, Tabs, Typography, Container, useMediaQuery } from "@mui/material";
|
||||
import { Box, Tab, Tabs, Container, useMediaQuery } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import ReactGA from "react-ga4";
|
||||
|
||||
import Paper from "../../components/Paper/Paper";
|
||||
@ -20,6 +21,7 @@ import { useTokenSymbol } from "../../hooks/tokens";
|
||||
|
||||
const Bonds = ({ chainId, address, connect }) => {
|
||||
const [isZoomed] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const [secondsTo, setSecondsTo] = useState(0);
|
||||
|
||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||
@ -27,7 +29,7 @@ const Bonds = ({ chainId, address, connect }) => {
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.send({ hitType: "pageview", page: "/bonds" });
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const { liveBonds } = useLiveBonds(chainId);
|
||||
const totalReserves = useTotalReserves(chainId);
|
||||
@ -57,17 +59,7 @@ const Bonds = ({ chainId, address, connect }) => {
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column">
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Active bonds
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Paper headerText="Active bonds" fullWidth enableBackground>
|
||||
<MetricCollection>
|
||||
<Metric
|
||||
label={`Treasury Balance`}
|
||||
|
||||
@ -177,12 +177,12 @@ const BondInputArea = ({
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
tooltip={`Total amount of ${ftsoSymbol} you will receive from this bond purchase`}
|
||||
tooltip={`The total amount of payout asset you will receive from this bond purchase`}
|
||||
/>
|
||||
|
||||
<DataRow
|
||||
title="Max You Can Buy"
|
||||
tooltip={`Maximum ${ftsoSymbol} that can be purchased in a single transaction. Prevents whales from buying entire bond supply at once.`}
|
||||
tooltip={`The maximum quantity of payout token offered via bonds at this moment in time`}
|
||||
balance={
|
||||
<span>
|
||||
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
|
||||
@ -195,14 +195,14 @@ const BondInputArea = ({
|
||||
<DataRow
|
||||
title="Discount"
|
||||
balance={<BondDiscount discount={bond.discount} textOnly />}
|
||||
tooltip={`The discount (or premium) between ${ftsoSymbol} market price and bond price. Higher discount = better deal for bond buyers.`}
|
||||
/>
|
||||
tooltip={`The bond discount is the percentage difference between ${ftsoSymbol} market value and the bond's price`}
|
||||
/>
|
||||
|
||||
<DataRow
|
||||
title={`Vesting Term`}
|
||||
balance={<BondVesting vesting={bond.vesting} />}
|
||||
tooltip={"Time until bond fully vests and becomes claimable. Vesting period aligns bond buyer with the protocol by encouraging longer-term holding."}
|
||||
/>
|
||||
tooltip={"The duration of the Bond whereby the bond can be claimed in its entirety"}
|
||||
/>
|
||||
|
||||
{recipientAddress !== address && (
|
||||
<DataRow title={`Recipient`} balance={shorten(recipientAddress)} />
|
||||
|
||||
@ -83,17 +83,7 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||
warmupLength={warmupInfo.expiry - epoch.number}
|
||||
setPreClaimConfirmed={() => setPreClaimConfirmed(true)}
|
||||
/>
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Your Bonds
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Paper headerText="Your Bonds" fullWidth enableBackground>
|
||||
<Box display="flex" flexDirection="column" alignItems="center">
|
||||
<Typography variant="h4" align="center" color="textSecondary">
|
||||
Payout Options
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,375 +0,0 @@
|
||||
import { useMemo, useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Link,
|
||||
Skeleton,
|
||||
TableContainer,
|
||||
Table,
|
||||
Paper,
|
||||
TableHead,
|
||||
TableBody,
|
||||
TableRow,
|
||||
TableCell,
|
||||
Modal,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useConfig } from "wagmi";
|
||||
import { ss58Decode } from "@polkadot-labs/hdkd-helpers";
|
||||
import { toHex } from "@polkadot-api/utils";
|
||||
|
||||
import { PrimaryButton } from "../../components/Button";
|
||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
import SwapCard from "../../components/Swap/SwapCard";
|
||||
import SwapCollection from "../../components/Swap/SwapCollection";
|
||||
|
||||
import { ghost } from "../../hooks/staking";
|
||||
import { useBalance } from "../../hooks/tokens";
|
||||
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import HourglassBottomIcon from '@mui/icons-material/HourglassBottom';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { formatNumber, formatCurrency, timeConverter } from "../../helpers";
|
||||
|
||||
import { BridgeRoute } from "./BridgeRoute";
|
||||
|
||||
const sliceString = (string, first, second) => {
|
||||
if (!string) return "";
|
||||
return string.slice(0, first) + "..." + string.slice(second);
|
||||
}
|
||||
|
||||
export const BridgeCardAction = ({
|
||||
isVerySmallScreen,
|
||||
isSemiSmallScreen,
|
||||
chainId,
|
||||
address,
|
||||
ghstSymbol,
|
||||
gatekeeperAddressEmpty,
|
||||
gatekeeperAddress,
|
||||
evmNetwork,
|
||||
connect,
|
||||
isConfirmed,
|
||||
setIsConfirmed,
|
||||
openBridgeModal,
|
||||
storeTransactionHash,
|
||||
}) => {
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [receiver, setReceiver] = useState("");
|
||||
const [convertedReceiver, setConvertedReceiver] = useState(undefined);
|
||||
const [amount, setAmount] = useState("");
|
||||
|
||||
const config = useConfig();
|
||||
const incomingFee = Number(evmNetwork?.incoming_fee ?? 0n) / 10000000;
|
||||
|
||||
const {
|
||||
balance: ghstBalance,
|
||||
refetch: ghstBalanceRefetch
|
||||
} = useBalance(chainId, "GHST", address);
|
||||
|
||||
const chainName = useMemo(() => {
|
||||
const client = config?.getClient();
|
||||
return client?.chain?.name;
|
||||
}, [config]);
|
||||
|
||||
const chainNativeCurrency = useMemo(() => {
|
||||
const client = config?.getClient();
|
||||
return client?.chain?.nativeCurrency?.symbol;
|
||||
}, [config]);
|
||||
|
||||
const chainExplorerUrl = useMemo(() => {
|
||||
const client = config?.getClient();
|
||||
return client?.chain?.blockExplorers?.default?.url;
|
||||
}, [config]);
|
||||
|
||||
const preparedAmount = useMemo(() => {
|
||||
try {
|
||||
const result = BigInt(parseFloat(amount) * Math.pow(10, 18));
|
||||
if (result > ghstBalance._value) {
|
||||
return ghstBalance._value;
|
||||
}
|
||||
return result;
|
||||
} catch {
|
||||
return 0n;
|
||||
}
|
||||
}, [amount]);
|
||||
|
||||
const amountAfterFee = useMemo(() => {
|
||||
const convertedAmount = parseFloat(amount);
|
||||
if (!convertedAmount) {
|
||||
return 0;
|
||||
}
|
||||
return convertedAmount * (1 - incomingFee / 100);
|
||||
}, [amount, incomingFee]);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
let isDisabled = isPending || gatekeeperAddressEmpty;
|
||||
if (address !== "") {
|
||||
isDisabled = isDisabled
|
||||
|| !convertedReceiver
|
||||
|| preparedAmount === 0n
|
||||
|| ghstBalance._value < preparedAmount;
|
||||
}
|
||||
return isDisabled;
|
||||
}, [isPending, gatekeeperAddressEmpty, address, convertedReceiver, preparedAmount, ghstBalance]);
|
||||
|
||||
const ghostFunds = async () => {
|
||||
setIsPending(true);
|
||||
try {
|
||||
const txHash = await ghost(chainId, address, convertedReceiver, preparedAmount);
|
||||
if (txHash) {
|
||||
storeTransactionHash(txHash, receiver, preparedAmount.toString());
|
||||
}
|
||||
} finally {
|
||||
await ghstBalanceRefetch();
|
||||
setReceiver("");
|
||||
setAmount("");
|
||||
setIsPending(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isConfirmed) {
|
||||
setIsConfirmed(false);
|
||||
ghostFunds();
|
||||
}
|
||||
}, [isConfirmed]);
|
||||
|
||||
const ghostOrConnect = async () => {
|
||||
if (address === "") {
|
||||
connect();
|
||||
} else if (!isConfirmed) {
|
||||
openBridgeModal();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const [publicKey, prefix] = ss58Decode(receiver);
|
||||
if (prefix !== 1995 && prefix !== 1996) {
|
||||
throw new Error("bad prefix");
|
||||
}
|
||||
setConvertedReceiver(toHex(publicKey));
|
||||
} catch {
|
||||
setConvertedReceiver(undefined);
|
||||
}
|
||||
}, [receiver])
|
||||
|
||||
return (
|
||||
<Box width="100%" height="340px" display="flex" flexDirection="column" justifyContent="space-between">
|
||||
<SwapCollection
|
||||
iconNotNeeded
|
||||
maxWidth="100%"
|
||||
UpperSwapCard={<SwapCard
|
||||
id={`bridge-token-receiver`}
|
||||
inputWidth={"100%"}
|
||||
value={convertedReceiver ? sliceString(receiver, 15, -10) : receiver}
|
||||
onChange={event => setReceiver(convertedReceiver ? "" : event.currentTarget.value)}
|
||||
inputProps={{ "data-testid": "fromInput" }}
|
||||
placeholder="GHOST address (sf prefixed)"
|
||||
endString={convertedReceiver
|
||||
? <GhostStyledIcon color="success" viewBox="0 0 25 25" component={CheckCircleIcon} />
|
||||
: undefined
|
||||
}
|
||||
type="text"
|
||||
maxWidth="100%"
|
||||
/>}
|
||||
LowerSwapCard={<SwapCard
|
||||
id={`bridge-token-amount`}
|
||||
inputWidth={"100%"}
|
||||
info={`${formatCurrency(ghstBalance.toString(), 4, ghstSymbol)}`}
|
||||
value={amount}
|
||||
onChange={event => setAmount(event.currentTarget.value)}
|
||||
inputProps={{ "data-testid": "fromInput" }}
|
||||
endString={"Max"}
|
||||
endStringOnClick={() => setAmount(ghstBalance.toString())}
|
||||
maxWidth="100%"
|
||||
/>}
|
||||
/>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
display="flex"
|
||||
gap="10px"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Box width="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{gatekeeperAddressEmpty && (
|
||||
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
<Typography align="justify" mr="10px" variant="body2" color="textSecondary">
|
||||
<em>
|
||||
There is no connected gatekeeper on {chainName} network yet.
|
||||
</em>
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{!gatekeeperAddressEmpty && (
|
||||
<Box width="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Gatekeeper:</Typography>}
|
||||
<Link
|
||||
fontSize="12px"
|
||||
lineHeight="15px"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`${chainExplorerUrl}/token/${gatekeeperAddress}`}
|
||||
>
|
||||
{sliceString(gatekeeperAddress, 10, -8)}
|
||||
</Link>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box width="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
<Box width="100%" display="flex" flexDirection="column" gap="0px">
|
||||
<Box maxWidth="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Bridge Fee:</Typography>}
|
||||
{incomingFee
|
||||
? <Typography fontSize="12px" lineHeight="15px">{`${incomingFee.toFixed(4)}%`}</Typography>
|
||||
: <Skeleton height={15} width="80px" />
|
||||
}
|
||||
</Box>
|
||||
<Box maxWidth="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
|
||||
{incomingFee
|
||||
? <Typography fontSize="12px" lineHeight="15px">{amountAfterFee.toFixed(4)} {ghstSymbol}</Typography>
|
||||
: <Skeleton height={15} width="80px" />
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<BridgeRoute coinName={ghstSymbol} chainTokenName={chainNativeCurrency} tokens={[ghstSymbol]} />
|
||||
</Box>
|
||||
<PrimaryButton
|
||||
fullWidth
|
||||
disabled={isDisabled}
|
||||
loading={isPending}
|
||||
onClick={() => ghostOrConnect()}
|
||||
>
|
||||
{address === "" ? "Connect" : "Bridge" }
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const BridgeCardHistory = ({
|
||||
isSemiSmallScreen,
|
||||
isBigScreen,
|
||||
filteredStoredTransactions,
|
||||
ghstSymbol,
|
||||
blockNumber,
|
||||
finalityDelay,
|
||||
setActiveTxIndex
|
||||
}) => {
|
||||
const isVeryBigScreen = useMediaQuery("(max-width: 1360px)");
|
||||
|
||||
const theme = useTheme();
|
||||
const background = (index) => {
|
||||
return index % 2 === 1 ? "" : theme.colors.gray[750];
|
||||
}
|
||||
|
||||
return (
|
||||
<Box height="320px">
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
className="custom-scrollbar"
|
||||
sx={{
|
||||
height: "320px",
|
||||
overflowY: 'scroll',
|
||||
msOverflowStyle: "thin !important",
|
||||
scrollbarWidth: "thin !important",
|
||||
}}
|
||||
>
|
||||
<Table sx={{ marginTop: "0px" }} stickyHeader aria-label="sticky available transactions">
|
||||
<TableHead>
|
||||
<TableRow sx={{ height: "40px" }}>
|
||||
{!(isBigScreen ^ isVeryBigScreen) && <TableCell align="left" style={{ padding: "0px", paddingLeft: "16px", fontSize: "12px", borderTopLeftRadius: "3px", background: theme.colors.paper.cardHover }}>
|
||||
Transaction
|
||||
</TableCell>}
|
||||
<TableCell align="center" style={{ padding: "0px", fontSize: "12px", background: theme.colors.paper.cardHover }}>
|
||||
Datetime
|
||||
</TableCell>
|
||||
<TableCell align="center" style={{ padding: "0px", fontSize: "12px", borderTopRightRadius: "3px", background: theme.colors.paper.cardHover }}>
|
||||
Status
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredStoredTransactions?.map((obj, idx) => (
|
||||
<TableRow
|
||||
key={idx}
|
||||
sx={{ cursor: "pointer", height: "30px" }}
|
||||
id={idx + `--tx-history`}
|
||||
data-testid={idx + `--tx-history`}
|
||||
onClick={() => setActiveTxIndex(idx)}
|
||||
>
|
||||
{!(isBigScreen ^ isVeryBigScreen) && <TableCell style={{ background: background(idx) }}>
|
||||
<Box display="flex" flexDirection="column" justifyContent="center">
|
||||
<Typography variant="caption">
|
||||
{formatCurrency(
|
||||
new DecimalBigNumber(BigInt(obj.amount), 18).toString(),
|
||||
isSemiSmallScreen ? 3 : 8,
|
||||
ghstSymbol
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
{sliceString(
|
||||
obj.receiverAddress,
|
||||
isSemiSmallScreen ? 5 : 10,
|
||||
isSemiSmallScreen ? -3 : -8
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>}
|
||||
|
||||
<TableCell style={{ background: background(idx) }}>
|
||||
<Box display="flex" flexDirection="column" alignItems="center" paddingLeft="5px">
|
||||
<Typography variant="caption">
|
||||
{new Date(obj.timestamp).toLocaleDateString('en-US')}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
{new Date(obj.timestamp).toLocaleTimeString('en-US')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell style={{ background: background(idx) }}>
|
||||
<Box display="flex" justifyContent="center" alignItems="center">
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
background: Number(blockNumber) - (obj.blockNumber + finalityDelay) < 0
|
||||
? theme.colors.feedback.warning
|
||||
: theme.colors.feedback.success,
|
||||
borderRadius: "100%",
|
||||
boxShadow: "0px 0px 1px black"
|
||||
}}
|
||||
>
|
||||
{Number(blockNumber) - (obj.blockNumber + finalityDelay) < 0 ?
|
||||
<GhostStyledIcon
|
||||
sx={{ width: "15px", height: "15px" }}
|
||||
viewBox="0 0 25 25"
|
||||
component={HourglassBottomIcon}
|
||||
/>
|
||||
:
|
||||
<GhostStyledIcon
|
||||
sx={{ width: "15px", height: "15px" }}
|
||||
viewBox="0 0 25 25"
|
||||
component={CheckIcon}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@ -1,144 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
import { Box, Paper, Grid, Typography, LinearProgress, useTheme } from "@mui/material"
|
||||
|
||||
import Metric from "../../components/Metric/Metric";
|
||||
import Countdown from "../../components/Countdown/Countdown";
|
||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||
import { formatNumber } from "../../helpers";
|
||||
|
||||
export const BridgeHeader = ({
|
||||
totalValidators,
|
||||
disabledValidators,
|
||||
bridgeStability,
|
||||
transactionEta,
|
||||
timeToNextEpoch,
|
||||
maxDelay,
|
||||
isSmallScreen
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const disabledPercentage = useMemo(() => {
|
||||
if (totalValidators === undefined || disabledValidators === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return ((totalValidators - disabledValidators) / totalValidators) * 100;
|
||||
}, [totalValidators, disabledValidators]);
|
||||
|
||||
const validatorsColor = useMemo(() => {
|
||||
if (disabledPercentage < 50) {
|
||||
return theme.colors.validatorsColor.red;
|
||||
}
|
||||
return theme.colors.validatorsColor.green;
|
||||
}, [disabledPercentage, theme]);
|
||||
|
||||
const stabilityColor = useMemo(() => {
|
||||
if (bridgeStability > 80) {
|
||||
return theme.colors.bridgeProgress.success;
|
||||
} else if (bridgeStability > 50) {
|
||||
return theme.colors.bridgeProgress.warning;
|
||||
} else {
|
||||
return theme.colors.bridgeProgress.error;
|
||||
}
|
||||
}, [bridgeStability, theme]);
|
||||
|
||||
const progressBarPostfix = useMemo(() => {
|
||||
if (bridgeStability > 90) {
|
||||
return "✅ Safe";
|
||||
} else if (bridgeStability > 80) {
|
||||
return "✅ Moderate Risk";
|
||||
} else if (bridgeStability > 70) {
|
||||
return "⚠️ High Risk";
|
||||
} else if (bridgeStability > 50) {
|
||||
return "⚠️ Critical Risk";
|
||||
} else {
|
||||
return "❌ Do NOT Bridge";
|
||||
}
|
||||
}, [bridgeStability]);
|
||||
|
||||
const formatTime = (totalSeconds) => {
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const secs = Math.floor(totalSeconds % 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours} hours ${minutes} mins`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes} mins`;
|
||||
} else {
|
||||
return `${secs} secs`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={isSmallScreen ? 4 : 1}>
|
||||
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||
<Metric
|
||||
isLoading={totalValidators === undefined || disabledValidators === undefined}
|
||||
metric={
|
||||
<Typography color={validatorsColor} fontSize="24px" fontWeight="700" lineHeight="33px">
|
||||
{totalValidators} ({formatNumber(disabledPercentage, 0)}% active)
|
||||
</Typography>
|
||||
}
|
||||
label="Total Validators"
|
||||
tooltip="Active and disabled GHOST Validators in the current GHOST Epoch."
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||
<Metric
|
||||
isLoading={timeToNextEpoch === undefined}
|
||||
metric={timeToNextEpoch < 30 ? "Rotating..." : formatTime(timeToNextEpoch)}
|
||||
label="Rotation in"
|
||||
tooltip="Bridge Stability Index refreshes every 10 minutes with new validator blocks; resets each Era when the validator set is updated."
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||
<Metric
|
||||
isLoading={transactionEta === undefined}
|
||||
metric={transactionEta > maxDelay ? "∞" : formatTime(transactionEta)}
|
||||
label="Max Bridge ETA"
|
||||
tooltip="Maximum estimated time for finalizing bridge transactions based on the latest update."
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item gap={2} xs={12} style={{ paddingTop: "0" }} >
|
||||
<Box position="relative" margin="4.5px 0">
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={bridgeStability ?? 0}
|
||||
sx={{
|
||||
borderRadius: "5px",
|
||||
height: "40px",
|
||||
'& .MuiLinearProgress-bar': {
|
||||
backgroundColor: stabilityColor,
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box sx={{
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
position: 'absolute',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>
|
||||
Bridge Stability {bridgeStability
|
||||
? `${formatNumber(bridgeStability, 0)}% ${progressBarPostfix}`
|
||||
: "N/A"
|
||||
}
|
||||
</Typography>
|
||||
<InfoTooltip message={
|
||||
<Box width="220px">
|
||||
<Typography>0% - 50%: ❌ Do NOT Bridge</Typography>
|
||||
<Typography>50% - 70%: ⚠️ Critical Risk</Typography>
|
||||
<Typography>70% - 80%: ⚠️ High Risk</Typography>
|
||||
<Typography>80% - 90%: ✅ Moderate Risk</Typography>
|
||||
<Typography>90% - 100%: ✅ Safe</Typography>
|
||||
</Box>
|
||||
} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@ -1,459 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { Box, Typography, Link, FormControlLabel, Checkbox, useTheme } from "@mui/material";
|
||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
||||
|
||||
import ThumbUpIcon from '@mui/icons-material/ThumbUp';
|
||||
import ThumbDownAltIcon from '@mui/icons-material/ThumbDownAlt';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import AssuredWorkloadIcon from '@mui/icons-material/AssuredWorkload';
|
||||
import AccountBalanceIcon from '@mui/icons-material/AccountBalance';
|
||||
|
||||
import HourglassBottomIcon from '@mui/icons-material/HourglassBottom';
|
||||
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
|
||||
import HandshakeIcon from '@mui/icons-material/Handshake';
|
||||
import PendingIcon from '@mui/icons-material/Pending';
|
||||
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
||||
|
||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||
import Modal from "../../components/Modal/Modal";
|
||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
import { PrimaryButton, TertiaryButton } from "../../components/Button";
|
||||
|
||||
import { formatCurrency } from "../../helpers";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
|
||||
export const BridgeModal = ({
|
||||
providerDetail,
|
||||
currentRecord,
|
||||
activeTxIndex,
|
||||
setActiveTxIndex,
|
||||
authorities,
|
||||
ghstSymbol,
|
||||
hashedArguments,
|
||||
chainExplorerUrl,
|
||||
removeStoredRecord,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [copiedIndex, setCopiedIndex] = useState(null);
|
||||
|
||||
const sliceString = (string, first, second) => {
|
||||
if (!string) return "";
|
||||
return string.slice(0, first) + "..." + string.slice(second);
|
||||
}
|
||||
|
||||
const copyToClipboard = (text, index) => {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
setCopiedIndex(index);
|
||||
setTimeout(() => setCopiedIndex(null) , 800);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
data-testid="transaction-details-modal"
|
||||
maxWidth="476px"
|
||||
headerContent={
|
||||
<Box display="flex" flexDirection="row">
|
||||
<Typography variant="h5">
|
||||
TX Hash
|
||||
<Link
|
||||
sx={{
|
||||
margin: "0px",
|
||||
font: "inherit",
|
||||
letterSpacing: "inherit",
|
||||
textDecoration: "underline",
|
||||
color: theme.colors.gray[10],
|
||||
textUnderlineOffset: "0.23rem",
|
||||
cursor: "pointer",
|
||||
textDecorationThickness: "3px",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
}
|
||||
}}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={currentRecord
|
||||
? `${chainExplorerUrl}/tx/${currentRecord ? currentRecord.transactionHash : ""}`
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{currentRecord ? sliceString(currentRecord.transactionHash, 10, -8) : ""}
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
open={activeTxIndex >= 0}
|
||||
onClose={() => setActiveTxIndex(-1)}
|
||||
minHeight={"100px"}
|
||||
>
|
||||
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
|
||||
{!providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||
<TertiaryButton
|
||||
fullWidth
|
||||
onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}
|
||||
>
|
||||
Get GHOST Connect
|
||||
</TertiaryButton>
|
||||
</Box>}
|
||||
{providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||
{currentRecord?.finalization > 0 && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
transition: "all 0.8s ease",
|
||||
transform: currentRecord?.finalization > 0 && "scale(1.2)",
|
||||
color: currentRecord?.finalization === 0 && theme.colors.primary[300]
|
||||
}}
|
||||
width="120px"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<GhostStyledIcon
|
||||
sx={{
|
||||
width: "35px",
|
||||
height: "35px",
|
||||
animation: currentRecord?.finalization > 0 && 'rotateHourGlass 2s ease-in-out infinite',
|
||||
'@keyframes rotateHourGlass': {
|
||||
'0%': { transform: 'rotate(0deg)' },
|
||||
'15%': { transform: 'rotate(0deg)' },
|
||||
'85%': { transform: 'rotate(180deg)' },
|
||||
'100%': { transform: 'rotate(180deg)' },
|
||||
},
|
||||
}}
|
||||
viewBox="0 0 25 25"
|
||||
component={HourglassBottomIcon}
|
||||
/>
|
||||
<Typography variant="caption">Finalization</Typography>
|
||||
<Typography variant="caption">
|
||||
{(currentRecord?.finalization ?? 0).toString()} blocks left
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<GhostStyledIcon
|
||||
sx={{
|
||||
transition: "all 0.3s ease",
|
||||
opacity: currentRecord?.finalization > 0 && "0.2"
|
||||
}}
|
||||
component={ArrowRightIcon}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
transition: "all 0.3s ease",
|
||||
opacity: currentRecord?.finalization > 0 && "0.2",
|
||||
transform: currentRecord?.finalization === 0 && currentRecord?.clappedPercentage < 50n && "scale(1.2)",
|
||||
color: currentRecord?.clappedPercentage > 50n && theme.colors.primary[300]
|
||||
}}
|
||||
width="120px"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box display="flex" flexDirection="column" justifyContent="center" alignItems="center">
|
||||
{currentRecord?.clappedPercentage < 50n
|
||||
? <GhostStyledIcon
|
||||
sx={{
|
||||
width: "35px",
|
||||
height: "35px",
|
||||
animation: currentRecord?.finalization === 0 && 'bounce 2s ease-in-out infinite',
|
||||
'@keyframes bounce': {
|
||||
'0%, 20%, 50%, 80%, 75%, 100%': { transform: 'translateY(0)' },
|
||||
'40%': { transform: 'translateY(-4px)' },
|
||||
'60%': { transform: 'translateY(-2px)' },
|
||||
},
|
||||
}}
|
||||
viewBox="0 0 25 25"
|
||||
component={AccountBalanceIcon}
|
||||
/>
|
||||
: <GhostStyledIcon sx={{ width: "35px", height: "35px" }} viewBox="0 0 25 25" component={AssuredWorkloadIcon} />
|
||||
}
|
||||
<Typography variant="caption">Capital Backed</Typography>
|
||||
<Typography variant="caption">
|
||||
{(currentRecord?.clappedAmount ?? 0n) / 10n**18n} {ghstSymbol} ({currentRecord?.clappedPercentage ?? 0}%)
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<GhostStyledIcon
|
||||
sx={{ transition: "all 0.3s ease", opacity: currentRecord?.clappedPercentage < 50n && "0.2" }}
|
||||
component={ArrowRightIcon}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
transition: "all 0.3s ease",
|
||||
opacity: currentRecord?.finalization > 0 && "0.2",
|
||||
transform: currentRecord?.finalization === 0 && currentRecord?.clapsPercentage < 50 && "scale(1.2)",
|
||||
color: currentRecord?.clapsPercentage > 50 && theme.colors.primary[300]
|
||||
}}
|
||||
width="120px"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box display="flex" flexDirection="row" justifyContent="center" alignItems="center">
|
||||
{currentRecord?.clapsPercentage < 50
|
||||
? (
|
||||
<>
|
||||
<GhostStyledIcon
|
||||
sx={{
|
||||
width: "35px",
|
||||
height: "35px",
|
||||
animation: currentRecord?.finalization === 0 && 'rotateRightHand 2s ease-in-out infinite',
|
||||
'@keyframes rotateRightHand': {
|
||||
'0%': { transform: 'rotateX(360deg)' },
|
||||
'15%': { transform: 'rotateX(360deg)' },
|
||||
'50%': { transform: 'rotateX(180deg)' },
|
||||
'85%': { transform: 'rotateX(0deg)' },
|
||||
'100%': { transform: 'rotateX(0deg)' },
|
||||
},
|
||||
}}
|
||||
viewBox="0 0 25 25"
|
||||
component={ThumbUpIcon}
|
||||
/>
|
||||
<GhostStyledIcon
|
||||
sx={{
|
||||
width: "35px",
|
||||
height: "35px",
|
||||
animation: currentRecord?.finalization === 0 && 'rotateRightHand 2s ease-in-out infinite',
|
||||
'@keyframes rotateRightHand': {
|
||||
'0%': { transform: 'rotateX(0deg)' },
|
||||
'15%': { transform: 'rotateX(0deg)' },
|
||||
'50%': { transform: 'rotateX(180deg)' },
|
||||
'85%': { transform: 'rotateX(360deg)' },
|
||||
'100%': { transform: 'rotateX(360deg)' },
|
||||
},
|
||||
}}
|
||||
viewBox="0 0 25 25"
|
||||
component={ThumbDownAltIcon}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<GhostStyledIcon
|
||||
sx={{
|
||||
width: "35px",
|
||||
height: "35px",
|
||||
}}
|
||||
viewBox="0 0 25 25"
|
||||
component={HandshakeIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
<Box display="flex" flexDirection="column" justifyContent="center" alignItems="center">
|
||||
<Typography variant="caption">Slow Claps</Typography>
|
||||
<Typography variant="caption">{currentRecord?.numberOfClaps ?? 0} / {authorities?.length ?? 0}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{currentRecord?.finalization === 0 && (
|
||||
<>
|
||||
<GhostStyledIcon
|
||||
sx={{ transition: "all 0.3s ease", opacity: !currentRecord?.applaused && "0.2" }}
|
||||
component={ArrowRightIcon}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
transition: "all 0.3s ease",
|
||||
opacity: !currentRecord?.applaused && "0.2",
|
||||
transform: currentRecord?.applaused && "scale(1.2)",
|
||||
color: currentRecord?.applaused && theme.colors.primary[300]
|
||||
}}
|
||||
width="120px"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<GhostStyledIcon
|
||||
sx={{ width: "35px", height: "35px" }}
|
||||
viewBox="0 0 25 25"
|
||||
component={CheckCircleIcon}
|
||||
/>
|
||||
<Typography variant="caption">Applaused</Typography>
|
||||
<Typography variant="caption">Check Receiver</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>}
|
||||
|
||||
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
|
||||
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||
<Typography variant="body2">GHOST Epoch:</Typography>
|
||||
<Typography variant="body2">{currentRecord?.sessionIndex}</Typography>
|
||||
</Box>
|
||||
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||
<Typography variant="body2">Accepted Bridge Risk:</Typography>
|
||||
<Typography variant="body2">{currentRecord?.bridgeStability}%</Typography>
|
||||
</Box>
|
||||
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||
<Box display="flex" flexDirection="row">
|
||||
<Typography variant="body2">Arguments Hash:</Typography>
|
||||
<InfoTooltip message="A unique identifier for transaction parameters, represented as a hash generated by keccak256(receiver, amount, blockNumber, chainId)." />
|
||||
</Box>
|
||||
<Link
|
||||
style={{ display: "flex", flexDirection: "row", justifyContent: "center", alignItems: "center" }}
|
||||
onClick={() => copyToClipboard(hashedArguments ? hashedArguments : "", 2)}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
{hashedArguments ? sliceString(hashedArguments, 10, -8) : "0x"}
|
||||
</Typography>
|
||||
<GhostStyledIcon
|
||||
sx={{ marginLeft: "5px", width: "12px", height: "12px" }}
|
||||
viewBox="0 0 25 25"
|
||||
component={copiedIndex === 2 ? CheckIcon : ContentPasteIcon}
|
||||
/>
|
||||
</Link>
|
||||
</Box>
|
||||
<hr style={{ width: "100%" }} />
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Typography variant="body2">Receiver Address:</Typography>
|
||||
<Link
|
||||
style={{ display: "flex", flexDirection: "row", justifyContent: "center", alignItems: "center" }}
|
||||
onClick={() => copyToClipboard(currentRecord ? currentRecord.receiverAddress : "", 0)}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
{currentRecord ? sliceString(currentRecord.receiverAddress, 14, -5) : ""}
|
||||
</Typography>
|
||||
<GhostStyledIcon
|
||||
sx={{ marginLeft: "5px", width: "12px", height: "12px" }}
|
||||
viewBox="0 0 25 25"
|
||||
component={copiedIndex === 0 ? CheckIcon : ContentPasteIcon}
|
||||
/>
|
||||
</Link>
|
||||
</Box>
|
||||
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||
<Typography variant="body2">Bridged Amount:</Typography>
|
||||
<Typography variant="body2">{formatCurrency(
|
||||
new DecimalBigNumber(
|
||||
BigInt(currentRecord ? currentRecord.amount : "0"),
|
||||
18
|
||||
).toString(), 9, ghstSymbol)
|
||||
}</Typography>
|
||||
</Box>
|
||||
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||
<Box display="flex" flexDirection="row">
|
||||
<Typography variant="body2">Executed at:</Typography>
|
||||
<InfoTooltip message="The timestamp when the bridge transaction was submitted." />
|
||||
</Box>
|
||||
<Typography variant="body2">{
|
||||
new Date(currentRecord ? currentRecord.timestamp : 0).toLocaleString('en-US')
|
||||
}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" flexDirection="column" gap="5px">
|
||||
<PrimaryButton
|
||||
fullWidth
|
||||
loading={false}
|
||||
onClick={() => removeStoredRecord()}
|
||||
>
|
||||
Erase Record
|
||||
</PrimaryButton>
|
||||
|
||||
<Typography variant="body2" sx={{ fontStyle: "italic" }}>
|
||||
This will permanently remove the bridge transaction record from the session storage, but it will not cancel the bridge transaction.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export const BridgeConfirmModal = ({
|
||||
bridgeStability,
|
||||
isOpen,
|
||||
setClose,
|
||||
handleButtonProceed
|
||||
}) => {
|
||||
const [isBridgingRiskChecked, setIsBridgingRiskChecked] = useState(false);
|
||||
const [isBridgingRecipientChecked, setIsBridgingRecipientChecked] = useState(false);
|
||||
|
||||
const handleProceed = () => {
|
||||
setIsBridgingRiskChecked(false);
|
||||
setIsBridgingRecipientChecked(false);
|
||||
handleButtonProceed();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
maxWidth="450px"
|
||||
minHeight="150px"
|
||||
headerText="Bridge Confirmation"
|
||||
open={isOpen}
|
||||
onClose={setClose}
|
||||
>
|
||||
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
|
||||
<Box width="100%" display="flex" flexDirection="column" alignItems="start">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
data-testid="acknowledge-bridge-stability"
|
||||
checked={isBridgingRiskChecked}
|
||||
onChange={event => setIsBridgingRiskChecked(event.target.checked)}
|
||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<span>
|
||||
{`I acknowledge bridging risk at ${bridgeStability}%.`}
|
||||
<Link
|
||||
sx={{
|
||||
margin: "0px",
|
||||
font: "inherit",
|
||||
letterSpacing: "inherit",
|
||||
textDecoration: "underline",
|
||||
textUnderlineOffset: "0.23rem",
|
||||
cursor: "pointer",
|
||||
textDecorationThickness: "1px",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
}
|
||||
}}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://ghostchain.io/bridge-disclaimer"
|
||||
>
|
||||
Learn more.
|
||||
</Link>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<hr style={{ margin: "10px 0", width: "100%" }} />
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
data-testid="acknowledge-bridge-stability"
|
||||
checked={isBridgingRecipientChecked}
|
||||
onChange={event => setIsBridgingRecipientChecked(event.target.checked)}
|
||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
||||
/>
|
||||
}
|
||||
label="I confirm that recipient address is a self-custodial wallet, not an exchange, third party service, or smart-contract."
|
||||
sx={{ '& .MuiFormControlLabel-label': { textAlign: "justify" } }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<PrimaryButton fullWidth disabled={!isBridgingRiskChecked || !isBridgingRecipientChecked} onClick={handleProceed}>
|
||||
Proceed Bridge
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
import React from "react";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
|
||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
import Token from "../../components/Token/Token";
|
||||
|
||||
export const BridgeRoute = ({ coinName, chainTokenName, tokens }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box width="100%" display="flex" alignItems="center" flexDirection="row" flexWrap="wrap">
|
||||
<Typography>Route:</Typography>
|
||||
<Box display="flex" marginLeft="20px" gap="5px" alignItems="center">
|
||||
{tokens?.map((token, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<RouteHop key={index} theme={theme} token={token} chainTokenName={chainTokenName} />
|
||||
<GhostStyledIcon sx={{ width: "12px", height: "12px" }} component={ArrowForwardIosIcon} />
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
<RouteHop theme={theme} token={coinName} />
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const RouteHop = ({ theme, token, chainTokenName, arrowNeeded }) => {
|
||||
return (
|
||||
<Box
|
||||
display="inline-flex"
|
||||
sx={{ backgroundColor: theme.colors.gray[600] }}
|
||||
borderRadius="6px"
|
||||
paddingX="9px"
|
||||
paddingY="6.5px"
|
||||
alignItems="center"
|
||||
>
|
||||
<Token chainTokenName={chainTokenName} name={token} sx={{ fontSize: "21px" }} />
|
||||
<Typography fontSize="15px" lineHeight="24px" marginLeft="9px">
|
||||
{token}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@ -1,331 +0,0 @@
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Link,
|
||||
LinearProgress,
|
||||
} from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
|
||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||
import { PrimaryButton } from "../../components/Button";
|
||||
|
||||
export const ValidatorTable = ({
|
||||
currentTime,
|
||||
currentBlock,
|
||||
latestCommits,
|
||||
isVerySmallScreen,
|
||||
bridgeStability,
|
||||
providerDetail,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [sortedBy, setsortedBy] = useState(undefined);
|
||||
|
||||
const stabilityColor = useMemo(() => {
|
||||
const red = Math.round(255 * (1 - bridgeStability / 100));
|
||||
const green = Math.round(255 * (bridgeStability / 100));
|
||||
return `rgb(${red}, ${green}, 0)`;
|
||||
}, [bridgeStability]);
|
||||
|
||||
const sortedCommits = useMemo(() => {
|
||||
return latestCommits?.sort((a, b) => {
|
||||
let one = 0;
|
||||
let two = 0;
|
||||
switch (sortedBy) {
|
||||
case -1:
|
||||
one = Number(b.lastUpdated ?? 0n);
|
||||
two = Number(a.lastUpdated ?? 0n);
|
||||
break;
|
||||
case 1:
|
||||
one = Number(a.lastUpdated ?? 0n);
|
||||
two = Number(b.lastUpdated ?? 0n);
|
||||
break;
|
||||
case -2:
|
||||
one = Number(b.lastStoredBlock ?? 0n);
|
||||
two = Number(a.lastStoredBlock ?? 0n);
|
||||
break;
|
||||
case 2:
|
||||
one = Number(a.lastStoredBlock ?? 0n);
|
||||
two = Number(b.lastStoredBlock ?? 0n);
|
||||
break;
|
||||
case -3:
|
||||
one = Number(b.lastStoredBlock ?? 0n);
|
||||
two = Number(a.lastStoredBlock ?? 0n);
|
||||
break;
|
||||
case 3:
|
||||
one = Number(a.lastStoredBlock ?? 0n);
|
||||
two = Number(b.lastStoredBlock ?? 0n);
|
||||
break;
|
||||
case -4:
|
||||
one = b.disabled ? 1 : 0;
|
||||
two = a.disabled ? 1 : 0;
|
||||
break;
|
||||
case 4:
|
||||
one = a.disabled ? 1 : 0;
|
||||
two = b.disabled ? 1 : 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return one - two;
|
||||
}) ?? [];
|
||||
}, [latestCommits, sortedBy]);
|
||||
|
||||
const changeSort = (prevValue, newValue) => {
|
||||
if (prevValue === undefined) {
|
||||
return newValue;
|
||||
}
|
||||
|
||||
const prevValueAbs = Math.abs(prevValue);
|
||||
const newValueAbs = Math.abs(newValue);
|
||||
|
||||
if (prevValueAbs === newValueAbs) {
|
||||
return prevValue * -1;
|
||||
} else {
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box width="100%" height="340px" display="flex" flexDirection="column" justifyContent="space-between" gap="10px">
|
||||
{!providerDetail && <Box sx={{ borderRadius: "15px", background: theme.colors.paper.background }} width="100%" height="100%" display="flex" justifyContent="center">
|
||||
<Box padding="20px 30px" display="flex" flexDirection="column" justifyContent="space-around" alignItems="center">
|
||||
<Typography sx={{ textAlign: "center" }} variant="h6">GHOST Connect is not detected on your browser!</Typography>
|
||||
<Typography sx={{ textAlign: "center" }} variant="body2">Download GHOST Connect browser extension for real-time visibility into validator status and related transaction risks.</Typography>
|
||||
<Typography sx={{ textAlign: "center" }} variant="body2"><b>Important:</b> The GHOST Connect is optional, but be aware that your bridge transaction will succeed or fail irreversibly based on the condition of the validators.</Typography>
|
||||
<PrimaryButton onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}>
|
||||
Get GHOST Connect
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
</Box>}
|
||||
{providerDetail && <TableContainer
|
||||
component={Paper}
|
||||
className="custom-scrollbar"
|
||||
sx={{
|
||||
height: "310px",
|
||||
overflowY: 'scroll',
|
||||
msOverflowStyle: "thin !important",
|
||||
scrollbarWidth: "thin !important",
|
||||
}}
|
||||
>
|
||||
<Table sx={{ marginTop: "0px" }} stickyHeader aria-label="sticky available validators">
|
||||
<TableHead>
|
||||
<TableRow sx={{ height: "40px" }}>
|
||||
<BridgeHeaderTableCell index={0} changeSort={() => setsortedBy(undefined)} value="Validator" background={theme.colors.paper.cardHover} borderTopLeftRadius="3px" />
|
||||
<BridgeHeaderTableCell index={1} sortedBy={sortedBy} changeSort={() => setsortedBy(prev => changeSort(prev, 1))} value="Last Acitve" background={theme.colors.paper.cardHover} tooltip="GHOST Validators must submit block commitments every 10 minutes to remain active." />
|
||||
<BridgeHeaderTableCell index={2} sortedBy={sortedBy} changeSort={() => setsortedBy(prev => changeSort(prev, 2))} value="Block Height" background={theme.colors.paper.cardHover} tooltip="The latest EVM block height reported by the Validator." />
|
||||
<BridgeHeaderTableCell index={3} sortedBy={sortedBy} changeSort={() => setsortedBy(prev => changeSort(prev, 3))} value="Block Delayed" background={theme.colors.paper.cardHover} tooltip="Block delays under 4 hours are safe. Block delays over 4 hours risk bridge transaction failure." />
|
||||
<BridgeHeaderTableCell index={4} sortedBy={sortedBy} changeSort={() => setsortedBy(prev => changeSort(prev, 4))} value="Status" background={theme.colors.paper.cardHover} borderTopRightRadius="3px" tooltip="Active and disabled validators fore the current GHOST Epoch." />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{latestCommits?.map((commit, index) => {
|
||||
return (
|
||||
<ValidatorRow
|
||||
key={index}
|
||||
colors={theme.colors}
|
||||
currentTime={currentTime}
|
||||
currentBlock={currentBlock}
|
||||
index={index}
|
||||
commit={commit}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>}
|
||||
<Box marginTop="5px" display="flex" flexDirection="row" justifyContent="center" alignItems="center">
|
||||
<Link
|
||||
underline="always"
|
||||
fontSize="12px"
|
||||
lineHeight="15px"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://telemetry.ghostchain.io"
|
||||
>
|
||||
See Validator Telemetry
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const BridgeHeaderTableCell = ({
|
||||
index=0,
|
||||
sortedBy=undefined,
|
||||
changeSort,
|
||||
align="center",
|
||||
borderTopRightRadius="0px",
|
||||
borderTopLeftRadius="0px",
|
||||
borderBottomRightRadius="0px",
|
||||
borderBottomLeftRadius="0px",
|
||||
background="transparent",
|
||||
fontSize="12px",
|
||||
padding="0px",
|
||||
tooltip,
|
||||
value
|
||||
}) => {
|
||||
return (
|
||||
<TableCell
|
||||
align={align}
|
||||
style={{
|
||||
borderTopRightRadius,
|
||||
borderTopLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
background,
|
||||
fontSize,
|
||||
padding,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<Box onClick={changeSort} display="flex" justifyContent="center" alignItems="center">
|
||||
{value}
|
||||
{tooltip
|
||||
? Math.abs(sortedBy) !== index
|
||||
? <InfoTooltip message={tooltip} />
|
||||
: <GhostStyledIcon
|
||||
sx={{ width: "12px", height: "12px" }}
|
||||
viewBox="0 0 25 25"
|
||||
component={sortedBy > 0 ? KeyboardArrowDownIcon : KeyboardArrowUpIcon}
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
</Box>
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
|
||||
const BridgeTableCell = ({
|
||||
align="center",
|
||||
borderTopRightRadius="0px",
|
||||
borderTopLeftRadius="0px",
|
||||
borderBottomRightRadius="0px",
|
||||
borderBottomLeftRadius="0px",
|
||||
background="transparent",
|
||||
fontSize="10px",
|
||||
padding="0px",
|
||||
value
|
||||
}) => {
|
||||
return (
|
||||
<TableCell
|
||||
align={align}
|
||||
style={{
|
||||
borderTopRightRadius,
|
||||
borderTopLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
background,
|
||||
fontSize,
|
||||
padding,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
|
||||
const ValidatorRow = ({
|
||||
colors,
|
||||
index,
|
||||
currentTime,
|
||||
currentBlock,
|
||||
commit,
|
||||
}) => {
|
||||
const background = index % 2 === 1 ? "" : colors.gray[750];
|
||||
return (
|
||||
<TableRow
|
||||
sx={{ height: "30px" }}
|
||||
id={index + `--vaidator`}
|
||||
data-testid={index + `--vaidator`}
|
||||
>
|
||||
<BridgeTableCell align="center" value={sliceString(commit.validator, 10, 45)} background={background} />
|
||||
<BridgeTableCell align="center" value={getTimeAgo(currentTime - (Number(commit?.lastUpdated) ?? 0))} background={background} />
|
||||
<BridgeTableCell align="center" value={(commit?.lastStoredBlock ?? 0n).toLocaleString('en-US')} background={background} />
|
||||
<BridgeTableCell align="center" value={blockDelayIcon(colors, currentBlock - (commit?.lastStoredBlock ?? 0n))} background={background} />
|
||||
<BridgeTableCell align="center" value={statusIcon(colors, commit?.disabled)} background={background} />
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const sliceString = (string, first, second) => {
|
||||
if (!string) return "";
|
||||
return string.slice(0, first) + "..." + string.slice(second);
|
||||
}
|
||||
|
||||
const getTimeAgo = (timestampDiff) => {
|
||||
const seconds = Math.floor(timestampDiff / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
|
||||
if (seconds < 60) return `${seconds}s ago`;
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
|
||||
return "long ago";
|
||||
}
|
||||
|
||||
const blockDelayIcon = (colors, timestampDiff) => {
|
||||
let color = colors.feedback.error;
|
||||
let icon = CancelIcon;
|
||||
|
||||
if (timestampDiff < 900000n) {
|
||||
color = colors.feedback.success;
|
||||
icon = CheckCircleIcon;
|
||||
} else if (timestampDiff < 2700000n) {
|
||||
color = colors.feedback.warning;
|
||||
icon = WarningIcon;
|
||||
}
|
||||
|
||||
return (
|
||||
<GhostStyledIcon
|
||||
sx={{
|
||||
marginLeft: "5px",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
fill: color
|
||||
}}
|
||||
viewBox="0 0 25 25"
|
||||
component={icon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const statusIcon = (colors, disabled) => {
|
||||
let color = colors.feedback.success;
|
||||
let icon = CheckCircleIcon;
|
||||
|
||||
if (disabled === true) {
|
||||
color = colors.feedback.error;
|
||||
icon = CancelIcon;
|
||||
}
|
||||
|
||||
return (
|
||||
<GhostStyledIcon
|
||||
sx={{
|
||||
marginLeft: "5px",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
fill: color
|
||||
}}
|
||||
viewBox="0 0 25 25"
|
||||
component={icon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -26,7 +26,7 @@ import { Tab, Tabs } from "../../components/Tabs/Tabs";
|
||||
import {
|
||||
UNISWAP_V2_ROUTER,
|
||||
UNISWAP_V2_FACTORY,
|
||||
RESERVE_ADDRESSES,
|
||||
DAI_ADDRESSES,
|
||||
FTSO_ADDRESSES,
|
||||
} from "../../constants/addresses";
|
||||
import { useTokenSymbol } from "../../hooks/tokens";
|
||||
@ -56,7 +56,7 @@ const Dex = ({ chainId, address, connect }) => {
|
||||
const [slippage, setSlippage] = useState(localStorage.getItem("dex-slippage") || "5");
|
||||
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("dex-decimals") || "5");
|
||||
|
||||
const [tokenAddressTop, setTokenAddressTop] = useState(RESERVE_ADDRESSES[chainId]);
|
||||
const [tokenAddressTop, setTokenAddressTop] = useState(DAI_ADDRESSES[chainId]);
|
||||
const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]);
|
||||
|
||||
const { symbol: tokenNameTop } = useTokenSymbol(chainId, tokenAddressTop);
|
||||
@ -75,8 +75,8 @@ const Dex = ({ chainId, address, connect }) => {
|
||||
setTokenAddressTop(currentQueryParameters.get("from"));
|
||||
newQueryParameters.set("from", currentQueryParameters.get("from"));
|
||||
} else {
|
||||
setTokenAddressTop(RESERVE_ADDRESSES[chainId]);
|
||||
newQueryParameters.set("from", RESERVE_ADDRESSES[chainId]);
|
||||
setTokenAddressTop(DAI_ADDRESSES[chainId]);
|
||||
newQueryParameters.set("from", DAI_ADDRESSES[chainId]);
|
||||
}
|
||||
|
||||
if (currentQueryParameters.has("to")) {
|
||||
@ -186,7 +186,7 @@ const Dex = ({ chainId, address, connect }) => {
|
||||
|
||||
<PageTitle
|
||||
name={`${pathname.name.charAt(0).toUpperCase() + pathname.name.slice(1).toLowerCase()} V2 Classic`}
|
||||
subtitle="Swap via Uniswap V2 Fork"
|
||||
subtitle="Classic interface to V2 smart contracts."
|
||||
/>
|
||||
<Container
|
||||
style={{
|
||||
|
||||
@ -46,7 +46,6 @@ const SwapContainer = ({
|
||||
refetch: balanceRefetchTop,
|
||||
contractAddress: addressTop,
|
||||
} = useBalance(chainId, tokenNameTop, address);
|
||||
|
||||
const {
|
||||
balance: balanceBottom,
|
||||
refetch: balanceRefetchBottom,
|
||||
@ -188,11 +187,11 @@ const SwapContainer = ({
|
||||
>
|
||||
<Box width="100%" display="flex" justifyContent="space-between">
|
||||
<Typography fontSize="12px" lineHeight="15px">Current price:</Typography>
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(currentPrice, formatDecimals, tokenNameTop)}</Typography>
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(currentPrice, formatDecimals)}</Typography>
|
||||
</Box>
|
||||
<Box width="100%" display="flex" justifyContent="space-between">
|
||||
<Typography fontSize="12px" lineHeight="15px">Next price:</Typography>
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals, tokenNameTop)}</Typography>
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals)}</Typography>
|
||||
</Box>
|
||||
<Box width="100%" display="flex" justifyContent="space-between">
|
||||
<Typography fontSize="12px" lineHeight="15px">Transaction deadline:</Typography>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useConfig } from "wagmi";
|
||||
import {
|
||||
Divider,
|
||||
Typography,
|
||||
@ -20,8 +19,7 @@ import TokenStack from "../../components/TokenStack/TokenStack";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { formatNumber } from "../../helpers/";
|
||||
import { useBalance, useTokenSymbol } from "../../hooks/tokens";
|
||||
import { isNetworkLegacy } from "../../constants";
|
||||
import { RESERVE_ADDRESSES, FTSO_ADDRESSES, STNK_ADDRESSES, GHST_ADDRESSES } from "../../constants/addresses";
|
||||
import { DAI_ADDRESSES, FTSO_ADDRESSES, STNK_ADDRESSES, GHST_ADDRESSES } from "../../constants/addresses";
|
||||
|
||||
const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }) => {
|
||||
const isSmallScreen = useMediaQuery("(max-width: 599px)");
|
||||
@ -39,19 +37,16 @@ const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }
|
||||
const { symbol: searchSymbol } = useTokenSymbol(chainId, address);
|
||||
const { balance: searchBalance } = useBalance(chainId, address, account);
|
||||
|
||||
const { balance: reserveBalance } = useBalance(chainId, "RESERVE", account);
|
||||
const { balance: daiBalance } = useBalance(chainId, "GDAI", account);
|
||||
const { balance: ftsoBalance } = useBalance(chainId, "FTSO", account);
|
||||
const { balance: stnkBalance } = useBalance(chainId, "STNK", account);
|
||||
const { balance: ghstBalance } = useBalance(chainId, "GHST", account);
|
||||
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||
const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
|
||||
const config = useConfig();
|
||||
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
|
||||
const searchToken = useMemo(() => {
|
||||
return [{
|
||||
name: searchSymbol,
|
||||
@ -64,10 +59,10 @@ const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }
|
||||
const knownTokens = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
name: reserveSymbol,
|
||||
icons: isNetworkLegacy(chainId) ? ["GDAI"] : [nativeSymbol],
|
||||
balance: reserveBalance,
|
||||
address: RESERVE_ADDRESSES[chainId]
|
||||
name: daiSymbol,
|
||||
icons: ["GDAI"],
|
||||
balance: daiBalance,
|
||||
address: DAI_ADDRESSES[chainId]
|
||||
},
|
||||
{
|
||||
name: ftsoSymbol,
|
||||
@ -88,7 +83,7 @@ const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }
|
||||
address: GHST_ADDRESSES[chainId]
|
||||
}
|
||||
]
|
||||
}, [reserveSymbol, ftsoSymbol, stnkSymbol, ghstSymbol, reserveBalance, ftsoBalance, stnkBalance, ghstBalance]);
|
||||
}, [daiSymbol, ftsoSymbol, stnkSymbol, ghstSymbol, daiBalance, ftsoBalance, stnkBalance, ghstBalance]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddress(userInput)) {
|
||||
|
||||
@ -11,7 +11,7 @@ import TokenStack from "../../components/TokenStack/TokenStack";
|
||||
import { PrimaryButton } from "../../components/Button";
|
||||
import { Tab, Tabs } from "../../components/Tabs/Tabs";
|
||||
|
||||
import { RESERVE_ADDRESSES } from "../../constants/addresses";
|
||||
import { DAI_ADDRESSES } from "../../constants/addresses";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { formatCurrency, formatNumber } from "../../helpers";
|
||||
|
||||
@ -44,14 +44,14 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
||||
symbol: "",
|
||||
})
|
||||
|
||||
const reserveConversionRate = useConversionRate(chainId, "RESERVE");
|
||||
const accumulatedDonation = useAccumulatedDonation(chainId, "RESERVE");
|
||||
const daiConversionRate = useConversionRate(chainId, "GDAI");
|
||||
const accumulatedDonation = useAccumulatedDonation(chainId, "GDAI");
|
||||
|
||||
const { balance: reserveBalance, refetch: reserveBalanceRefetch } = useTokenBalance(chainId, "RESERVE", address);
|
||||
const { balance: daiBalance, refetch: daiBalanceRefetch } = useTokenBalance(chainId, "GDAI", address);
|
||||
const { data: nativeBalance, refetch: balanceRefetch } = useBalance({ address });
|
||||
const { data: contractBalance, refetch: contractBalanceRefetch } = useBalance({ address: RESERVE_ADDRESSES[chainId] });
|
||||
const { totalSupply: reserveTotalSupply, refetch: refetchReserveTotalSupply } = useTotalSupply(chainId, "RESERVE");
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||
const { data: contractBalance, refetch: contractBalanceRefetch } = useBalance({ address: DAI_ADDRESSES[chainId] });
|
||||
const { totalSupply: reserveTotalSupply, refetch: refetchReserveTotalSupply } = useTotalSupply(chainId, "GDAI");
|
||||
const { symbol: faucetSymbol } = useTokenSymbol(chainId, "GDAI");
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.send({ hitType: "pageview", page: "/faucet" });
|
||||
@ -93,10 +93,10 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
||||
}, [amount, balance, nativeInfo])
|
||||
|
||||
const estimatedAmountIn = useMemo(() => {
|
||||
const rate = new DecimalBigNumber(reserveConversionRate.toString(), 0);
|
||||
const rate = new DecimalBigNumber(daiConversionRate.toString(), 0);
|
||||
const value = new DecimalBigNumber(amount, nativeInfo.decimals);
|
||||
return value.mul(rate);
|
||||
}, [amount, reserveConversionRate, nativeInfo]);
|
||||
}, [amount, daiConversionRate, nativeInfo]);
|
||||
|
||||
const contractBalanceFree = useMemo(() => {
|
||||
const realContractBalance = contractBalance ? contractBalance.value : 0n;
|
||||
@ -129,7 +129,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
||||
}
|
||||
|
||||
await balanceRefetch();
|
||||
await reserveBalanceRefetch();
|
||||
await daiBalanceRefetch();
|
||||
await contractBalanceRefetch();
|
||||
await refetchReserveTotalSupply();
|
||||
setAmount("");
|
||||
@ -159,7 +159,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
||||
<meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
|
||||
</Helmet>
|
||||
|
||||
<PageTitle name={`${reserveSymbol} Faucet`} subtitle={`Swap Sepolia ${nativeInfo.symbol} for ${reserveSymbol}`} />
|
||||
<PageTitle name={`${faucetSymbol} Faucet`} subtitle={`Swap Sepolia ${nativeInfo.symbol} for ${faucetSymbol}.`} />
|
||||
<Container
|
||||
style={{
|
||||
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
@ -188,7 +188,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
||||
</Tabs>
|
||||
{!isSemiSmallScreen && <PrimaryButton
|
||||
variant="text"
|
||||
href={`${scanInfo.url}/token/${RESERVE_ADDRESSES[chainId]}`}
|
||||
href={`${scanInfo.url}/token/${DAI_ADDRESSES[chainId]}`}
|
||||
>
|
||||
Check on {scanInfo.name}
|
||||
</PrimaryButton>}
|
||||
@ -211,14 +211,14 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
||||
{!isMint && <SwapCard
|
||||
id={`faucet-sepolia-eth`}
|
||||
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
||||
tokenName={reserveSymbol}
|
||||
token={<TokenStack tokens={[reserveSymbol]} sx={{ fontSize: "21px" }} />}
|
||||
info={`${formatCurrency(reserveBalance.toString(), 4, reserveSymbol)}`}
|
||||
tokenName={faucetSymbol}
|
||||
token={<TokenStack tokens={[faucetSymbol]} sx={{ fontSize: "21px" }} />}
|
||||
info={`${formatCurrency(daiBalance.toString(), 4, faucetSymbol)}`}
|
||||
value={amount}
|
||||
onChange={event => setAmount(event.currentTarget.value)}
|
||||
inputProps={{ "data-testid": "fromInput" }}
|
||||
endString={"Max"}
|
||||
endStringOnClick={() => setAmount(reserveBalance.toString())}
|
||||
endStringOnClick={() => setAmount(daiBalance.toString())}
|
||||
/>}
|
||||
<Box
|
||||
mb="20px"
|
||||
@ -231,15 +231,15 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
||||
<>
|
||||
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">{nativeInfo.symbol} multiplier:</Typography>}
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatNumber(reserveConversionRate, 2)}</Typography>
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatNumber(daiConversionRate, 2)}</Typography>
|
||||
</Box>
|
||||
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmountIn, 5, reserveSymbol)}</Typography>
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmountIn, 5, faucetSymbol)}</Typography>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {reserveSymbol} balance:</Typography>}
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(reserveBalance, 5, reserveSymbol)}</Typography>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {faucetSymbol} balance:</Typography>}
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(daiBalance, 5, faucetSymbol)}</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
@ -267,7 +267,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
||||
preparedAmount?._value === 0n ||
|
||||
isPending ||
|
||||
(isMint && balance?.lt(preparedAmount)) ||
|
||||
(!isMint && reserveBalance?.lt(preparedAmount))
|
||||
(!isMint && daiBalance?.lt(preparedAmount))
|
||||
)
|
||||
}
|
||||
loading={isPending}
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import ReactGA from "react-ga4";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Box, Container, Grid, Divider, Typography, useMediaQuery } from "@mui/material";
|
||||
|
||||
import Paper from "../../components/Paper/Paper";
|
||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||
import { PrimaryButton } from "../../components/Button";
|
||||
|
||||
import GovernanceInfoText from "./components/GovernanceInfoText";
|
||||
import ProposalsList from "./components/ProposalsList";
|
||||
import { ProposalsCount, MinQuorumPercentage, ProposalThreshold } from "./components/Metric";
|
||||
|
||||
import { useTokenSymbol } from "../../hooks/tokens";
|
||||
|
||||
const Governance = ({ connect, config, address, chainId }) => {
|
||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
|
||||
const handleModal = () => {
|
||||
const navigate = useNavigate();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.send({ hitType: "pageview", page: "/governance" });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageTitle name="Protocol Governance" subtitle={`Cast $${ghstSymbol} to facilitate DAO decisions`} />
|
||||
<Container
|
||||
style={{
|
||||
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
minHeight: "calc(100vh - 128px)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mt: "15px" }}>
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Proposal Requirements
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||
<ProposalsCount chainId={chainId} />
|
||||
</Grid>
|
||||
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||
<MinQuorumPercentage chainId={chainId} ghstSymbol={ghstSymbol} />
|
||||
</Grid>
|
||||
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||
<ProposalThreshold chainId={chainId} ghstSymbol={ghstSymbol} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Box mt="45px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
|
||||
<PrimaryButton
|
||||
fullWidth
|
||||
onClick={() => navigate(`/governance/create`)}
|
||||
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
|
||||
>
|
||||
Create Proposal
|
||||
</PrimaryButton>
|
||||
<Box textAlign="center" mt="15px">
|
||||
<GovernanceInfoText />
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
<ProposalsList address={address} config={config} chainId={chainId} />
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Governance;
|
||||
@ -1,226 +0,0 @@
|
||||
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||
import ReactGA from "react-ga4";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
TableContainer,
|
||||
Table,
|
||||
TableRow,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableCell,
|
||||
Typography,
|
||||
Link,
|
||||
OutlinedInput,
|
||||
InputLabel,
|
||||
FormControl,
|
||||
useMediaQuery,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
|
||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
|
||||
import { GHOST_GOVERNANCE_ADDRESSES, GHST_ADDRESSES } from "../../constants/addresses";
|
||||
|
||||
import Paper from "../../components/Paper/Paper";
|
||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||
import { PrimaryButton, TertiaryButton } from "../../components/Button";
|
||||
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
||||
|
||||
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
|
||||
import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
|
||||
import ProposalModal from "./components/ProposalModal";
|
||||
import { parseFunctionCalldata } from "./components/functions/index";
|
||||
import { MY_PROPOSALS_PREFIX } from "./helpers";
|
||||
|
||||
const NewProposal = ({ config, address, connect, chainId }) => {
|
||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const myStoredProposals = localStorage.getItem(`${MY_PROPOSALS_PREFIX}-${address}`);
|
||||
const [myProposals, setMyProposals] = useState(
|
||||
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
|
||||
);
|
||||
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [isModalOpened, setIsModalOpened] = useState(false);
|
||||
const [proposalFunctions, setProposalFunctions] = useState([]);
|
||||
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
const { balance: ghstBalance } = useBalance(chainId, ghstSymbol, address)
|
||||
const { threshold } = useProposalThreshold(chainId, ghstSymbol);
|
||||
const {
|
||||
proposalHash,
|
||||
proposalDescription
|
||||
} = useProposalHash(chainId, proposalFunctions);
|
||||
|
||||
useEffect(() => {
|
||||
const toStore = JSON.stringify(myProposals.map(id => id.toString()));
|
||||
localStorage.setItem(`${MY_PROPOSALS_PREFIX}-${address}`, toStore);
|
||||
}, [myProposals]);
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.send({ hitType: "pageview", page: "/governance/create" });
|
||||
}, []);
|
||||
|
||||
const addCalldata = (calldata) => setProposalFunctions(prev => [...prev, calldata]);
|
||||
const removeCalldata = (index) => setProposalFunctions(prev => prev.filter((_, i) => i !== index));
|
||||
|
||||
const storeProposal = (proposalId) => setMyProposals(prev => [...prev, proposalId]);
|
||||
const removeProposal = (proposalId) => setMyProposals(prev => prev.filter(item => item !== proposalId));
|
||||
|
||||
const nativeCurrency = useMemo(() => {
|
||||
const client = config?.getClient();
|
||||
return client?.chain?.nativeCurrency?.symbol;
|
||||
}, [config]);
|
||||
|
||||
const submitProposal = useCallback(async () => {
|
||||
setIsPending(true);
|
||||
|
||||
const result = await propose(chainId, address, proposalFunctions, proposalDescription);
|
||||
if (result) {
|
||||
storeProposal(proposalHash);
|
||||
setProposalFunctions([]);
|
||||
}
|
||||
|
||||
setIsPending(false);
|
||||
}, [chainId, address, proposalHash, proposalFunctions, proposalDescription]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProposalModal
|
||||
nativeCurrency={nativeCurrency}
|
||||
ftsoSymbol={ftsoSymbol}
|
||||
chainId={chainId}
|
||||
addCalldata={addCalldata}
|
||||
isOpened={isModalOpened}
|
||||
closeModal={() => setIsModalOpened(false)}
|
||||
/>
|
||||
<Box>
|
||||
<PageTitle name="Create Proposal" subtitle="Submit your proposal to strengthen the DAO" />
|
||||
<Container
|
||||
style={{
|
||||
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
minHeight: "calc(100vh - 128px)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mt: "15px" }}>
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Proposal Functions
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
topRight={
|
||||
<PrimaryButton variant="text" href="http://ghostchain.io/governance">
|
||||
Explore Governance
|
||||
</PrimaryButton>
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<Box>
|
||||
{proposalFunctions.length === 0 && <Box
|
||||
width="100%"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
marginBottom="50px"
|
||||
marginTop="25px"
|
||||
>
|
||||
<Typography variant="subtitle1">
|
||||
Create new proposal by adding one or more of the functions below.
|
||||
</Typography>
|
||||
</Box>}
|
||||
|
||||
</Box>
|
||||
<Box display="flex" flexDirection="column" alignItems="center">
|
||||
<Box sx={{ width: isSemiSmallScreen ? "100%" : "350px" }}>
|
||||
<TokenAllowanceGuard
|
||||
spendAmount={threshold}
|
||||
tokenName={ghstSymbol}
|
||||
owner={address}
|
||||
spender={GHOST_GOVERNANCE_ADDRESSES[chainId]}
|
||||
decimals={ghstBalance._decimals}
|
||||
approvalText={`Approve ${ghstSymbol}`}
|
||||
approvalPendingText={"Approving..."}
|
||||
connect={connect}
|
||||
isVertical
|
||||
>
|
||||
<Box display="flex" flexDirection="column" alignItems="center">
|
||||
<TertiaryButton
|
||||
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
|
||||
fullWidth
|
||||
disabled={isPending}
|
||||
onClick={() => setIsModalOpened(true)}
|
||||
>
|
||||
Add New Function
|
||||
</TertiaryButton>
|
||||
<PrimaryButton
|
||||
disabled={
|
||||
proposalFunctions.length === 0 ||
|
||||
ghstBalance.lt(threshold) ||
|
||||
isPending
|
||||
}
|
||||
fullWidth
|
||||
onClick={() => submitProposal()}
|
||||
>
|
||||
{isPending ? "Submitting..." : "Submit Proposal"}
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
</TokenAllowanceGuard>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{proposalFunctions.length > 0 && <Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Proposal Functions
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<TableContainer>
|
||||
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>{proposalFunctions.map((metadata, index) => {
|
||||
return parseFunctionCalldata(metadata, index, chainId, nativeCurrency, removeCalldata);
|
||||
})}</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>}
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NewProposal;
|
||||
@ -1,486 +0,0 @@
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import ReactGA from "react-ga4";
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useBlock, useBlockNumber } from "wagmi";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
Typography,
|
||||
Link,
|
||||
TableContainer,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
useMediaQuery,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
|
||||
import Paper from "../../components/Paper/Paper";
|
||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||
import LinearProgressBar from "../../components/Progress/LinearProgressBar";
|
||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||
import Chip from "../../components/Chip/Chip";
|
||||
import { PrimaryButton, SecondaryButton } from "../../components/Button";
|
||||
|
||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
|
||||
|
||||
import { formatNumber, formatCurrency, shorten } from "../../helpers";
|
||||
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
||||
import { DecimalBigNumber, } from "../../helpers/DecimalBigNumber";
|
||||
|
||||
import { convertStatusToColor, convertStatusToLabel } from "./helpers";
|
||||
import { parseFunctionCalldata } from "./components/functions";
|
||||
|
||||
import { networkAvgBlockSpeed } from "../../constants";
|
||||
|
||||
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
|
||||
import {
|
||||
useProposalState,
|
||||
useProposalProposer,
|
||||
useProposalLocked,
|
||||
useProposalQuorum,
|
||||
useProposalVoteOf,
|
||||
useProposalVotes,
|
||||
useProposalDetails,
|
||||
useProposalSnapshot,
|
||||
useProposalDeadline,
|
||||
useProposalVotingDelay,
|
||||
castVote,
|
||||
executeProposal,
|
||||
releaseLocked
|
||||
} from "../../hooks/governance";
|
||||
|
||||
import { VOTED_PROPOSALS_PREFIX } from "./helpers";
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
import Timeline from '@mui/lab/Timeline';
|
||||
import TimelineItem from '@mui/lab/TimelineItem';
|
||||
import TimelineSeparator from '@mui/lab/TimelineSeparator';
|
||||
import TimelineConnector from '@mui/lab/TimelineConnector';
|
||||
import TimelineContent from '@mui/lab/TimelineContent';
|
||||
import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent';
|
||||
import TimelineDot from '@mui/lab/TimelineDot';
|
||||
///////////////////////////
|
||||
import FastfoodIcon from '@mui/icons-material/Fastfood';
|
||||
import LaptopMacIcon from '@mui/icons-material/LaptopMac';
|
||||
import HotelIcon from '@mui/icons-material/Hotel';
|
||||
import RepeatIcon from '@mui/icons-material/Repeat';
|
||||
|
||||
const HUNDRED = new DecimalBigNumber(100n, 0);
|
||||
|
||||
|
||||
const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||
const { id } = useParams();
|
||||
const proposalId = BigInt(id);
|
||||
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
|
||||
|
||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||
|
||||
const theme = useTheme();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
const { balance } = useBalance(chainId, "GHST", address);
|
||||
|
||||
const { state: proposalState } = useProposalState(chainId, proposalId);
|
||||
const { proposer: proposalProposer } = useProposalProposer(chainId, proposalId);
|
||||
const { locked: proposalLocked } = useProposalLocked(chainId, proposalId);
|
||||
const { quorum: proposalQuorum } = useProposalQuorum(chainId, proposalId);
|
||||
|
||||
const { voteOf } = useProposalVoteOf(chainId, proposalId, address);
|
||||
const { forVotes, againstVotes, totalVotes } = useProposalVotes(chainId, proposalId);
|
||||
const { proposalDetails } = useProposalDetails(chainId, proposalId);
|
||||
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
|
||||
|
||||
const { pastTotalSupply: totalSupply } = usePastTotalSupply(chainId, "GHST", proposalSnapshot);
|
||||
const { pastVotes } = usePastVotes(chainId, "GHST", proposalSnapshot, address);
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.send({ hitType: "pageview", page: `/governance/${id}` });
|
||||
}, []);
|
||||
|
||||
const quorumPercentage = useMemo(() => {
|
||||
if (totalSupply._value === 0n) return 0;
|
||||
return proposalQuorum / totalSupply * HUNDRED;
|
||||
}, [proposalQuorum, totalSupply]);
|
||||
|
||||
const votePercentage = useMemo(() => {
|
||||
if (totalSupply._value === 0n) return 0;
|
||||
return totalVotes * HUNDRED / totalSupply;
|
||||
}, [totalVotes, totalSupply]);
|
||||
|
||||
const forPercentage = useMemo(() => {
|
||||
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
||||
return forVotes * HUNDRED / totalSupply;
|
||||
}, [forVotes, totalSupply]);
|
||||
|
||||
const againstPercentage= useMemo(() => {
|
||||
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
||||
return againstVotes * HUNDRED / totalSupply;
|
||||
}, [againstVotes, totalSupply]);
|
||||
|
||||
const voteWeightPercentage = useMemo(() => {
|
||||
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
||||
return pastVotes * HUNDRED / totalSupply;
|
||||
}, [pastVotes, totalSupply]);
|
||||
|
||||
const voteValue = useMemo(() => {
|
||||
if (totalVotes?._value == 0n) {
|
||||
return 0;
|
||||
}
|
||||
const value = forVotes * HUNDRED / totalVotes;
|
||||
return Math.floor(Number(value.toString()));
|
||||
}, [forVotes, totalVotes]);
|
||||
|
||||
const voteTarget = useMemo(() => {
|
||||
if (totalSupply._value == 0n) {
|
||||
return 80;
|
||||
}
|
||||
|
||||
const value = Number(totalVotes / totalSupply);
|
||||
const result = (5 - Math.sqrt(1 + 80/9 * (value - 0.1) )) / 4
|
||||
|
||||
return Math.floor(result * 100);
|
||||
}, [totalVotes, totalSupply]);
|
||||
|
||||
const nativeCurrency = useMemo(() => {
|
||||
const client = config?.getClient();
|
||||
return client?.chain?.nativeCurrency?.symbol;
|
||||
}, [config]);
|
||||
|
||||
const etherscanLink = useMemo(() => {
|
||||
const client = config.getClient();
|
||||
let url = client?.chain?.blockExplorers?.default?.url;
|
||||
if (url) {
|
||||
url = url + `/address/${proposalProposer}`;
|
||||
}
|
||||
return url;
|
||||
}, [proposalProposer, config]);
|
||||
|
||||
const handleVote = async (support) => {
|
||||
setIsPending(true);
|
||||
const result = await castVote(chainId, address, proposalId, support);
|
||||
|
||||
if (result) {
|
||||
const storedVotedProposals = localStorage.getItem(`${VOTED_PROPOSALS_PREFIX}-${address}`);
|
||||
const proposals = JSON.parse(storedVotedProposals || "[]").map(id => BigInt(id));
|
||||
proposals.push(proposalId);
|
||||
const toStore = JSON.stringify(proposals.map(id => id.toString()));
|
||||
localStorage.setItem(`${VOTED_PROPOSALS_PREFIX}-${address}`, toStore);
|
||||
await queryClient.invalidateQueries();
|
||||
}
|
||||
setIsPending(false);
|
||||
}
|
||||
|
||||
const handleExecute = async () => {
|
||||
setIsPending(true);
|
||||
await executeProposal(chainId, address, proposalId);
|
||||
await queryClient.invalidateQueries();
|
||||
setIsPending(false);
|
||||
}
|
||||
|
||||
const handleRelease = async () => {
|
||||
setIsPending(true);
|
||||
await releaseLocked(chainId, address, proposalId);
|
||||
await queryClient.invalidateQueries();
|
||||
setIsPending(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageTitle
|
||||
name={`GBP-${id.slice(-5)}`}
|
||||
subtitle={
|
||||
<Typography component="span">
|
||||
{`Cast $${ghstSymbol} to shape this proposal's outcome`}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<Container
|
||||
style={{
|
||||
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
minHeight: "calc(100vh - 128px)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mt: "15px" }}>
|
||||
<Box display="flex" justifyContent="space-between" gap="20px">
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="10px">
|
||||
<Typography variant="h6">
|
||||
Progress
|
||||
</Typography>
|
||||
<Chip
|
||||
sx={{ marginTop: "4px", width: "88px" }}
|
||||
label={convertStatusToLabel(proposalState)}
|
||||
background={convertStatusToColor(proposalState)}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
topRight={
|
||||
<PrimaryButton sx={{ padding: "0 !important"}} variant="text" href={"https://forum.ghostchain.io"} >
|
||||
View Forum
|
||||
</PrimaryButton>
|
||||
}
|
||||
>
|
||||
<Box height="280px" display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography sx={{ textShadow: "0 0 black", fontWeight: 600 }} variant="body1" color={theme.colors.feedback.success}>
|
||||
For: {formatNumber(forVotes.toString(), 2)} ({formatNumber(forPercentage?.toString(), 1)}%)
|
||||
</Typography>
|
||||
<Typography sx={{ textShadow: "0 0 black", fontWeight: 600 }} variant="body1" color={theme.colors.feedback.error}>
|
||||
Against: {formatNumber(againstVotes.toString(), 2)} ({formatNumber(againstPercentage?.toString(), 1)}%)
|
||||
</Typography>
|
||||
</Box>
|
||||
<LinearProgressBar
|
||||
barColor={theme.colors.feedback.success}
|
||||
barBackground={theme.colors.feedback.error}
|
||||
variant="determinate"
|
||||
value={voteValue}
|
||||
target={voteTarget}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography>Proposer</Typography>
|
||||
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
|
||||
{`${shorten(proposalProposer)}`}
|
||||
</Link>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography>Locked</Typography>
|
||||
<Typography>{formatCurrency(proposalLocked, 4, ghstSymbol)}</Typography>
|
||||
</Box>
|
||||
|
||||
<hr width="100%" />
|
||||
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box display="flex" flexDirection="row">
|
||||
<Typography>Quorum</Typography>
|
||||
<InfoTooltip message={`Minimum $${ghstSymbol} turnout required for the proposal to become valid, as percentage of the total $${ghstSymbol} supply at the time when proposal was created`} />
|
||||
</Box>
|
||||
<Typography>{formatNumber(proposalQuorum.toString(), 4)} ({formatNumber(quorumPercentage, 1)}%)</Typography>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box display="flex" flexDirection="row">
|
||||
<Typography>Total</Typography>
|
||||
<InfoTooltip message={`Total votes for the proposal, as percentage of the total $${ghstSymbol} supply at the time when proposal was created`}/>
|
||||
</Box>
|
||||
<Typography>{formatNumber(totalSupply.toString(), 4)} ({formatNumber(votePercentage, 1)}%)</Typography>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box display="flex" flexDirection="row">
|
||||
<Typography>Votes</Typography>
|
||||
<InfoTooltip message={`Your voting power, as percentage of total $${ghstSymbol} at the time when proposal was created`} />
|
||||
</Box>
|
||||
<Typography>{formatNumber(pastVotes.toString(), 4)} ({formatNumber(voteWeightPercentage, 1)}%)</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" gap="20px">
|
||||
{address === undefined || address === ""
|
||||
? <PrimaryButton fullWidth onClick={() => connect()}>Connect</PrimaryButton>
|
||||
: voteOf === 0n
|
||||
? <>
|
||||
<SecondaryButton
|
||||
fullWidth
|
||||
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
|
||||
onClick={() => handleVote(1)}
|
||||
>
|
||||
For
|
||||
</SecondaryButton>
|
||||
<SecondaryButton
|
||||
fullWidth
|
||||
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
|
||||
onClick={() => handleVote(0)}
|
||||
>
|
||||
Against
|
||||
</SecondaryButton>
|
||||
</>
|
||||
: <SecondaryButton
|
||||
fullWidth
|
||||
disabled
|
||||
>
|
||||
{`Voted ${voteOf === 1n ? "Against" : "For"}`}
|
||||
</SecondaryButton>
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box height="48px" display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Timeline
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<VotingTimeline
|
||||
proposalLocked={proposalLocked}
|
||||
connect={connect}
|
||||
handleExecute={handleExecute}
|
||||
handleRelease={handleRelease}
|
||||
state={proposalState}
|
||||
address={address}
|
||||
isProposer={proposalProposer === address}
|
||||
chainId={chainId}
|
||||
proposalId={id}
|
||||
isPending={isPending}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Executable Code
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<TableContainer>
|
||||
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>{proposalDetails?.map((metadata, index) => {
|
||||
return parseFunctionCalldata(metadata, index, chainId, nativeCurrency);
|
||||
})}</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
</Container>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const VotingTimeline = ({ connect, handleExecute, handleRelease, proposalLocked, proposalId, chainId, state, address, isProposer, isPending }) => {
|
||||
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
|
||||
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
|
||||
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
|
||||
|
||||
const voteStarted = useMemo(() => {
|
||||
if (proposalSnapshot && propsalVotingDelay) {
|
||||
return proposalSnapshot > propsalVotingDelay ? proposalSnapshot - propsalVotingDelay : 0;
|
||||
}
|
||||
return 0n;
|
||||
}, [proposalSnapshot, propsalVotingDelay]);
|
||||
|
||||
return (
|
||||
<Box height="280px" display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
|
||||
<Timeline sx={{ margin: 0, padding: 0 }}>
|
||||
<VotingTimelineItem chainId={chainId} occured={voteStarted} message="Proposed on" isFirst />
|
||||
<VotingTimelineItem chainId={chainId} occured={proposalSnapshot} message="Voting started" />
|
||||
<VotingTimelineItem chainId={chainId} occured={proposalDeadline} message="Voting ends" />
|
||||
</Timeline>
|
||||
<Box width="100%" display="flex" gap="10px">
|
||||
{(isProposer && (proposalLocked?._value ?? 0n) > 0n) && <SecondaryButton
|
||||
fullWidth
|
||||
disabled={isPending || state < 2}
|
||||
onClick={() => address === "" ? connect() : handleRelease()}
|
||||
>
|
||||
{address === "" ? "Connect" : "Release"}
|
||||
</SecondaryButton>}
|
||||
<SecondaryButton
|
||||
fullWidth
|
||||
disabled={isPending || state !== 4}
|
||||
onClick={() => address === "" ? connect() : handleExecute()}
|
||||
>
|
||||
{address === "" ? "Connect" : state === 4 ? "Execute" : convertStatusToLabel(state)}
|
||||
</SecondaryButton>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const VotingTimelineItem = ({ position, isFirst, isLast, chainId, occured, message }) => {
|
||||
const { data: blockNumber } = useBlockNumber({ chainId, watch: true });
|
||||
const { data: blockInfo } = useBlock({
|
||||
chainId: chainId,
|
||||
blockNumber: occured,
|
||||
query: {
|
||||
enabled: Boolean(occured)
|
||||
}
|
||||
});
|
||||
|
||||
const timestamp = useMemo(() => {
|
||||
if (blockInfo && blockNumber > occured) {
|
||||
const timestamp = Number(blockInfo?.timestamp ?? 0n) * 1000;
|
||||
return new Date(timestamp).toLocaleString();
|
||||
}
|
||||
|
||||
const blocksRemaining = Number(occured) - Number(blockNumber);
|
||||
const secondsRemaining = blocksRemaining * Number(networkAvgBlockSpeed(chainId));
|
||||
const predictedTimestamp = Math.floor(Date.now() / 1000) + secondsRemaining;
|
||||
|
||||
return new Date(predictedTimestamp * 1000).toLocaleString();
|
||||
}, [chainId, occured, blockInfo, blockNumber]);
|
||||
|
||||
return (
|
||||
<TimelineItem>
|
||||
<TimelineOppositeContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
padding: "0 16px",
|
||||
minWidth: "140px",
|
||||
flex: 0,
|
||||
}}
|
||||
>
|
||||
<Typography>{message}</Typography>
|
||||
</TimelineOppositeContent>
|
||||
|
||||
<TimelineSeparator>
|
||||
<TimelineConnector sx={{ background: isFirst ? "transparent" : "#fff" }} />
|
||||
<TimelineDot sx={{ width: "15px", height: "15px", background: "#fff", margin: "12px 0" }} />
|
||||
<TimelineConnector sx={{ background: isLast ? "transparent" : "#fff" }} />
|
||||
</TimelineSeparator>
|
||||
|
||||
<TimelineContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
padding: "0 26px",
|
||||
}}
|
||||
>
|
||||
<Typography>{timestamp}</Typography>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProposalDetails;
|
||||
@ -1,23 +0,0 @@
|
||||
import { Link, Typography, useTheme } from "@mui/material";
|
||||
|
||||
const GovernanceInfoText = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
fontSize="0.875em"
|
||||
lineHeight="15px"
|
||||
>
|
||||
ghostDAO’s adaptive governance system algorithmically sets minimum collateral based on activity.
|
||||
<Link
|
||||
color={theme.colors.primary[300]}
|
||||
href="http://forum.ghostchain.io/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Learn more here.</Link>
|
||||
</Typography>
|
||||
)
|
||||
};
|
||||
|
||||
export default GovernanceInfoText;
|
||||
@ -1,65 +0,0 @@
|
||||
import Metric from "../../../components/Metric/Metric";
|
||||
|
||||
import { formatCurrency, formatNumber } from "../../../helpers";
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { getTokenDecimals } from "../../../hooks/helpers";
|
||||
import { useTotalSupply } from "../../../hooks/tokens";
|
||||
import {
|
||||
useMinQuorum,
|
||||
useProposalThreshold,
|
||||
useProposalCount
|
||||
} from "../../../hooks/governance";
|
||||
|
||||
export const MinQuorumPercentage = props => {
|
||||
const { numerator, denominator, percentage } = useMinQuorum(props.chainId);
|
||||
const { totalSupply } = useTotalSupply(props.chainId, "GHST");
|
||||
const decimals = getTokenDecimals(props.ghstSymbol);
|
||||
|
||||
const value = new DecimalBigNumber(
|
||||
(totalSupply?._value ?? 0n) * numerator / denominator,
|
||||
decimals
|
||||
);
|
||||
|
||||
const _props = {
|
||||
...props,
|
||||
label: `Min Quorum`,
|
||||
tooltip: `Minimum $${props.ghstSymbol} turnout required for the proposal to become valid`,
|
||||
};
|
||||
const tokenValue = formatCurrency(value?.toString(), 2, props.ghstSymbol);
|
||||
const percentageValue = formatNumber(percentage * 100, 2);
|
||||
|
||||
if (percentage) _props.metric = `${tokenValue} (${percentageValue}%)`;
|
||||
else _props.isLoading = true;
|
||||
|
||||
return <Metric {..._props} />;
|
||||
};
|
||||
|
||||
export const ProposalThreshold = props => {
|
||||
const { threshold } = useProposalThreshold(props.chainId, props.ghstSymbol);
|
||||
|
||||
const _props = {
|
||||
...props,
|
||||
label: "Min Collateral",
|
||||
tooltip: `Minimum $${props.ghstSymbol} required to be locked to create a proposal`,
|
||||
};
|
||||
|
||||
if (threshold) _props.metric = `${formatCurrency(threshold.toString(), 2, props.ghstSymbol)}`;
|
||||
else _props.isLoading = true;
|
||||
|
||||
return <Metric {..._props} />;
|
||||
}
|
||||
|
||||
export const ProposalsCount = props => {
|
||||
const { proposalCount } = useProposalCount(props.chainId);
|
||||
|
||||
const _props = {
|
||||
...props,
|
||||
label: `Proposal Count`,
|
||||
tooltip: `Total proposals created from current governor of ghostDAO`,
|
||||
};
|
||||
|
||||
if (proposalCount || proposalCount === 0n) _props.metric = `${formatNumber(proposalCount.toString(), 0)}`;
|
||||
else _props.isLoading = true;
|
||||
|
||||
return <Metric {..._props} />;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { Link, Typography, useTheme } from "@mui/material";
|
||||
|
||||
const ProposalInfoText = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
fontSize="0.875em"
|
||||
lineHeight="15px"
|
||||
>
|
||||
Important: Only the 10 most recent proposals are displayed. Only one proposal can be active at a time.
|
||||
<Link
|
||||
color={theme.colors.primary[300]}
|
||||
href="https://ghostchain.io/ghostdao_litepaper"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Learn more here.</Link>
|
||||
</Typography>
|
||||
)
|
||||
};
|
||||
|
||||
export default ProposalInfoText;
|
||||
@ -1,119 +0,0 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
|
||||
import Modal from "../../../components/Modal/Modal";
|
||||
import Select from "../../../components/Select/Select";
|
||||
import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
||||
|
||||
import {
|
||||
getFunctionArguments,
|
||||
getFunctionCalldata,
|
||||
getFunctionDescription,
|
||||
allPossibleFunctions
|
||||
} from "./functions";
|
||||
|
||||
const ProposalModal = ({ isOpened, closeModal, nativeCurrency, ftsoSymbol, addCalldata, chainId }) => {
|
||||
const [selectedOption, setSelectedOption] = useState();
|
||||
const [renderArguments, setRenderArguments] = useState(false);
|
||||
|
||||
const handleChange = (event) => {
|
||||
setSelectedOption(event.target.value);
|
||||
};
|
||||
|
||||
const headerLabel = useMemo(() => {
|
||||
const data = allPossibleFunctions.find(obj => obj.value === selectedOption);
|
||||
if (data && renderArguments) {
|
||||
return data.label;
|
||||
}
|
||||
return "Executable Code";
|
||||
}, [selectedOption, renderArguments]);
|
||||
|
||||
const handleCalldata = useCallback(() => {
|
||||
addCalldata(getFunctionCalldata(selectedOption, chainId));
|
||||
setSelectedOption(null);
|
||||
closeModal();
|
||||
}, [selectedOption, chainId]);
|
||||
|
||||
const handleClose = () => {
|
||||
setSelectedOption(null);
|
||||
setRenderArguments(false);
|
||||
closeModal();
|
||||
}
|
||||
|
||||
const handleAddCalldata = (calldata) => {
|
||||
addCalldata(calldata);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
const ArgumentsSteps = useMemo(() => getFunctionArguments(selectedOption, chainId), [selectedOption]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
headerContent={
|
||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
||||
<Typography variant="h4">{headerLabel}</Typography>
|
||||
</Box>
|
||||
}
|
||||
open={isOpened}
|
||||
onClose={handleClose}
|
||||
maxWidth="460px"
|
||||
minHeight="200px"
|
||||
>
|
||||
<Box minHeight="220px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
|
||||
{renderArguments
|
||||
? <ArgumentsSteps
|
||||
nativeCurrency={nativeCurrency}
|
||||
ftsoSymbol={ftsoSymbol}
|
||||
chainId={chainId}
|
||||
addCalldata={handleAddCalldata}
|
||||
toInitialStep={() => setRenderArguments(false)}
|
||||
/>
|
||||
: <InitialStep
|
||||
selectedOption={selectedOption}
|
||||
handleChange={handleChange}
|
||||
handleCalldata={handleCalldata}
|
||||
handleProceed={() => setRenderArguments(true)}
|
||||
ready={ArgumentsSteps !== null}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const InitialStep = ({ selectedOption, handleChange, handleCalldata, handleProceed, ready }) => {
|
||||
const theme = useTheme();
|
||||
const functionDescription = useMemo(() => getFunctionDescription(selectedOption), [selectedOption]);
|
||||
return (
|
||||
<Box minHeight="220px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
|
||||
<Select
|
||||
value={selectedOption ?? ""}
|
||||
onChange={handleChange}
|
||||
options={allPossibleFunctions}
|
||||
inputWidth="100%"
|
||||
renderValue={(selected) => {
|
||||
if (!selected || selected.length === 0) {
|
||||
return (
|
||||
<span style={{ color: theme.colors.gray[500], opacity: 0.7 }}>
|
||||
Select function
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return allPossibleFunctions.find(opt => opt.value === selected)?.label || selected;
|
||||
}}
|
||||
/>
|
||||
<Typography align="center" variant="body2">
|
||||
<b>{selectedOption}</b>
|
||||
{" "}
|
||||
{functionDescription}
|
||||
</Typography>
|
||||
{ready
|
||||
? <TertiaryButton disabled={!selectedOption} onClick={() => handleProceed()} fullWidth>Proceed</TertiaryButton>
|
||||
: <PrimaryButton disabled={!selectedOption} onClick={() => handleCalldata()} fullWidth>Create Function</PrimaryButton>
|
||||
}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProposalModal;
|
||||
@ -1,367 +0,0 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Link,
|
||||
Tabs,
|
||||
Tab,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
useTheme,
|
||||
useMediaQuery
|
||||
} from "@mui/material";
|
||||
import { getBlockNumber } from "@wagmi/core";
|
||||
|
||||
import GhostStyledIcon from "../../../components/Icon/GhostIcon";
|
||||
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
|
||||
|
||||
import { networkAvgBlockSpeed } from "../../../constants";
|
||||
import { prettifySecondsInDays, prettifySeconds } from "../../../helpers/timeUtil";
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
|
||||
import Chip from "../../../components/Chip/Chip";
|
||||
import Modal from "../../../components/Modal/Modal";
|
||||
import Paper from "../../../components/Paper/Paper";
|
||||
import LinearProgressBar from "../../../components/Progress/LinearProgressBar";
|
||||
import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
||||
|
||||
import ProposalInfoText from "./ProposalInfoText";
|
||||
import {
|
||||
convertStatusToColor,
|
||||
convertStatusToLabel,
|
||||
MY_PROPOSALS_PREFIX,
|
||||
VOTED_PROPOSALS_PREFIX
|
||||
} from "../helpers";
|
||||
|
||||
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||
|
||||
import { useProposals } from "../../../hooks/governance";
|
||||
|
||||
const MAX_PROPOSALS_TO_SHOW = 10;
|
||||
|
||||
const ProposalsList = ({ chainId, address, config }) => {
|
||||
const isSmallScreen = useScreenSize("md");
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
|
||||
const [proposalsFilter, setProposalFilter] = useState("active");
|
||||
|
||||
const myStoredProposals = localStorage.getItem(`${MY_PROPOSALS_PREFIX}-${address}`);
|
||||
const [myProposals, setMyProposals] = useState(
|
||||
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
|
||||
);
|
||||
|
||||
const storedVotedProposals = localStorage.getItem(`${VOTED_PROPOSALS_PREFIX}-${address}`);
|
||||
const [votedProposals, setVotedProposals] = useState(
|
||||
storedVotedProposals ? JSON.parse(storedVotedProposals).map(id => BigInt(id)) : []
|
||||
);
|
||||
|
||||
const searchedIndexes = useMemo(() => {
|
||||
switch (proposalsFilter) {
|
||||
case "voted":
|
||||
return votedProposals;
|
||||
case "created":
|
||||
return myProposals;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}, [proposalsFilter]);
|
||||
|
||||
const [blockNumber, setBlockNumber] = useState(0n);
|
||||
const { proposals } = useProposals(chainId, MAX_PROPOSALS_TO_SHOW, searchedIndexes);
|
||||
|
||||
getBlockNumber(config).then(block => setBlockNumber(block));
|
||||
|
||||
if (proposals?.length === 0 && proposalsFilter === "active") {
|
||||
return (
|
||||
<Box display="flex" justifyContent="center">
|
||||
<Typography variant="h4">No proposals yet</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Proposals
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box display="flex" flexDirection="column" gap="40px">
|
||||
{proposals?.map(proposal => (
|
||||
<ProposalCard
|
||||
key={proposal.hashes.short}
|
||||
proposal={proposal}
|
||||
blockNumber={blockNumber}
|
||||
chainId={chainId}
|
||||
openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{proposalsFilter === "active" && <Box my="24px" textAlign="center">
|
||||
<ProposalInfoText />
|
||||
</Box>}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box
|
||||
width="100%"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
flexDirection="row"
|
||||
gap="5px"
|
||||
>
|
||||
<Typography variant="h6">
|
||||
Proposals
|
||||
</Typography>
|
||||
|
||||
<PrimaryButton
|
||||
variant="text"
|
||||
href="https://forum.ghostchain.io"
|
||||
>
|
||||
View Forum
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<ProposalFilterTrigger trigger={proposalsFilter} setTrigger={setProposalFilter} />
|
||||
|
||||
<ProposalTable>
|
||||
{proposals?.map(proposal => (
|
||||
<ProposalRow
|
||||
key={proposal.hashes.short}
|
||||
proposal={proposal}
|
||||
blockNumber={blockNumber}
|
||||
chainId={chainId}
|
||||
openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
|
||||
/>
|
||||
))}
|
||||
</ProposalTable>
|
||||
|
||||
{proposalsFilter === "active" && <Box mt="24px" textAlign="center" width="70%" mx="auto">
|
||||
<ProposalInfoText />
|
||||
</Box>}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
const ProposalTable = ({ children }) => (
|
||||
<TableContainer>
|
||||
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Proposal ID</TableCell>
|
||||
<TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Status</TableCell>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Vote Ends</TableCell>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
|
||||
<TableCell align="center" style={{ width: "180px", padding: "8px 0" }}></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>{children}</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
|
||||
const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const voteValue = useMemo(() => {
|
||||
const againstVotes = proposal?.votes?.at(0)?._value ?? 0n
|
||||
const forVotes = proposal?.votes?.at(1)?._value ?? 0n;
|
||||
const totalVotes = againstVotes + forVotes;
|
||||
|
||||
if (totalVotes == 0n) {
|
||||
return 0;
|
||||
}
|
||||
const value = forVotes * 100n / totalVotes;
|
||||
return Math.floor(Number(value.toString()));
|
||||
}, [proposal]);
|
||||
|
||||
const voteTarget = useMemo(() => {
|
||||
const againstVotes = proposal?.votes?.at(0)?._value ?? 0n;
|
||||
const forVotes = proposal?.votes?.at(1)?._value ?? 0n;
|
||||
|
||||
const totalSupply = new DecimalBigNumber(
|
||||
proposal?.pastTotalSupply?._value ?? 0n,
|
||||
proposal?.pastTotalSupply?._decimals
|
||||
);
|
||||
const totalVotes = new DecimalBigNumber(
|
||||
againstVotes + forVotes,
|
||||
proposal?.pastTotalSupply?._decimals
|
||||
);
|
||||
|
||||
if (totalSupply._value == 0n) {
|
||||
return 80;
|
||||
}
|
||||
|
||||
const value = Number(totalVotes / totalSupply);
|
||||
const result = (5 - Math.sqrt(1 + 80/9 * (value - 0.1) )) / 4;
|
||||
|
||||
return Math.floor(result * 100);
|
||||
}, [proposal]);
|
||||
|
||||
return (
|
||||
<TableRow id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
<Typography>GDP-{proposal.hashes.short}</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
<Chip
|
||||
sx={{ width: "100px" }}
|
||||
label={convertStatusToLabel(proposal.state)}
|
||||
background={convertStatusToColor(proposal.state)}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
<Typography>
|
||||
{convertDeadline(
|
||||
proposal.deadline,
|
||||
blockNumber,
|
||||
chainId
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
<Box marginLeft="15px" marginRight="15px">
|
||||
<LinearProgressBar
|
||||
barColor={theme.colors.feedback.success}
|
||||
barBackground={theme.colors.feedback.error}
|
||||
variant="determinate"
|
||||
value={voteValue}
|
||||
target={voteTarget}
|
||||
/>
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
{(proposal.state === "Active" || proposal.state === "Succeeded") && <PrimaryButton
|
||||
fullWidth
|
||||
onClick={() => openProposal()}
|
||||
sx={{ maxWidth: "130px" }}
|
||||
>
|
||||
{proposal.state === "Succeeded" ? "Execute" : "Vote"}
|
||||
</PrimaryButton>}
|
||||
{(proposal.state !== "Active" && proposal.state !== "Succeeded") && <TertiaryButton
|
||||
fullWidth
|
||||
onClick={() => openProposal()}
|
||||
sx={{ alignSelf: "right", maxWidth: "130px" }}
|
||||
>
|
||||
View
|
||||
</TertiaryButton>}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery('(max-width: 450px)');
|
||||
|
||||
return (
|
||||
<Box id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
|
||||
<Box display="flex" flexDirection={isSmallScreen ? "column" : "row"} justifyContent="space-between">
|
||||
<Box display="flex" flexDirection="column" width="100%">
|
||||
<Box display="flex" flexDirection="row" alignItems="center" width="100%" gap="10px">
|
||||
<Typography variant="h3">GIP-{proposal.hashes.short}</Typography>
|
||||
<Chip
|
||||
sx={{ width: "88px" }}
|
||||
label={convertStatusToLabel(proposal.state)}
|
||||
background={convertStatusToColor(proposal.state)}
|
||||
/>
|
||||
</Box>
|
||||
<Typography>
|
||||
{convertDeadline(
|
||||
proposal.deadline,
|
||||
blockNumber,
|
||||
chainId
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box marginTop="15px" marginBottom="15px">
|
||||
<LinearProgressBar
|
||||
barColor={theme.colors.feedback.success}
|
||||
barBackground={theme.colors.feedback.error}
|
||||
variant="determinate"
|
||||
value={69}
|
||||
target={Math.floor(Math.random() * 101)}
|
||||
/>
|
||||
</Box>
|
||||
<Box marginBottom="20px">
|
||||
{(proposal.state === "Active" || proposal.state === "Succeeded") && <PrimaryButton
|
||||
fullWidth
|
||||
onClick={() => openProposal()}
|
||||
>
|
||||
{proposal.state === "Succeeded" ? "Execute" : "Vote"}
|
||||
</PrimaryButton>}
|
||||
{(proposal.state !== "Active" && proposal.state !== "Succeeded") && <TertiaryButton
|
||||
fullWidth
|
||||
onClick={() => openProposal()}
|
||||
>
|
||||
View
|
||||
</TertiaryButton>}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ProposalFilterTrigger = ({ trigger, setTrigger }) => {
|
||||
return (
|
||||
<Tabs
|
||||
centered
|
||||
textColor="primary"
|
||||
indicatorColor="primary"
|
||||
value={trigger}
|
||||
aria-label="Proposal filter tabs"
|
||||
onChange={(_, view) => setTrigger(view)}
|
||||
TabIndicatorProps={{ style: { display: "none" } }}
|
||||
>
|
||||
<Tab aria-label="proposal-filter-active-button" value="active" label="Active" style={{ fontSize: "1rem" }} />
|
||||
<Tab aria-label="proposal-filter-voted-button" value="voted" label="Voted" style={{ fontSize: "1rem" }} />
|
||||
<Tab aria-label="proposal-filter-created-button" value="created" label="Created" style={{ fontSize: "1rem" }} />
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
const convertDeadline = (deadline, blockNumber, chainId) => {
|
||||
const diff = blockNumber > deadline ? blockNumber - deadline : deadline - blockNumber;
|
||||
const voteSeconds = Number(diff * networkAvgBlockSpeed(chainId));
|
||||
|
||||
const result = prettifySeconds(voteSeconds, "mins");
|
||||
if (result === "now") {
|
||||
return new Date(Date.now()).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
return `in ${result}`;
|
||||
}
|
||||
|
||||
export default ProposalsList;
|
||||
@ -1,39 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
import { encodeFunctionData } from 'viem';
|
||||
|
||||
|
||||
import {
|
||||
DAO_TREASURY_ADDRESSES,
|
||||
} from "../../../../constants/addresses";
|
||||
import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json";
|
||||
|
||||
import { ParsedCell } from "./index";
|
||||
|
||||
export const prepareAuditReservesCalldata = (chainId) => {
|
||||
const value = 0n;
|
||||
const label = "auditReserves";
|
||||
const target = DAO_TREASURY_ADDRESSES[chainId];
|
||||
const calldata = encodeFunctionData({
|
||||
abi: TreasuryAbi,
|
||||
functionName: 'auditReserves',
|
||||
});
|
||||
return { label, target, calldata, value };
|
||||
}
|
||||
|
||||
export const prepareAuditReservesDescription = "function audits and updates the protocol's total reserve value. It sums the value of all approved reserve and liquidity tokens, then stores and logs the new total.";
|
||||
|
||||
export const AuditReservesSteps = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
export const AuditReservesParsed = (props) => {
|
||||
return (
|
||||
<>
|
||||
{props.isTable && <AuditReservesParsedCell {...props} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const AuditReservesParsedCell = (props) => {
|
||||
return <ParsedCell {...props} />
|
||||
}
|
||||
@ -1,334 +0,0 @@
|
||||
import { useRef, useMemo, useState, useEffect } from "react";
|
||||
import { encodeFunctionData } from 'viem';
|
||||
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
|
||||
|
||||
import Modal from "../../../../components/Modal/Modal";
|
||||
import Select from "../../../../components/Select/Select";
|
||||
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
|
||||
import { shorten } from "../../../../helpers";
|
||||
import { useTokenSymbol } from "../../../../hooks/tokens";
|
||||
|
||||
import { ArgumentsWrapper, BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
|
||||
|
||||
import {
|
||||
RESERVE_ADDRESSES,
|
||||
FTSO_DAI_LP_ADDRESSES,
|
||||
BOND_DEPOSITORY_ADDRESSES,
|
||||
} from "../../../../constants/addresses";
|
||||
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
|
||||
|
||||
export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, intervals, booleans) => {
|
||||
const value = 0n;
|
||||
const label = "create";
|
||||
const target = BOND_DEPOSITORY_ADDRESSES[chainId];
|
||||
const calldata = encodeFunctionData({
|
||||
abi: DepositoryAbi,
|
||||
functionName: 'create',
|
||||
args: [markets, terms, quoteToken, intervals, booleans]
|
||||
});
|
||||
return { label, target, calldata, value };
|
||||
}
|
||||
|
||||
export const prepareCreateBondDescription = "function creates a new bond market by processing pricing, capacity, and term inputs. It initializes and stores the new bond market's complete configuration in the protocol.";
|
||||
|
||||
export const CreateBondParsed = (props) => {
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(props.chainId, "FTSO");
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
headerContent={
|
||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
||||
<Typography variant="h4">View create</Typography>
|
||||
</Box>
|
||||
}
|
||||
open={isOpened}
|
||||
onClose={() => setIsOpened(false)}
|
||||
maxWidth="460px"
|
||||
minHeight="200px"
|
||||
>
|
||||
<Box minHeight="220px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
|
||||
<CreateBondSteps args={props.args} ftsoSymbol={ftsoSymbol} nativeCurrency={props.nativeCoin} {...props} />
|
||||
</Box>
|
||||
</Modal>
|
||||
{props.isTable && <CreateBondParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props}/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const CreateBondParsedCell = (props) => {
|
||||
return <ParsedCell {...props} />
|
||||
}
|
||||
|
||||
export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitialStep, addCalldata, args }) => {
|
||||
const createMode = args === undefined;
|
||||
|
||||
const [step, setStep] = useState(1);
|
||||
const [nextDisabled, setNextDisabled] = useState(false);
|
||||
|
||||
const [capacity, setCapacity] = useState(args?.at(0)?.at(0));
|
||||
const [initialPrice, setInitialPrice] = useState(args?.at(0)?.at(1));
|
||||
const [debtBuffer, setDebtBuffer] = useState(args?.at(0)?.at(2));
|
||||
|
||||
const [depositInterval, setDepositInterval] = useState(args?.at(3)?.at(0));
|
||||
const [tuneInterval, setTuneInterval] = useState(args?.at(3)?.at(1));
|
||||
|
||||
const [tokenAddress, setTokenAddress] = useState(args?.at(2));
|
||||
const [capacityInQuote, setCapacityInQuote] = useState(args?.at(4)?.at(0) ?? true);
|
||||
const [fixedTerm, setFixedTerm] = useState(args?.at(4)?.at(1) ?? false);
|
||||
|
||||
const [vestingLength, setVestingLength] = useState(args?.at(1)?.at(0));
|
||||
const [conclusionTimestamp, setConclusionTimestamp] = useState(args?.at(1)?.at(1));
|
||||
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, RESERVE_ADDRESSES[chainId]);
|
||||
|
||||
const handleProceed = () => {
|
||||
const markets = [capacity, initialPrice, debtBuffer];
|
||||
const terms = [vestingLength, conclusionTimestamp];
|
||||
const intervals = [depositInterval, tuneInterval];
|
||||
const booleans = [capacityInQuote, fixedTerm];
|
||||
|
||||
addCalldata(prepareCreateBondCalldata(chainId, markets, terms, tokenAddress, intervals, booleans))
|
||||
}
|
||||
|
||||
const empty = () => {};
|
||||
|
||||
const incrementStep = () => {
|
||||
setStep(prev => prev + 1);
|
||||
}
|
||||
|
||||
const decrementStep = () => {
|
||||
if (step > 1) setStep(prev => prev - 1);
|
||||
else toInitialStep();
|
||||
}
|
||||
|
||||
const isNextDisabled = useMemo(() => {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return tokenAddress === undefined;
|
||||
case 2:
|
||||
return !capacity || !initialPrice || !debtBuffer;
|
||||
case 3:
|
||||
return !depositInterval || !tuneInterval;
|
||||
case 4:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
|
||||
|
||||
const possibleTokens = [
|
||||
{ value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
|
||||
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
|
||||
];
|
||||
|
||||
return (
|
||||
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent="space-between">
|
||||
{step === 1 && <TokenAndBooleansArguments
|
||||
tokenAddress={tokenAddress}
|
||||
createMode={createMode}
|
||||
possibleTokens={possibleTokens}
|
||||
setTokenAddress={createMode ? setTokenAddress : empty}
|
||||
nativeCurrency={reserveSymbol}
|
||||
ftsoSymbol={ftsoSymbol}
|
||||
capacityInQuote={capacityInQuote}
|
||||
setCapacityInQuote={createMode ? setCapacityInQuote : empty}
|
||||
fixedTerm={fixedTerm}
|
||||
setFixedTerm={createMode ? setFixedTerm : empty}
|
||||
/>}
|
||||
{step === 2 && <MarketArguments
|
||||
capacityInQuote={capacityInQuote}
|
||||
currencySymbol={possibleTokens.find(token => token.value === tokenAddress).symbol}
|
||||
ftsoSymbol={ftsoSymbol}
|
||||
capacity={capacity}
|
||||
setCapacity={createMode ? setCapacity : empty}
|
||||
initialPrice={initialPrice}
|
||||
setInitialPrice={createMode ? setInitialPrice : empty}
|
||||
debtBuffer={debtBuffer}
|
||||
setDebtBuffer={createMode ? setDebtBuffer : empty}
|
||||
/>}
|
||||
{step === 3 && <IntervalsArguments
|
||||
depositInterval={depositInterval}
|
||||
setDepositInterval={createMode ? setDepositInterval : empty}
|
||||
tuneInterval={tuneInterval}
|
||||
setTuneInterval={createMode ? setTuneInterval : empty}
|
||||
/>}
|
||||
{step === 4 && <TermsAgruments
|
||||
fixedTerm={fixedTerm}
|
||||
vestingLength={vestingLength}
|
||||
setVestingLength={createMode ? setVestingLength : empty}
|
||||
conclusionTimestamp={conclusionTimestamp}
|
||||
setConclusionTimestamp={createMode ? setConclusionTimestamp : empty}
|
||||
/>}
|
||||
<Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
|
||||
<Box display="flex" gap="10px">
|
||||
<TertiaryButton onClick={() => decrementStep()} fullWidth>Back</TertiaryButton>
|
||||
<TertiaryButton disabled={isNextDisabled} onClick={() => incrementStep()} fullWidth>Next</TertiaryButton>
|
||||
</Box>
|
||||
{(step === 4 && createMode) && <PrimaryButton onClick={() => handleProceed()} fullWidth>Create Function</PrimaryButton>}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const MarketArguments = ({
|
||||
capacityInQuote,
|
||||
currencySymbol,
|
||||
ftsoSymbol,
|
||||
capacity,
|
||||
setCapacity,
|
||||
initialPrice,
|
||||
setInitialPrice,
|
||||
debtBuffer,
|
||||
setDebtBuffer
|
||||
}) => {
|
||||
return (
|
||||
<Box>
|
||||
<ArgumentInput
|
||||
endString={capacityInQuote ? currencySymbol : ftsoSymbol}
|
||||
label="market[0]"
|
||||
value={capacity ?? ""}
|
||||
setValue={setCapacity}
|
||||
tooltip={`Total capacity limit of the bond market denominated in ${capacityInQuote ? currencySymbol : ftsoSymbol}, representing the maximum amount of bonds that can be sold in this market.`}
|
||||
/>
|
||||
<ArgumentInput
|
||||
endString={currencySymbol}
|
||||
label="market[1]"
|
||||
value={initialPrice ?? ""}
|
||||
setValue={setInitialPrice}
|
||||
tooltip={`Initial bond price of 1 ${ftsoSymbol} denominated in ${currencySymbol}. The bond price adjusts dynamically via Dutch auction based on market demand.`}
|
||||
/>
|
||||
<ArgumentInput
|
||||
endString="%"
|
||||
label="market[2]"
|
||||
value={debtBuffer ?? ""}
|
||||
setValue={setDebtBuffer}
|
||||
tooltip="Debt buffer in basis points (e.g. 30,000 = 30%). Determines how far bond price can fall from initial price before tuning is triggered."
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const IntervalsArguments = ({
|
||||
depositInterval,
|
||||
setDepositInterval,
|
||||
tuneInterval,
|
||||
setTuneInterval,
|
||||
}) => {
|
||||
return (
|
||||
<Box>
|
||||
<ArgumentInput
|
||||
endString="seconds"
|
||||
label="_intervals[0]"
|
||||
value={depositInterval ?? ""}
|
||||
setValue={setDepositInterval}
|
||||
tooltip="Time in seconds that determines per-transaction bond purchase limit based on time until expiration. Higher values = larger per-transaction purchases allowed."
|
||||
/>
|
||||
<ArgumentInput
|
||||
endString="seconds"
|
||||
label="_intervals[0]"
|
||||
value={tuneInterval ?? ""}
|
||||
setValue={setTuneInterval}
|
||||
tooltip="Time in seconds between bond price tuning events that evaluate actual vs. expected bond sales, adjusting how aggressively price decays in the next interval."
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const TermsAgruments = ({
|
||||
fixedTerm,
|
||||
vestingLength,
|
||||
setVestingLength,
|
||||
conclusionTimestamp,
|
||||
setConclusionTimestamp,
|
||||
}) => {
|
||||
return (
|
||||
<Box>
|
||||
<ArgumentInput
|
||||
endString="seconds"
|
||||
label={"_terms[0]"}
|
||||
value={vestingLength ?? ""}
|
||||
setValue={setVestingLength}
|
||||
tooltip={fixedTerm
|
||||
? "Time in seconds representing bond vesting schedule. Bond becomes fully claimable after this time elapses from purchase date."
|
||||
: "Absolute Unix timestamp when all bonds mature and become fully claimable, regardless of the purchase date."
|
||||
}
|
||||
/>
|
||||
<ArgumentInput
|
||||
endString="seconds"
|
||||
label="_terms[1]"
|
||||
value={conclusionTimestamp ?? ""}
|
||||
setValue={setConclusionTimestamp}
|
||||
tooltip="Unix timestamp when bond market stops accepting new purchases. Bond market closes after this time."
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const TokenAndBooleansArguments = ({
|
||||
createMode,
|
||||
tokenAddress,
|
||||
possibleTokens,
|
||||
nativeCurrency,
|
||||
ftsoSymbol,
|
||||
setTokenAddress,
|
||||
capacityInQuote,
|
||||
setCapacityInQuote,
|
||||
fixedTerm,
|
||||
setFixedTerm,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [selectedOption, setSelectedOption] = useState(tokenAddress);
|
||||
|
||||
const handleChange = (event) => {
|
||||
if (createMode) {
|
||||
setSelectedOption(event.target.value);
|
||||
setTokenAddress(event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<BooleanTrigger
|
||||
value={capacityInQuote}
|
||||
label="_booleans[0]"
|
||||
leftText={"True"}
|
||||
rightText={"False"}
|
||||
setLeftValue={() => setCapacityInQuote(true)}
|
||||
setRightValue={() => setCapacityInQuote(false)}
|
||||
tooltip={`Determines how the bond market capacity is measured. True = measured in _quoteToken, False = measured in ${ftsoSymbol}.`}
|
||||
/>
|
||||
<BooleanTrigger
|
||||
value={fixedTerm}
|
||||
label="_booleans[1]"
|
||||
leftText="True"
|
||||
rightText="False"
|
||||
setLeftValue={() => setFixedTerm(true)}
|
||||
setRightValue={() => setFixedTerm(false)}
|
||||
tooltip={`Defines the bond maturity model. True = each purchase matures after vesting duration from its purchase date. False = all purchases mature at the conclusion timestamp.`}
|
||||
/>
|
||||
<ArgumentsWrapper
|
||||
label="_quoteToken"
|
||||
tooltip={`ERC20 token that users pay with to purchase bonds (e.g. ${nativeCurrency} or ${nativeCurrency}-${ftsoSymbol} LP token).`}
|
||||
>
|
||||
<Select
|
||||
value={selectedOption ?? ""}
|
||||
onChange={handleChange}
|
||||
options={possibleTokens}
|
||||
inputWidth="100%"
|
||||
renderValue={(selected) => {
|
||||
if (!selected || selected.length === 0) {
|
||||
return (
|
||||
<span style={{ color: theme.colors.gray[500], opacity: 0.7 }}>
|
||||
Select payment token
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return possibleTokens.find(opt => opt.value === selected)?.label || selected;
|
||||
}}
|
||||
/>
|
||||
</ArgumentsWrapper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@ -1,112 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { encodeFunctionData } from 'viem';
|
||||
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
|
||||
|
||||
import Modal from "../../../../components/Modal/Modal";
|
||||
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
|
||||
|
||||
import {
|
||||
DISTRIBUTOR_ADDRESSES,
|
||||
} from "../../../../constants/addresses";
|
||||
import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json";
|
||||
|
||||
import { BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
|
||||
|
||||
export const prepareSetAdjustmentCalldata = (chainId, rateChange, targetRate, increase) => {
|
||||
const value = 0n;
|
||||
const label = "setAdjustment";
|
||||
const target = DISTRIBUTOR_ADDRESSES[chainId];
|
||||
const calldata = encodeFunctionData({
|
||||
abi: DistributorAbi,
|
||||
functionName: 'setAdjustment',
|
||||
args: [rateChange, targetRate, increase]
|
||||
});
|
||||
return { label, target, calldata, value };
|
||||
}
|
||||
|
||||
export const prepareSetAdjustmentDescription = "function schedules a gradual change to the staking APY. It increases or decreases the staking APY by a specified rate per epoch until a target rate reached.";
|
||||
|
||||
export const SetAdjustmentParsed = (props) => {
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
headerContent={
|
||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
||||
<Typography variant="h4">View setAdjustment</Typography>
|
||||
</Box>
|
||||
}
|
||||
open={isOpened}
|
||||
onClose={() => setIsOpened(false)}
|
||||
maxWidth="460px"
|
||||
minHeight="200px"
|
||||
>
|
||||
<Box minHeight="220px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
|
||||
<SetAdjustmentSteps {...props} />
|
||||
</Box>
|
||||
</Modal>
|
||||
{props.isTable && <SetAdjustmentParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const SetAdjustmentParsedCell = (props) => {
|
||||
return <ParsedCell {...props} />
|
||||
}
|
||||
|
||||
export const SetAdjustmentSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
|
||||
const createMode = args === undefined;
|
||||
|
||||
const [rate, setRate] = useState(args?.at(0));
|
||||
const [target, setTarget] = useState(args?.at(1));
|
||||
const [increase, setIncrease] = useState(args?.at(1) ?? true);
|
||||
|
||||
const handleProceed = () => {
|
||||
addCalldata(prepareSetAdjustmentCalldata(chainId, rate, target, increase));
|
||||
}
|
||||
|
||||
return (
|
||||
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent="space-between">
|
||||
<Box>
|
||||
<BooleanTrigger
|
||||
value={increase}
|
||||
leftText="True"
|
||||
rightText="False"
|
||||
setLeftValue={() => createMode ? setIncrease(true) : {}}
|
||||
setRightValue={() => createMode ? setIncrease(false) : {}}
|
||||
label="add"
|
||||
tooltip="Adjusts the current rate toward the target eCSPR staking rate. True = increase rate, False = decrease rate."
|
||||
/>
|
||||
<ArgumentInput
|
||||
disabled={!createMode}
|
||||
endString="%"
|
||||
label="rate"
|
||||
value={rate ?? ""}
|
||||
setValue={createMode ? setRate : () => {}}
|
||||
tooltip="Each epoch, the current staking rate increases by this amount until the target rate is reached [e.g. 154 => APY = (1 + (Current + 154)/1,000,000)^(365*3)]."
|
||||
/>
|
||||
<ArgumentInput
|
||||
disabled={!createMode}
|
||||
endString="%"
|
||||
label="target"
|
||||
value={target ?? ""}
|
||||
setValue={createMode ? setTarget : () => {}}
|
||||
tooltip="The target staking rate to be achieved [e.g. 633 => APY = (1 + 633/1,000,000)^(365*3) – 1 = 100%]."
|
||||
/>
|
||||
</Box>
|
||||
{createMode && <Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
|
||||
<Box display="flex" gap="10px">
|
||||
<TertiaryButton onClick={toInitialStep} fullWidth>Back</TertiaryButton>
|
||||
<TertiaryButton disabled fullWidth>Next</TertiaryButton>
|
||||
</Box>
|
||||
<PrimaryButton
|
||||
disabled={!target || !rate}
|
||||
onClick={() => handleProceed()}
|
||||
fullWidth
|
||||
>
|
||||
Create Function
|
||||
</PrimaryButton>
|
||||
</Box>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@ -1,308 +0,0 @@
|
||||
import { useRef, useMemo, useState } from "react";
|
||||
import { Box, Typography, Link, TableCell, TableRow, useTheme } from "@mui/material";
|
||||
import { decodeFunctionData } from 'viem';
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { StyledInputBase } from "../../../../components/Swap/SwapCard";
|
||||
import { TertiaryButton } from "../../../../components/Button";
|
||||
import InfoTooltip from "../../../../components/Tooltip/InfoTooltip";
|
||||
|
||||
import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json";
|
||||
import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json";
|
||||
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
|
||||
|
||||
import { config } from "../../../../config";
|
||||
import { shorten, formatCurrency } from "../../../../helpers";
|
||||
|
||||
import { prepareAuditReservesDescription, prepareAuditReservesCalldata, AuditReservesSteps, AuditReservesParsed } from "./AuditReserves";
|
||||
import { prepareSetAdjustmentDescription, prepareSetAdjustmentCalldata, SetAdjustmentSteps, SetAdjustmentParsed } from "./SetAdjustment";
|
||||
import { prepareCreateBondDescription, prepareCreateBondCalldata, CreateBondSteps, CreateBondParsed } from "./CreateBond";
|
||||
|
||||
const DEFAULT_DESCRIPTION = "Please select the function to include in your proposal. Multi-functional proposals are allowed, but each included function should be clearly specified."
|
||||
|
||||
export const allPossibleFunctions = [
|
||||
{ value: "auditReserves", label: "auditReserves" },
|
||||
{ value: "setAdjustment", label: "setAdjustment" },
|
||||
{ value: "create", label: "create" },
|
||||
];
|
||||
|
||||
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
|
||||
|
||||
const identifyAction = (calldata) => {
|
||||
let decoded = { functionName: "Unknown", args: [] };
|
||||
for (const abi of allAbis) {
|
||||
try {
|
||||
decoded = decodeFunctionData({
|
||||
abi: abi,
|
||||
data: calldata,
|
||||
});
|
||||
return decoded;
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
|
||||
const { label, calldata, target, value } = metadata;
|
||||
const { functionName, args } = identifyAction(calldata);
|
||||
const labelOrName = label ?? (functionName ?? "Unknown");
|
||||
|
||||
const remove = removeCalldata && (() => removeCalldata(index));
|
||||
|
||||
switch (functionName) {
|
||||
case "auditReserves":
|
||||
return <AuditReservesParsed
|
||||
isTable
|
||||
key={index}
|
||||
calldata={calldata}
|
||||
label={labelOrName}
|
||||
chainId={chainId}
|
||||
remove={remove}
|
||||
nativeCoin={nativeCoin}
|
||||
value={value}
|
||||
target={target}
|
||||
id={index}
|
||||
/>;
|
||||
case "setAdjustment":
|
||||
return <SetAdjustmentParsed
|
||||
isTable
|
||||
args={args}
|
||||
key={index}
|
||||
calldata={calldata}
|
||||
label={labelOrName}
|
||||
chainId={chainId}
|
||||
remove={remove}
|
||||
nativeCoin={nativeCoin}
|
||||
value={value}
|
||||
target={target}
|
||||
id={index}
|
||||
/>;
|
||||
case "create":
|
||||
return <CreateBondParsed
|
||||
isTable
|
||||
args={args}
|
||||
key={index}
|
||||
calldata={calldata}
|
||||
label={labelOrName}
|
||||
chainId={chainId}
|
||||
remove={remove}
|
||||
nativeCoin={nativeCoin}
|
||||
value={value}
|
||||
target={target}
|
||||
id={index}
|
||||
/>;
|
||||
default:
|
||||
return <ParsedCell
|
||||
isTable
|
||||
key={index}
|
||||
calldata={calldata}
|
||||
label="Unknown"
|
||||
remove={remove}
|
||||
nativeCoin={nativeCoin}
|
||||
value={value}
|
||||
target={target}
|
||||
id={index}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export const getFunctionArguments = (functionName) => {
|
||||
switch (functionName) {
|
||||
case "auditReserves":
|
||||
return null;
|
||||
case "setAdjustment":
|
||||
return SetAdjustmentSteps;
|
||||
case "create":
|
||||
return CreateBondSteps;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const getFunctionCalldata = (functionName, chainId) => {
|
||||
switch (functionName) {
|
||||
case "auditReserves":
|
||||
return prepareAuditReservesCalldata(chainId);
|
||||
case "setAdjustment":
|
||||
return prepareSetAdjustmentCalldata(chainId);
|
||||
case "create":
|
||||
return prepareCreateBondCalldata(chainId);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const getFunctionDescription = (functionName) => {
|
||||
switch (functionName) {
|
||||
case "auditReserves":
|
||||
return prepareAuditReservesDescription;
|
||||
case "setAdjustment":
|
||||
return prepareSetAdjustmentDescription;
|
||||
case "create":
|
||||
return prepareCreateBondDescription;
|
||||
default:
|
||||
return DEFAULT_DESCRIPTION;
|
||||
}
|
||||
}
|
||||
|
||||
export const BooleanValue = ({ left, text, isSelected, setSelected }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
onClick={() => setSelected()}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
borderRadius: left ? "12px 0 0 12px" : "0 12px 12px 0",
|
||||
flex: 1,
|
||||
border: "2px solid #fff",
|
||||
borderLeft: left ? "2px solid #fff" : "none",
|
||||
borderRight: left ? "none" : "2px solid #fff",
|
||||
background: `${isSelected ? "#fff" : theme.colors.gray[600] }`
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color={isSelected ? theme.colors.gray[600] : "#fff"}
|
||||
align="center"
|
||||
>
|
||||
{text}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const ArgumentsWrapper = ({ label, tooltip, children }) => {
|
||||
return (
|
||||
<Box sx={{ marginBottom: "18px" }}>
|
||||
<Box sx={{ marginLeft: "5px", marginBottom: "5px" }} display="flex" flexDirection="row" alignItems="center">
|
||||
<Typography>{label}</Typography>
|
||||
<InfoTooltip message={tooltip} />
|
||||
</Box>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const BooleanTrigger = ({ value, label, tooltip, leftText, rightText, setLeftValue, setRightValue }) => {
|
||||
return (
|
||||
<ArgumentsWrapper label={label} tooltip={tooltip}>
|
||||
<Box display="flex" width="100%" height="40px">
|
||||
<BooleanValue setSelected={setLeftValue} left isSelected={value} text={leftText} />
|
||||
<BooleanValue setSelected={setRightValue} isSelected={!value} text={rightText} />
|
||||
</Box>
|
||||
</ArgumentsWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export const ArgumentInput = ({
|
||||
endString,
|
||||
label,
|
||||
tooltip,
|
||||
value,
|
||||
setValue,
|
||||
disabled,
|
||||
inputType = "number",
|
||||
placeholder = "0",
|
||||
maxWidth = "100%"
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const ref = useRef(null);
|
||||
|
||||
return (
|
||||
<Box sx={{ marginBottom: "15px" }}>
|
||||
<Box sx={{ marginLeft: "5px", marginBottom: "5px" }} display="flex" flexDirection="row" alignItems="center">
|
||||
<Typography>{label}</Typography>
|
||||
<InfoTooltip message={tooltip} />
|
||||
</Box>
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
maxWidth={maxWidth}
|
||||
sx={{ backgroundColor: theme.colors.gray[750] }}
|
||||
borderRadius="12px"
|
||||
padding="6px"
|
||||
onClick={() => {
|
||||
ref.current && ref.current.focus();
|
||||
}}
|
||||
>
|
||||
<Box width="100%" display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||
<StyledInputBase
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
type={inputType}
|
||||
fontSize="20px"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
{endString && (
|
||||
<Box sx={{ paddingRight: "10px", color: theme.colors.gray[500] }} fontSize="12px" lineHeight="15px">
|
||||
{endString}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const ParsedCell = (props) => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
const etherscanLink = useMemo(() => {
|
||||
const client = config.getClient();
|
||||
let url = client?.chain?.blockExplorers?.default?.url;
|
||||
if (url) {
|
||||
url = url + `/address/${props.target}`;
|
||||
}
|
||||
return url;
|
||||
}, [props, config]);
|
||||
|
||||
const handleCalldataCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(props.calldata);
|
||||
setIsCopied(true);
|
||||
toast.success("Calldata successfully copied to your clipboard.", { duration: 2000 });
|
||||
|
||||
setTimeout(() => setIsCopied(false), 2000);
|
||||
} catch (err) {
|
||||
toast.error("Could not copy calldata to clipboard, check logs for error details.", { duration: 5000 });
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
<Typography>{props.label}</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
|
||||
<Typography>{shorten(props.target)}</Typography>
|
||||
</Link>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
|
||||
<Typography>{shorten(props.calldata)}</Typography>
|
||||
</Link>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
|
||||
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
export const MY_PROPOSALS_PREFIX = "MY_PROPOSALS";
|
||||
export const VOTED_PROPOSALS_PREFIX = "VOTED_PROPOSALS";
|
||||
|
||||
export const convertStatusToColor = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return "#f2e370";
|
||||
case 2:
|
||||
return "#f06f73";
|
||||
case 3:
|
||||
return "#f06f73";
|
||||
case 4:
|
||||
return "#60c45b";
|
||||
case 5:
|
||||
return "#60c45b";
|
||||
case 6:
|
||||
return "#f06f73";
|
||||
case 7:
|
||||
return "#f2e370";
|
||||
default:
|
||||
return "#a2b7ce";
|
||||
}
|
||||
}
|
||||
|
||||
export const convertStatusToLabel = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return "Active";
|
||||
case 2:
|
||||
return "Canceled";
|
||||
case 3:
|
||||
return "Defeated";
|
||||
case 4:
|
||||
return "Succeeded";
|
||||
case 5:
|
||||
return "Queued";
|
||||
case 6:
|
||||
return "Expired";
|
||||
case 7:
|
||||
return "Executed";
|
||||
default:
|
||||
return "Pending";
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,6 @@ import Paper from "../../components/Paper/Paper";
|
||||
|
||||
import GhostIcon from "../../assets/icons/ghost-nav-header.svg?react";
|
||||
import EthIcon from "../../assets/tokens/ETH.svg?react";
|
||||
import { parseKnownToken } from "../../components/Token/Token"
|
||||
|
||||
import { useSwitchChain } from 'wagmi';
|
||||
import toast from "react-hot-toast";
|
||||
@ -93,7 +92,7 @@ export default function NotFound({
|
||||
alignItems="center"
|
||||
gap="15px"
|
||||
>
|
||||
<SvgIcon component={parseKnownToken(chain.nativeCurrency.symbol)} inheritViewBox style={{ height: "25px" }} />
|
||||
<SvgIcon component={EthIcon} viewBox="0 0 32 32" />
|
||||
<Typography fontSize="1.2em">{chain.name}</Typography>
|
||||
</Box>
|
||||
</Button>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Dispatch, SetStateAction, useState, useEffect } from "react";
|
||||
import { Box, Container, Grid, Divider, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { Box, Container, Grid, Divider, Typography, Link, useMediaQuery, useTheme } from "@mui/material";
|
||||
import ReactGA from "react-ga4";
|
||||
|
||||
import Paper from "../../components/Paper/Paper";
|
||||
@ -47,7 +47,7 @@ export const StakeContainer = ({ chainId, address, connect }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box >
|
||||
<PageTitle name="Protocol Staking" subtitle={`Stake your ${ftsoSymbol} to earn rebase yields`} />
|
||||
<Container
|
||||
style={{
|
||||
|
||||
@ -16,33 +16,34 @@ import { SecondaryButton } from "../../../components/Button";
|
||||
import TokenStack from "../../../components/TokenStack/TokenStack";
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { formatCurrency } from "../../../helpers";
|
||||
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
||||
import { isNetworkLegacy } from "../../../constants";
|
||||
|
||||
import { useLpValuation } from "../../../hooks/treasury";
|
||||
import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens";
|
||||
|
||||
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
|
||||
import {
|
||||
DAI_ADDRESSES,
|
||||
FTSO_ADDRESSES,
|
||||
} from "../../../constants/addresses";
|
||||
|
||||
const FarmPools = ({ chainId }) => {
|
||||
const isSmallScreen = useMediaQuery("(max-width: 775px)");
|
||||
|
||||
const { totalSupply: reserveFtsoUniTotalSupply } = useTotalSupply(chainId, "RESERVE_FTSO");
|
||||
const reserveFtsoUniValuation = useLpValuation(chainId, "RESERVE_FTSO", reserveFtsoUniTotalSupply._value);
|
||||
const { totalSupply: daiFtsoUniTotalSupply } = useTotalSupply(chainId, "GDAI_FTSO");
|
||||
const daiFtsoUniValuation = useLpValuation(chainId, "GDAI_FTSO", daiFtsoUniTotalSupply._value);
|
||||
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||
const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
|
||||
const pools = [
|
||||
{
|
||||
icons: ["FTSO", isNetworkLegacy(chainId) ? "GDAI" : tokenNameConverter(chainId, reserveSymbol)],
|
||||
name: `${ftsoSymbol}-${reserveSymbol}`,
|
||||
icons: ["FTSO", "GDAI"],
|
||||
name: `${ftsoSymbol}-${daiSymbol}`,
|
||||
dex: "Uniswap V2",
|
||||
url: "/dex/uniswap",
|
||||
tvl: reserveFtsoUniValuation,
|
||||
tvl: daiFtsoUniValuation,
|
||||
params: createSearchParams({
|
||||
pool: "true",
|
||||
from: `${RESERVE_ADDRESSES[chainId]}`,
|
||||
from: `${DAI_ADDRESSES[chainId]}`,
|
||||
to: `${FTSO_ADDRESSES[chainId]}`,
|
||||
})
|
||||
},
|
||||
@ -61,17 +62,7 @@ const FarmPools = ({ chainId }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper
|
||||
fullWidth
|
||||
enableBackground
|
||||
headerContent={
|
||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||
<Typography variant="h6">
|
||||
Farm Pools
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Paper headerText="Farm Pools" fullWidth enableBackground>
|
||||
<TableContainer>
|
||||
<Table aria-label="Farm pools" style={{ tableLayout: "fixed" }}>
|
||||
<TableHead>
|
||||
|
||||
@ -52,7 +52,7 @@ export const TotalDeposit = props => {
|
||||
const _props = {
|
||||
...props,
|
||||
label: "Total Deposit",
|
||||
tooltip: `Total reserves of native coins, LP tokens, and other assets in the ghostDAO treasury backing the entire circulating supply of ${props.stnkSymbol}.`,
|
||||
tooltip: `The total stablecoin reserves in the ghostDAO treasury backing the entire circulating supply of ${props.stnkSymbol}.`,
|
||||
};
|
||||
|
||||
if (deposit) _props.metric = `${formatCurrency(deposit, 2)}`;
|
||||
|
||||
@ -84,7 +84,7 @@ export const FatsoBacking = props => {
|
||||
const _props = {
|
||||
...props,
|
||||
label: `Backing per ${props.ftsoSymbol}`,
|
||||
tooltip: `The total amount of native coins, LP tokens, and other assets held by the ghostDAO treasury to support the value of each ${props.ftsoSymbol} in circulation.`
|
||||
tooltip: `The total amount of stablecoins held by the ghostDAO treasury to support the value of each ${props.ftsoSymbol} in circulation.`
|
||||
};
|
||||
|
||||
if (backing) _props.metric = formatCurrency(backing, 2);
|
||||
|
||||
@ -1,22 +1,19 @@
|
||||
import { Grid, Box, Typography, useTheme } from "@mui/material";
|
||||
import { useAccount, useConfig, useBalance as useBalanceNative } from "wagmi";
|
||||
import { useAccount } from "wagmi";
|
||||
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||
|
||||
import Token from "../../../components/Token/Token";
|
||||
import { SecondaryButton } from "../../../components/Button";
|
||||
import { formatNumber, formatCurrency } from "../../../helpers";
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { isNetworkLegacy } from "../../../constants"
|
||||
|
||||
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
|
||||
import {
|
||||
useFtsoPrice,
|
||||
useStnkPrice,
|
||||
useGhstPrice,
|
||||
useReservePrice,
|
||||
useNativePrice,
|
||||
useDaiPrice,
|
||||
} from "../../../hooks/prices";
|
||||
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
||||
|
||||
const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams, balance, price, description }) => {
|
||||
const navigate = useNavigate();
|
||||
@ -77,26 +74,20 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
|
||||
const theme = useTheme();
|
||||
const { address } = useAccount();
|
||||
|
||||
const config = useConfig();
|
||||
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
const networkName = config?.getClient()?.chain?.name;
|
||||
|
||||
const nativePrice = useNativePrice(chainId);
|
||||
const ftsoPrice = useFtsoPrice(chainId);
|
||||
const stnkPrice = useStnkPrice(chainId);
|
||||
const ghstPrice = useGhstPrice(chainId);
|
||||
const reservePrice = useReservePrice(chainId);
|
||||
const daiPrice = useDaiPrice(chainId);
|
||||
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||
const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
|
||||
const { data: nativeBalance } = useBalanceNative({ address });
|
||||
const { balance: ftsoBalance, contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", address);
|
||||
const { balance: stnkBalance, contractAddress: stnkAddress } = useBalance(chainId, "STNK", address);
|
||||
const { balance: ghstBalance, contractAddress: ghstAddress } = useBalance(chainId, "GHST", address);
|
||||
const { balance: reserveBalance, contractAddress: reserveAddress } = useBalance(chainId, "RESERVE", address);
|
||||
const { balance: daiBalance, contractAddress: daiAddress } = useBalance(chainId, "GDAI", address);
|
||||
|
||||
return (
|
||||
<Grid container spacing={0} justifyContent={"center"}>
|
||||
@ -105,7 +96,7 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
|
||||
isMobileScreen={isMobileScreen}
|
||||
tokenUrl="/dex/uniswap"
|
||||
tokenUrlParams={createSearchParams({
|
||||
from: `${reserveAddress}`,
|
||||
from: `${daiAddress}`,
|
||||
to: `${ftsoAddress}`,
|
||||
})}
|
||||
theme={theme}
|
||||
@ -118,7 +109,7 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
|
||||
isMobileScreen={isMobileScreen}
|
||||
tokenUrl="/dex/uniswap"
|
||||
tokenUrlParams={createSearchParams({
|
||||
from: `${reserveAddress}`,
|
||||
from: `${daiAddress}`,
|
||||
to: `${stnkAddress}`,
|
||||
})}
|
||||
theme={theme}
|
||||
@ -131,43 +122,26 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
|
||||
isMobileScreen={isMobileScreen}
|
||||
tokenUrl="/dex/uniswap"
|
||||
tokenUrlParams={createSearchParams({
|
||||
from: `${reserveAddress}`,
|
||||
from: `${daiAddress}`,
|
||||
to: `${ghstAddress}`,
|
||||
})}
|
||||
theme={theme}
|
||||
tokenName={ghstSymbol}
|
||||
balance={ghstBalance}
|
||||
price={ghstPrice}
|
||||
description={`${ghstSymbol} is the governance token enabling pure Web3 cross-chain magic. 1 ${ghstSymbol} = 1 ${ftsoSymbol} x Current Index.`}
|
||||
description={`${ghstSymbol} enables ghostDAO to have on-chain governance and to be truly cross-chain. ${ghstSymbol} Price = ${ftsoSymbol} Price x Current Index.`}
|
||||
/>
|
||||
<TokenTab
|
||||
isMobileScreen={isMobileScreen}
|
||||
tokenUrl={isNetworkLegacy(chainId) ? "/faucet" : "/wrapper"}
|
||||
tokenUrl="/faucet"
|
||||
tokenUrlParams=""
|
||||
theme={theme}
|
||||
tokenName={reserveSymbol}
|
||||
balance={reserveBalance}
|
||||
price={reservePrice}
|
||||
description={isNetworkLegacy(chainId)
|
||||
? `${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${reserveSymbol} being the primary and most liquid asset.`
|
||||
: `${reserveSymbol} (Wrapped ${nativeSymbol}) is an ERC-20 token that represents ${nativeSymbol} and is pegged 1:1 to the value of ${nativeSymbol}.`
|
||||
}
|
||||
tokenName={daiSymbol}
|
||||
balance={daiBalance}
|
||||
price={daiPrice}
|
||||
description={`${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${daiSymbol} being the primary and most liquid asset.`}
|
||||
/>
|
||||
</Box>
|
||||
{!isNetworkLegacy(chainId) && (
|
||||
<Box width="100%" mt="25px">
|
||||
<TokenTab
|
||||
isMobileScreen={true}
|
||||
tokenUrl={isNetworkLegacy(chainId) ? "/faucet" : "/wrapper"}
|
||||
tokenUrlParams=""
|
||||
theme={theme}
|
||||
tokenName={nativeSymbol}
|
||||
balance={new DecimalBigNumber(nativeBalance?.value ?? 0n, 18)}
|
||||
price={reservePrice}
|
||||
description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,260 +0,0 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { Box, Container, Typography, useMediaQuery } from "@mui/material";
|
||||
import { useConfig, useBalance } from "wagmi";
|
||||
import { Helmet } from "react-helmet";
|
||||
import ReactGA from "react-ga4";
|
||||
|
||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||
import Paper from "../../components/Paper/Paper";
|
||||
import SwapCard from "../../components/Swap/SwapCard";
|
||||
import TokenStack from "../../components/TokenStack/TokenStack";
|
||||
import { PrimaryButton } from "../../components/Button";
|
||||
import { Tab, Tabs } from "../../components/Tabs/Tabs";
|
||||
|
||||
import { RESERVE_ADDRESSES } from "../../constants/addresses";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { formatCurrency, formatNumber } from "../../helpers";
|
||||
|
||||
import {
|
||||
useBalance as useTokenBalance,
|
||||
useTokenSymbol,
|
||||
useTotalSupply,
|
||||
useConversionRate,
|
||||
useAccumulatedDonation,
|
||||
depositNative,
|
||||
withdrawWeth
|
||||
} from "../../hooks/tokens";
|
||||
|
||||
const WethWrapper = ({ chainId, address, config, connect }) => {
|
||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||
|
||||
const [chainName, setChainName] = useState("");
|
||||
const [isMint, setIsMint] = useState(true);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [balance, setBalance] = useState(new DecimalBigNumber(0, 0));
|
||||
const [amount, setAmount] = useState("");
|
||||
const [scanInfo, setScanInfo] = useState({
|
||||
name: "",
|
||||
url: "",
|
||||
});
|
||||
const [nativeInfo, setNativeInfo] = useState({
|
||||
decimals: 18,
|
||||
name: "",
|
||||
symbol: "",
|
||||
})
|
||||
|
||||
const { balance: reserveBalance, refetch: reserveBalanceRefetch } = useTokenBalance(chainId, "RESERVE", address);
|
||||
const { data: nativeBalance, refetch: balanceRefetch } = useBalance({ address });
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.send({ hitType: "pageview", page: "/wrapper" });
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const value = nativeBalance ? nativeBalance.value : 0n;
|
||||
const decimals = nativeBalance ? nativeBalance.decimals : 18;
|
||||
setBalance(new DecimalBigNumber(value, decimals));
|
||||
}, [nativeBalance]);
|
||||
|
||||
useEffect(() => {
|
||||
let scanName = "";
|
||||
let scanUrl = "";
|
||||
|
||||
const client = config?.getClient();
|
||||
scanName = client?.chain?.blockExplorers?.default?.name;
|
||||
scanUrl = client?.chain?.blockExplorers?.default?.url;
|
||||
|
||||
setScanInfo({
|
||||
name: scanName,
|
||||
url: scanUrl,
|
||||
})
|
||||
|
||||
setChainName(client?.chain?.name);
|
||||
|
||||
setNativeInfo(client?.chain?.nativeCurrency)
|
||||
}, [chainId]);
|
||||
|
||||
const changeIsMinted = (value) => {
|
||||
setAmount("");
|
||||
setIsMint(value);
|
||||
}
|
||||
|
||||
const preparedAmount = useMemo(() => {
|
||||
if (address === "") new DecimalBigNumber("0", 0);
|
||||
const decimals = isMint ? nativeInfo.decimals : 18;
|
||||
return new DecimalBigNumber(amount, decimals);
|
||||
}, [amount, balance, nativeInfo])
|
||||
|
||||
const estimatedAmountIn = useMemo(() => {
|
||||
return new DecimalBigNumber(amount, nativeInfo.decimals);
|
||||
}, [amount, nativeInfo]);
|
||||
|
||||
const estimatedAmountOut = useMemo(() => {
|
||||
return new DecimalBigNumber(amount, nativeInfo.decimals);
|
||||
}, [amount, nativeInfo]);
|
||||
|
||||
const actionOrConnect = async () => {
|
||||
if (address === "") {
|
||||
connect();
|
||||
} else {
|
||||
setIsPending(true);
|
||||
|
||||
if (isMint) {
|
||||
await depositNative(chainId, address, preparedAmount._value.toString());
|
||||
} else {
|
||||
await withdrawWeth(chainId, address, preparedAmount._value.toString());
|
||||
}
|
||||
|
||||
await balanceRefetch();
|
||||
await reserveBalanceRefetch();
|
||||
setAmount("");
|
||||
setIsPending(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box height="calc(100vh - 43px)">
|
||||
<Helmet>
|
||||
<title>ghostWrapper | WETH9</title>
|
||||
<meta name="description" content="Standard WETH9 wrapper. Convert native coin to WETH9 representation directly to your wallet." />
|
||||
<meta name="keywords" content="weth, weth9, ghostFaucet, web3 faucet, ethereum, sepolia, polygon, bnb, bsc, AVAX" />
|
||||
|
||||
<meta property="og:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
|
||||
<meta property="og:title" content="ghostDAO | The DeFi 2.0 cross-chain reserve currency" />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:description" content="Standard WETH9 wrapper. Convert native coin to WETH9 representation directly to your wallet." />
|
||||
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:site" content="@realGhostChain" />
|
||||
<meta name="twitter:title" content="ghostDAO | The DeFi 2.0 cross-chain reserve currency" />
|
||||
<meta name="twitter:description" content="Standard WETH9 wrapper. Convert native coin to WETH9 representation directly to your wallet." />
|
||||
<meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
|
||||
</Helmet>
|
||||
|
||||
<PageTitle name={`${reserveSymbol} Wrapper`} subtitle={`Wrap ${nativeInfo.symbol} into ${reserveSymbol}`} />
|
||||
<Container
|
||||
style={{
|
||||
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "calc(100vh - 153px)"
|
||||
}}
|
||||
>
|
||||
<Box width="100%" maxWidth="476px" display="flex" alignItems="center" justifyContent="center" flexDirection="column">
|
||||
<Paper
|
||||
headerContent={
|
||||
<Box alignItems="center" justifyContent="space-between" display="flex" width="100%">
|
||||
<Tabs
|
||||
centered
|
||||
textColor="primary"
|
||||
indicatorColor="primary"
|
||||
value={isMint ? 0 : 1}
|
||||
aria-label="Faucet menu"
|
||||
onChange={(_, view) => changeIsMinted(view === 0)}
|
||||
TabIndicatorProps={{ style: { display: "none" } }}
|
||||
>
|
||||
<Tab aria-label="faucet-mint-button" label="Wrap" style={{ fontSize: "1.5rem" }} />
|
||||
<Tab aria-label="faucet-burn-button" label="Unwrap" style={{ fontSize: "1.5rem" }} />
|
||||
</Tabs>
|
||||
{!isSemiSmallScreen && <PrimaryButton
|
||||
variant="text"
|
||||
href={`${scanInfo.url}/token/${RESERVE_ADDRESSES[chainId]}`}
|
||||
>
|
||||
Check on {scanInfo.name}
|
||||
</PrimaryButton>}
|
||||
</Box>
|
||||
}
|
||||
enableBackground
|
||||
fullWidth
|
||||
>
|
||||
<Box>
|
||||
{isMint && <SwapCard
|
||||
id={`faucet-sepolia-eth`}
|
||||
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
||||
tokenName={nativeInfo.symbol}
|
||||
token={<TokenStack tokens={[nativeInfo.symbol]} sx={{ fontSize: "21px" }} />}
|
||||
info={`${isSemiSmallScreen ? "" : "Balance: "}${formatCurrency(balance.toString(), 4, nativeInfo.symbol)}`}
|
||||
value={amount}
|
||||
onChange={event => setAmount(event.currentTarget.value)}
|
||||
inputProps={{ "data-testid": "fromInput" }}
|
||||
/>}
|
||||
{!isMint && <SwapCard
|
||||
id={`faucet-sepolia-eth`}
|
||||
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
||||
tokenName={reserveSymbol}
|
||||
token={<TokenStack tokens={[reserveSymbol]} sx={{ fontSize: "21px" }} />}
|
||||
info={`${formatCurrency(reserveBalance.toString(), 4, reserveSymbol)}`}
|
||||
value={amount}
|
||||
onChange={event => setAmount(event.currentTarget.value)}
|
||||
inputProps={{ "data-testid": "fromInput" }}
|
||||
endString={"Max"}
|
||||
endStringOnClick={() => setAmount(reserveBalance.toString())}
|
||||
/>}
|
||||
<Box
|
||||
mb="20px"
|
||||
mt="20px"
|
||||
flexDirection="column"
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{isMint && (
|
||||
<>
|
||||
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmountIn, 5, reserveSymbol)}</Typography>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {reserveSymbol} balance:</Typography>}
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(reserveBalance, 5, reserveSymbol)}</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{!isMint && (
|
||||
<>
|
||||
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmountOut, 5, nativeInfo.symbol)}</Typography>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {nativeInfo.symbol} balance:</Typography>}
|
||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(balance, 5, nativeInfo.symbol)}</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
<PrimaryButton
|
||||
fullWidth
|
||||
disabled={
|
||||
address !== "" && (
|
||||
preparedAmount?._value === 0n ||
|
||||
isPending ||
|
||||
(isMint && balance?.lt(preparedAmount)) ||
|
||||
(!isMint && reserveBalance?.lt(preparedAmount))
|
||||
)
|
||||
}
|
||||
loading={isPending}
|
||||
onClick={() => actionOrConnect()}
|
||||
>
|
||||
{address === "" ?
|
||||
"Connect"
|
||||
:
|
||||
isMint ? "Wrap" : "Unwrap"
|
||||
}
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default WethWrapper;
|
||||
@ -41,14 +41,3 @@ export const formatNumber = (number, precision = 0) => {
|
||||
export const sortBondsByDiscount = (bonds) => {
|
||||
return Array.from(bonds).filter((bond) => !bond.isSoldOut).sort((a, b) => (a.discount.gt(b.discount) ? -1 : 1));
|
||||
};
|
||||
|
||||
export const timeConverter = (time, max = 7200, maxText = "long ago") => {
|
||||
const seconds = Number(time);
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
if (mins > max) {
|
||||
return maxText;
|
||||
} else {
|
||||
return `${mins}m ${secs < 10 ? '0' : ''}${secs}s`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
export const tokenNameConverter = (chainId, name) => {
|
||||
if (name?.toUpperCase() === "WETH") {
|
||||
switch (chainId) {
|
||||
case 63:
|
||||
name = "wmETC"
|
||||
break;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { createClient } from "@polkadot-api/substrate-client"
|
||||
import { getObservableClient } from "@polkadot-api/observable-client"
|
||||
import useSWR from "swr"
|
||||
|
||||
const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3"
|
||||
const DEFAULT_CHAIN_ID = "0xa217f4ee58a944470e9633ca5bd6d28a428ed64cd9b6f3e413565f359f89af90"
|
||||
const UnstableProvider = createContext(null)
|
||||
export const useUnstableProvider = () => useContext(UnstableProvider)
|
||||
|
||||
|
||||
@ -6,9 +6,4 @@ export * from "./useClapsInSession";
|
||||
export * from "./useApplauseThreshold";
|
||||
export * from "./useReceivedClaps";
|
||||
export * from "./useAuthorities";
|
||||
export * from "./useValidators";
|
||||
export * from "./useDisabledValidators";
|
||||
export * from "./useBlockCommitments";
|
||||
export * from "./useApplauseDetails";
|
||||
export * from "./useBabeSlots";
|
||||
export * from "./useErasTotalStaked";
|
||||
export * from "./useApplausesForTransaction";
|
||||
|
||||
@ -6,41 +6,42 @@ import { fromHex } from "@polkadot-api/utils";
|
||||
import { useUnstableProvider } from "./UnstableProvider"
|
||||
import { useMetadata } from "./MetadataProvider"
|
||||
|
||||
export const useApplauseDetails = ({ currentSession, argsHash }) => {
|
||||
export const useApplausesForTransaction = ({ currentSession, txHash, argsHash }) => {
|
||||
const { chainHead$, chainId } = useUnstableProvider()
|
||||
const metadata = useMetadata()
|
||||
const { data: applauseDetails } = useSWRSubscription(
|
||||
chainHead$ && argsHash && currentSession && chainId && metadata
|
||||
? ["applauseDetails", chainHead$, argsHash, currentSession, chainId, metadata]
|
||||
const { data: applausesForTransaction } = useSWRSubscription(
|
||||
chainHead$ && txHash && argsHash && currentSession && chainId && metadata
|
||||
? ["applausesForTransaction", chainHead$, txHash, argsHash, currentSession, chainId, metadata]
|
||||
: null,
|
||||
([_, chainHead$, argsHash, currentSession, chainId, metadata], { next }) => {
|
||||
([_, chainHead$, txHash, argsHash, currentSession, chainId, metadata], { next }) => {
|
||||
const { finalized$, storage$ } = chainHead$
|
||||
const subscription = finalized$.pipe(
|
||||
filter(Boolean),
|
||||
mergeMap((blockInfo) => {
|
||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||
const applauseDetails = builder.buildStorage("GhostSlowClaps", "ApplauseDetails")
|
||||
const applausesForTransaction = builder.buildStorage("GhostSlowClaps", "ApplausesForTransaction")
|
||||
|
||||
return storage$(blockInfo?.hash, "value", () =>
|
||||
applauseDetails?.keys.enc(
|
||||
applausesForTransaction?.keys.enc(
|
||||
currentSession,
|
||||
{ asBytes: () => fromHex(txHash) },
|
||||
{ asBytes: () => fromHex(argsHash) },
|
||||
)
|
||||
).pipe(
|
||||
filter(Boolean),
|
||||
distinct(),
|
||||
map((value) => applauseDetails?.value.dec(value))
|
||||
map((value) => applausesForTransaction?.value.dec(value))
|
||||
)
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(applauseDetails) {
|
||||
next(null, applauseDetails)
|
||||
next(applausesForTransaction) {
|
||||
next(null, applausesForTransaction)
|
||||
},
|
||||
error: next,
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}
|
||||
)
|
||||
return applauseDetails
|
||||
return applausesForTransaction
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
import useSWRSubscription from "swr/subscription"
|
||||
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
||||
import { distinct, filter, map, mergeMap } from "rxjs"
|
||||
|
||||
import { useUnstableProvider } from "./UnstableProvider"
|
||||
import { useMetadata } from "./MetadataProvider"
|
||||
|
||||
export const useGenesisSlot = () => {
|
||||
const { chainHead$, chainId } = useUnstableProvider()
|
||||
const metadata = useMetadata()
|
||||
const { data: genesisSlot } = useSWRSubscription(
|
||||
chainHead$ && chainId && metadata
|
||||
? ["genesisSlot", chainHead$, chainId, metadata]
|
||||
: null,
|
||||
([_, chainHead$, chainId, metadata], { next }) => {
|
||||
const { finalized$, storage$ } = chainHead$
|
||||
const subscription = finalized$.pipe(
|
||||
filter(Boolean),
|
||||
mergeMap((blockInfo) => {
|
||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||
const genesisSlot = builder.buildStorage("Babe", "GenesisSlot")
|
||||
return storage$(blockInfo?.hash, "value", () =>
|
||||
genesisSlot?.keys.enc()
|
||||
).pipe(
|
||||
filter(Boolean),
|
||||
distinct(),
|
||||
map((value) => genesisSlot?.value.dec(value))
|
||||
)
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(genesisSlot) {
|
||||
next(null, genesisSlot)
|
||||
},
|
||||
error: next,
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}
|
||||
)
|
||||
return genesisSlot
|
||||
}
|
||||
|
||||
export const useCurrentSlot = () => {
|
||||
const { chainHead$, chainId } = useUnstableProvider()
|
||||
const metadata = useMetadata()
|
||||
const { data: currentSlot } = useSWRSubscription(
|
||||
chainHead$ && chainId && metadata
|
||||
? ["currentSlot", chainHead$, chainId, metadata]
|
||||
: null,
|
||||
([_, chainHead$, chainId, metadata], { next }) => {
|
||||
const { finalized$, storage$ } = chainHead$
|
||||
const subscription = finalized$.pipe(
|
||||
filter(Boolean),
|
||||
mergeMap((blockInfo) => {
|
||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||
const currentSlot = builder.buildStorage("Babe", "CurrentSlot")
|
||||
return storage$(blockInfo?.hash, "value", () =>
|
||||
currentSlot?.keys.enc()
|
||||
).pipe(
|
||||
filter(Boolean),
|
||||
distinct(),
|
||||
map((value) => currentSlot?.value.dec(value))
|
||||
)
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(currentSlot) {
|
||||
next(null, currentSlot)
|
||||
},
|
||||
error: next,
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}
|
||||
)
|
||||
return currentSlot
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
import useSWRSubscription from "swr/subscription"
|
||||
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
||||
import { distinct, filter, map, mergeMap } from "rxjs"
|
||||
|
||||
import { useUnstableProvider } from "./UnstableProvider"
|
||||
import { useMetadata } from "./MetadataProvider"
|
||||
|
||||
export const useBlockCommitments = ({ evmChainId }) => {
|
||||
const { chainHead$, chainId } = useUnstableProvider()
|
||||
const metadata = useMetadata()
|
||||
const { data: blockCommitments } = useSWRSubscription(
|
||||
chainHead$ && chainId && metadata
|
||||
? ["blockCommitments", chainHead$, evmChainId, chainId, metadata]
|
||||
: null,
|
||||
([_, chainHead$, evmChainId, chainId, metadata], { next }) => {
|
||||
const { finalized$, storage$ } = chainHead$
|
||||
const subscription = finalized$.pipe(
|
||||
filter(Boolean),
|
||||
mergeMap((blockInfo) => {
|
||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||
const blockCommitments = builder.buildStorage("GhostSlowClaps", "BlockCommitments")
|
||||
return storage$(blockInfo?.hash, "value", () =>
|
||||
blockCommitments?.keys.enc(BigInt(evmChainId))
|
||||
).pipe(
|
||||
filter(Boolean),
|
||||
distinct(),
|
||||
map((value) => blockCommitments?.value.dec(value))
|
||||
)
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(blockCommitments) {
|
||||
next(null, blockCommitments)
|
||||
},
|
||||
error: next,
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}
|
||||
)
|
||||
return blockCommitments?.reduce((acc, [index, obj]) => {
|
||||
acc[index] = obj;
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
import useSWRSubscription from "swr/subscription"
|
||||
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
||||
import { distinct, filter, map, mergeMap } from "rxjs"
|
||||
|
||||
import { useUnstableProvider } from "./UnstableProvider"
|
||||
import { useMetadata } from "./MetadataProvider"
|
||||
|
||||
export const useDisabledValidators = () => {
|
||||
const { chainHead$, chainId } = useUnstableProvider()
|
||||
const metadata = useMetadata()
|
||||
const { data: disabledIndexes, error } = useSWRSubscription(
|
||||
chainHead$ && chainId && metadata
|
||||
? ["disabledIndexes", chainHead$, chainId, metadata]
|
||||
: null,
|
||||
([_, chainHead$, chainId, metadata], { next }) => {
|
||||
const { finalized$, storage$ } = chainHead$
|
||||
const subscription = finalized$.pipe(
|
||||
filter(Boolean),
|
||||
mergeMap((blockInfo) => {
|
||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||
const disabledIndexes = builder.buildStorage("Session", "DisabledValidators")
|
||||
return storage$(blockInfo?.hash, "value", () =>
|
||||
disabledIndexes?.keys.enc()
|
||||
).pipe(
|
||||
filter(Boolean),
|
||||
distinct(),
|
||||
map((value) => disabledIndexes?.value.dec(value))
|
||||
)
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(disabledIndexes) {
|
||||
next(null, disabledIndexes)
|
||||
},
|
||||
error: next,
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}
|
||||
)
|
||||
return disabledIndexes ? disabledIndexes : []
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
import useSWRSubscription from "swr/subscription"
|
||||
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
||||
import { distinct, filter, map, mergeMap } from "rxjs"
|
||||
|
||||
import { useUnstableProvider } from "./UnstableProvider"
|
||||
import { useMetadata } from "./MetadataProvider"
|
||||
|
||||
export const useErasTotalStake = ({ eraIndex }) => {
|
||||
const { chainHead$, chainId } = useUnstableProvider()
|
||||
const metadata = useMetadata()
|
||||
const { data: eraTotalStake } = useSWRSubscription(
|
||||
chainHead$ && chainId && metadata
|
||||
? ["eraTotalStake", chainHead$, eraIndex, chainId, metadata]
|
||||
: null,
|
||||
([_, chainHead$, eraIndex, chainId, metadata], { next }) => {
|
||||
const { finalized$, storage$ } = chainHead$
|
||||
const subscription = finalized$.pipe(
|
||||
filter(Boolean),
|
||||
mergeMap((blockInfo) => {
|
||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||
const eraTotalStake = builder.buildStorage("Staking", "ErasTotalStake")
|
||||
return storage$(blockInfo?.hash, "value", () =>
|
||||
eraTotalStake?.keys.enc(eraIndex)
|
||||
).pipe(
|
||||
filter(Boolean),
|
||||
distinct(),
|
||||
map((value) => eraTotalStake?.value.dec(value))
|
||||
)
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(eraTotalStake) {
|
||||
next(null, eraTotalStake)
|
||||
},
|
||||
error: next,
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}
|
||||
)
|
||||
return eraTotalStake;
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
import useSWRSubscription from "swr/subscription"
|
||||
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
||||
import { distinct, filter, map, mergeMap } from "rxjs"
|
||||
|
||||
import { useUnstableProvider } from "./UnstableProvider"
|
||||
import { useMetadata } from "./MetadataProvider"
|
||||
|
||||
export const useValidators = ({ currentSession }) => {
|
||||
const { chainHead$, chainId } = useUnstableProvider()
|
||||
const metadata = useMetadata()
|
||||
const { data: slowClapValidators } = useSWRSubscription(
|
||||
chainHead$ && chainId && metadata
|
||||
? ["slowClapValidators", chainHead$, currentSession, chainId, metadata]
|
||||
: null,
|
||||
([_, chainHead$, currentSession, chainId, metadata], { next }) => {
|
||||
const { finalized$, storage$ } = chainHead$
|
||||
const subscription = finalized$.pipe(
|
||||
filter(Boolean),
|
||||
mergeMap((blockInfo) => {
|
||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||
const slowClapValidators = builder.buildStorage("GhostSlowClaps", "Validators")
|
||||
return storage$(blockInfo?.hash, "value", () =>
|
||||
slowClapValidators?.keys.enc(currentSession)
|
||||
).pipe(
|
||||
filter(Boolean),
|
||||
distinct(),
|
||||
map((value) => slowClapValidators?.value.dec(value))
|
||||
)
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(slowClapValidators) {
|
||||
next(null, slowClapValidators)
|
||||
},
|
||||
error: next,
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}
|
||||
)
|
||||
return slowClapValidators
|
||||
}
|
||||
@ -1,594 +0,0 @@
|
||||
import { useReadContract, useReadContracts } from "wagmi";
|
||||
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||
import toast from "react-hot-toast";
|
||||
import { keccak256, stringToBytes } from 'viem'
|
||||
|
||||
import { config } from "../../config";
|
||||
|
||||
import {
|
||||
GHOST_GOVERNANCE_ADDRESSES,
|
||||
} from "../../constants/addresses";
|
||||
import { abi as GovernorAbi } from "../../abi/GhostGovernor.json";
|
||||
import { abi as GovernorCountingAbi } from "../../abi/GovernorGhostCounting.json";
|
||||
import { abi as GovernorStorageAbi } from "../../abi/GovernorStorage.json";
|
||||
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
|
||||
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { getTokenDecimals, getTokenAbi, getTokenAddress } from "../helpers";
|
||||
|
||||
export const useProposalVoteOf = (chainId, proposalId, who) => {
|
||||
const { data, error } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "voteOf",
|
||||
args: [proposalId, who],
|
||||
scopeKey: `voteOf-${chainId}-${proposalId?.toString()}-${who}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const voteOf = data ? BigInt(data) : 0n;
|
||||
return { voteOf };
|
||||
}
|
||||
|
||||
export const useProposalHash = (chainId, functions) => {
|
||||
const { proposalCount } = useProposalCount(chainId);
|
||||
const proposalDescription = `Proposal #${proposalCount}`;
|
||||
const descriptionHash = keccak256(stringToBytes(proposalDescription));
|
||||
|
||||
const { data: proposalHash, refetch } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "hashProposal",
|
||||
args: [
|
||||
functions.map(f => f.target),
|
||||
functions.map(f => f.value),
|
||||
functions.map(f => f.calldata),
|
||||
descriptionHash
|
||||
],
|
||||
scopeKey: `hashProposal-${chainId}-${functions.map(f => f.calldata)}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
return { proposalHash, proposalDescription };
|
||||
}
|
||||
|
||||
export const useActiveProposedLock = (chainId) => {
|
||||
const decimals = getTokenDecimals("GHST");
|
||||
|
||||
const { data: activeProposedLock, refetch } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "activeProposedLock",
|
||||
scopeKey: `activeProposedLock-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const result = new DecimalBigNumber(
|
||||
activeProposedLock ? activeProposedLock : 0n,
|
||||
decimals
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const useMinQuorum = (chainId) => {
|
||||
const { data: quorumNumerator, refetch: quorumNumeratorRefetch } = useReadContract({
|
||||
abi: GovernorVotesQuorumFractionAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "quorumNumerator",
|
||||
scopeKey: `quorumNumerator-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const { data: quorumDenominator, refetch: quorumDenominatorRefetch } = useReadContract({
|
||||
abi: GovernorVotesQuorumFractionAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "quorumDenominator",
|
||||
scopeKey: `quorumDenominator-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const numerator = quorumNumerator ?? 0n;
|
||||
const denominator = quorumDenominator ?? 1n;
|
||||
const percentage = Number(100n * numerator / denominator) / 100;
|
||||
|
||||
return { numerator, denominator, percentage }
|
||||
}
|
||||
|
||||
export const useProposalThreshold = (chainId, name) => {
|
||||
const decimals = getTokenDecimals(name);
|
||||
const { proposalCount } = useProposalCount(chainId);
|
||||
|
||||
const { data } = useReadContract({
|
||||
abi: GovernorStorageAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalThreshold",
|
||||
scopeKey: `proposalThreshold-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const { data: activeProposedLock } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "activeProposedLock",
|
||||
scopeKey: `activeProposedLock-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const lastIndex = proposalCount === 0n ? 0n : proposalCount - 1n;
|
||||
const { proposalId } = useProposalDetailsAt(chainId, lastIndex, {
|
||||
enabled: proposalCount !== 0n
|
||||
});
|
||||
const { state } = useProposalState(chainId, proposalId, {
|
||||
enabled: proposalCount !== 0n && !!proposalId
|
||||
});
|
||||
|
||||
let threshold = new DecimalBigNumber(data ?? 0n, decimals);
|
||||
|
||||
if (proposalCount !== 0n && state !== undefined && state < 2) {
|
||||
threshold = new DecimalBigNumber(activeProposedLock ?? 0n, decimals);
|
||||
}
|
||||
|
||||
return { threshold };
|
||||
}
|
||||
|
||||
export const useProposalCount = (chainId) => {
|
||||
const { data, refetch } = useReadContract({
|
||||
abi: GovernorStorageAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalCount",
|
||||
scopeKey: `proposalCount-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const proposalCount = data ?? 0n;
|
||||
return { proposalCount };
|
||||
}
|
||||
|
||||
export const useProposalState = (chainId, proposalId) => {
|
||||
const { data } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "state",
|
||||
args: [proposalId],
|
||||
scopeKey: `state-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const state = data ?? 0;
|
||||
return { state };
|
||||
}
|
||||
|
||||
export const useProposalProposer = (chainId, proposalId) => {
|
||||
const { data } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalProposer",
|
||||
args: [proposalId],
|
||||
scopeKey: `proposalProposer-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const proposer = data ? data : "0x0000000000000000000000000000000000000000";
|
||||
return { proposer };
|
||||
}
|
||||
|
||||
export const useProposalLocked = (chainId, proposalId) => {
|
||||
const decimals = getTokenDecimals(name);
|
||||
const { data } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "lockedAmounts",
|
||||
args: [proposalId],
|
||||
scopeKey: `lockedAmounts-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const locked = new DecimalBigNumber(data ?? 0n, decimals);
|
||||
return { locked }
|
||||
}
|
||||
|
||||
export const useProposalQuorum = (chainId, proposalId) => {
|
||||
const decimals = getTokenDecimals(name);
|
||||
const { snapshot } = useProposalSnapshot(chainId, proposalId);
|
||||
const { data } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "quorum",
|
||||
args: [snapshot],
|
||||
scopeKey: `quorum-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const quorum = new DecimalBigNumber(data ?? 0n, decimals);
|
||||
return { quorum }
|
||||
}
|
||||
|
||||
export const useProposalVotes = (chainId, proposalId) => {
|
||||
const decimals = getTokenDecimals(name);
|
||||
|
||||
const { data } = useReadContract({
|
||||
abi: GovernorCountingAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalVotes",
|
||||
args: [proposalId],
|
||||
scopeKey: `proposalVotes-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const againstVotes = new DecimalBigNumber(data?.at(0) ?? 0n, decimals);
|
||||
const forVotes = new DecimalBigNumber(data?.at(1) ?? 0n, decimals);
|
||||
const totalVotes = new DecimalBigNumber(
|
||||
(data?.at(0) ?? 0n) + (data?.at(1) ?? 0n),
|
||||
decimals
|
||||
);
|
||||
|
||||
return { forVotes, againstVotes, totalVotes }
|
||||
}
|
||||
|
||||
export const useProposalSnapshot = (chainId, proposalId) => {
|
||||
const { data, error } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalSnapshot",
|
||||
args: [proposalId],
|
||||
scopeKey: `proposalSnapshot-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const snapshot = data ?? 0n;
|
||||
return { snapshot };
|
||||
}
|
||||
|
||||
export const useProposalDetailsAt = (chainId, index) => {
|
||||
const { data, error } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalDetailsAt",
|
||||
args: [index],
|
||||
scopeKey: `proposalDetailsAt-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const proposalId = data?.at(0) ?? 0n;
|
||||
const proposalDetailsAt = data?.at(1)?.map((target, index) => ({
|
||||
target,
|
||||
value: data?.at(2)?.at(index),
|
||||
calldata: data?.at(3)?.at(index),
|
||||
|
||||
}));
|
||||
|
||||
return { proposalDetailsAt, proposalId };
|
||||
}
|
||||
|
||||
export const useProposalDetails = (chainId, proposalId) => {
|
||||
const { data } = useReadContract({
|
||||
abi: GovernorStorageAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalDetails",
|
||||
args: [proposalId],
|
||||
scopeKey: `proposalDetails-${chainId}-${proposalId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const proposalDetails = data?.at(0)?.map((target, index) => ({
|
||||
target,
|
||||
value: data?.at(1)?.at(index),
|
||||
calldata: data?.at(2)?.at(index),
|
||||
}));
|
||||
|
||||
return { proposalDetails };
|
||||
}
|
||||
|
||||
export const useProposalDeadline = (chainId, proposalId) => {
|
||||
const { data, refetch } = useReadContract({
|
||||
abi: GovernorStorageAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
args: [proposalId],
|
||||
functionName: "proposalDeadline",
|
||||
scopeKey: `proposalDeadline-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const deadline = data ?? 0n;
|
||||
return { deadline };
|
||||
}
|
||||
|
||||
export const useProposalVotingDelay = (chainId) => {
|
||||
const { data } = useReadContract({
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "votingDelay",
|
||||
scopeKey: `votingDelay-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const delay = data ?? 0n;
|
||||
return { delay };
|
||||
}
|
||||
|
||||
export const useProposals = (chainId, depth, searchedIndexes) => {
|
||||
const decimals = getTokenDecimals("GHST");
|
||||
const ghstAbi = getTokenAbi("GHST");
|
||||
const ghstAddress = getTokenAddress(chainId, "GHST");
|
||||
|
||||
const { proposalCount } = useProposalCount(chainId);
|
||||
|
||||
const start = Number(proposalCount);
|
||||
const end = Math.max(0, start - depth);
|
||||
const indexes = searchedIndexes
|
||||
? searchedIndexes.map((_, i) => i)
|
||||
: [...Array(start - end)].map((_, i) => start - 1 - i);
|
||||
|
||||
const { data: proposalsDetailsAt } = useReadContracts({
|
||||
contracts: indexes?.map(index => {
|
||||
return {
|
||||
abi: GovernorStorageAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalDetailsAt",
|
||||
args: [index],
|
||||
scopeKey: `proposalDetailsAt-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { data: proposalDetails } = useReadContracts({
|
||||
contracts: searchedIndexes?.map(index => {
|
||||
return {
|
||||
abi: GovernorStorageAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalDetails",
|
||||
args: [index],
|
||||
scopeKey: `proposalDetails-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { data: proposalDeadlines } = useReadContracts({
|
||||
contracts: indexes?.map(index => {
|
||||
const proposalId = searchedIndexes
|
||||
? searchedIndexes?.at(index)
|
||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||
|
||||
return {
|
||||
abi: GovernorStorageAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalDeadline",
|
||||
args: [proposalId],
|
||||
scopeKey: `proposalDeadline-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { data: proposalVotes } = useReadContracts({
|
||||
contracts: indexes?.map(index => {
|
||||
const proposalId = searchedIndexes
|
||||
? searchedIndexes?.at(index)
|
||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||
|
||||
return {
|
||||
abi: GovernorCountingAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalVotes",
|
||||
args: [proposalId],
|
||||
scopeKey: `proposalVotes-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { data: proposalStates } = useReadContracts({
|
||||
contracts: indexes?.map(index => {
|
||||
const proposalId = searchedIndexes
|
||||
? searchedIndexes?.at(index)
|
||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||
|
||||
return {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "state",
|
||||
args: [proposalId],
|
||||
scopeKey: `state-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { data: proposalSnapshots } = useReadContracts({
|
||||
contracts: indexes?.map(index => {
|
||||
const proposalId = searchedIndexes
|
||||
? searchedIndexes?.at(index)
|
||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||
|
||||
return {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalSnapshot",
|
||||
args: [proposalId],
|
||||
scopeKey: `proposalSnapshot-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { data: proposalQuorums } = useReadContracts({
|
||||
contracts: proposalSnapshots?.map((proposal, index) => {
|
||||
const timepoint = proposal?.result;
|
||||
return {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "quorum",
|
||||
args: [timepoint],
|
||||
scopeKey: `quorum-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { data: pastTotalSupplies } = useReadContracts({
|
||||
contracts: proposalSnapshots?.map((proposal, index) => {
|
||||
const timepoint = proposal?.result;
|
||||
return {
|
||||
abi: ghstAbi,
|
||||
address: ghstAddress,
|
||||
functionName: "getPastTotalSupply",
|
||||
args: [timepoint],
|
||||
scopeKey: `getPastTotalSupply-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { data: proposalProposer } = useReadContracts({
|
||||
contracts: indexes?.map(index => {
|
||||
const proposalId = searchedIndexes
|
||||
? searchedIndexes?.at(index)
|
||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||
|
||||
return {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "proposalProposer",
|
||||
args: [proposalId],
|
||||
scopeKey: `proposalProposer-${chainId}-${index}`,
|
||||
chainId: chainId,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const hashes = indexes?.map(index => {
|
||||
let result = { short: index + 1, full: undefined };
|
||||
const proposalId = searchedIndexes
|
||||
? searchedIndexes?.at(index)
|
||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||
|
||||
if (proposalId) {
|
||||
const hash = "0x" + proposalId.toString(16);
|
||||
result.short = hash.slice(-5);
|
||||
result.full = hash;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
const proposals = indexes?.map(index => ({
|
||||
hashes: hashes?.at(index),
|
||||
proposer: proposalProposer?.at(index)?.result,
|
||||
details: proposalsDetailsAt?.at(index)?.result,
|
||||
deadline: proposalDeadlines?.at(index)?.result ?? 0n,
|
||||
snapshot: proposalSnapshots?.at(index)?.result ?? 0n,
|
||||
state: proposalStates?.at(index)?.result ?? 0,
|
||||
pastTotalSupply: new DecimalBigNumber(pastTotalSupplies?.at(index)?.result ?? 0n, decimals),
|
||||
quorum: new DecimalBigNumber(proposalQuorums?.at(index)?.result ?? 0n, decimals),
|
||||
votes: proposalVotes?.at(index)?.result?.map(
|
||||
vote => new DecimalBigNumber(vote ?? 0n, decimals),
|
||||
),
|
||||
}));
|
||||
|
||||
return { proposals };
|
||||
}
|
||||
|
||||
export const releaseLocked = async (chainId, account, proposalId) => {
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: 'releaseLocked',
|
||||
args: [proposalId],
|
||||
account: account,
|
||||
chainId: chainId
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("Release locked transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
toast.success("Successfully release locked funds from the governor.");
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Release locked funds failed. Check logs for error detalization.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const executeProposal = async (chainId, account, proposalId) => {
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: 'execute',
|
||||
args: [proposalId],
|
||||
account: account,
|
||||
chainId: chainId
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("Proposal execution transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
toast.success("Proposal execution was successful, wait for updates.");
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Proposal execution failed. Check logs for error detalization.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const castVote = async (chainId, account, proposalId, support) => {
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: 'castVote',
|
||||
args: [proposalId, support],
|
||||
account: account,
|
||||
chainId: chainId
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("Cast vote transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
toast.success("Successfully casted a vote, should be applied the proposal.");
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Vote cast failed. Check logs for error detalization.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const propose = async (chainId, account, functions, description) => {
|
||||
const targets = functions.map(f => f.target);
|
||||
const values = functions.map(f => f.value);
|
||||
const calldatas = functions.map(f => f.calldata);
|
||||
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: 'propose',
|
||||
args: [targets, values, calldatas, description],
|
||||
account: account,
|
||||
chainId: chainId
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("Proposal transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
toast.success("Successfully proposed a set of functions to be executed.");
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Proposal creation failed. Check logs for error detalization.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,9 @@
|
||||
import {
|
||||
RESERVE_ADDRESSES,
|
||||
DAI_ADDRESSES,
|
||||
FTSO_ADDRESSES,
|
||||
STNK_ADDRESSES,
|
||||
GHST_ADDRESSES,
|
||||
FTSO_DAI_LP_ADDRESSES,
|
||||
WETH_ADDRESSES,
|
||||
} from "../constants/addresses";
|
||||
|
||||
import { abi as DaiAbi } from "../abi/Reserve.json";
|
||||
@ -12,7 +11,6 @@ import { abi as FatsoAbi } from "../abi/Fatso.json";
|
||||
import { abi as StinkyAbi } from "../abi/Stinky.json";
|
||||
import { abi as GhostAbi } from "../abi/Ghost.json";
|
||||
import { abi as Erc20Abi } from "../abi/ERC20.json";
|
||||
import { abi as WethAbi } from "../abi/WETH9.json";
|
||||
|
||||
// TBD: should be extended on new tokens
|
||||
export const getTokenAbi = (name) => {
|
||||
@ -24,9 +22,6 @@ export const getTokenAbi = (name) => {
|
||||
case "GDAI":
|
||||
abi = DaiAbi;
|
||||
break;
|
||||
case "RESERVE":
|
||||
abi = DaiAbi;
|
||||
break;
|
||||
case "FTSO":
|
||||
abi = FatsoAbi;
|
||||
break;
|
||||
@ -45,12 +40,6 @@ export const getTokenAbi = (name) => {
|
||||
case "CSPR":
|
||||
abi = GhostAbi;
|
||||
break;
|
||||
case "WETH":
|
||||
abi = WethAbi;
|
||||
break;
|
||||
case "WMWETH":
|
||||
abi = WethAbi;
|
||||
break;
|
||||
}
|
||||
return abi;
|
||||
}
|
||||
@ -65,9 +54,6 @@ export const getTokenDecimals = (name) => {
|
||||
case "GDAI":
|
||||
decimals = 18;
|
||||
break;
|
||||
case "RESERVE":
|
||||
decimals = 18;
|
||||
break;
|
||||
case "FTSO":
|
||||
decimals = 9;
|
||||
break;
|
||||
@ -86,12 +72,6 @@ export const getTokenDecimals = (name) => {
|
||||
case "CSPR":
|
||||
decimals = 18;
|
||||
break;
|
||||
case "WETH":
|
||||
decimals = 18;
|
||||
break;
|
||||
case "WMWETH":
|
||||
decimals = 18;
|
||||
break;
|
||||
}
|
||||
return decimals;
|
||||
}
|
||||
@ -101,13 +81,10 @@ export const getTokenAddress = (chainId, name) => {
|
||||
let address = name;
|
||||
switch (name?.toUpperCase()) {
|
||||
case "DAI":
|
||||
address = RESERVE_ADDRESSES[chainId];
|
||||
address = DAI_ADDRESSES[chainId];
|
||||
break;
|
||||
case "GDAI":
|
||||
address = RESERVE_ADDRESSES[chainId];
|
||||
break;
|
||||
case "RESERVE":
|
||||
address = RESERVE_ADDRESSES[chainId];
|
||||
address = DAI_ADDRESSES[chainId];
|
||||
break;
|
||||
case "FTSO":
|
||||
address = FTSO_ADDRESSES[chainId];
|
||||
@ -130,27 +107,16 @@ export const getTokenAddress = (chainId, name) => {
|
||||
case "GDAI_FTSO":
|
||||
address = FTSO_DAI_LP_ADDRESSES[chainId];
|
||||
break;
|
||||
case "RESERVE_FTSO":
|
||||
address = FTSO_DAI_LP_ADDRESSES[chainId];
|
||||
break;
|
||||
case "WETH":
|
||||
address = WETH_ADDRESSES[chainId];
|
||||
break;
|
||||
case "WETC":
|
||||
address = WETH_ADDRESSES[chainId];
|
||||
break;
|
||||
case "WMETC":
|
||||
address = WETH_ADDRESSES[chainId];
|
||||
break;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
// TBD: should be extended on new tokens
|
||||
export const getTokenIcons = (chainId, address) => {
|
||||
let icons = [""];
|
||||
switch (address) {
|
||||
case RESERVE_ADDRESSES[chainId]:
|
||||
icons = ["WETH"];
|
||||
case DAI_ADDRESSES[chainId]:
|
||||
icons = ["GDAI"];
|
||||
break;
|
||||
case FTSO_ADDRESSES[chainId]:
|
||||
icons = ["FTSO"];
|
||||
@ -162,7 +128,7 @@ export const getTokenIcons = (chainId, address) => {
|
||||
icons = ["GHST"];
|
||||
break;
|
||||
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
||||
icons = ["FTSO", "WETH"];
|
||||
icons = ["FTSO", "GDAI"];
|
||||
break;
|
||||
}
|
||||
return icons;
|
||||
@ -178,14 +144,11 @@ export const getBondNameDisplayName = (chainId, stringValue, tokenAddress) => {
|
||||
export const getTokenPurchaseLink = (chainId, tokenAddress) => {
|
||||
let purchaseUrl = "https://app.dao.ghostchain.io/#/dex/uniswap";
|
||||
switch (tokenAddress) {
|
||||
case RESERVE_ADDRESSES[chainId]:
|
||||
case DAI_ADDRESSES[chainId]:
|
||||
purchaseUrl = "https://app.dao.ghostchain.io/#/faucet";
|
||||
if (chainId == 63) {
|
||||
purchaseUrl = "https://app.dao.ghostchain.io/#/wrapper";
|
||||
}
|
||||
break;
|
||||
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
||||
purchaseUrl += `?pool=true&from=${RESERVE_ADDRESSES[chainId]}&to=${FTSO_ADDRESSES[chainId]}`;
|
||||
purchaseUrl += `?pool=true&from=${DAI_ADDRESSES[chainId]}&to=${FTSO_ADDRESSES[chainId]}`;
|
||||
break;
|
||||
}
|
||||
return purchaseUrl;
|
||||
|
||||
@ -1,144 +1,26 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useReadContract } from "wagmi";
|
||||
|
||||
import { useCurrentIndex, useGhostedSupply } from "../staking";
|
||||
import { useUniswapV2PairReserves } from "../uniswapv2";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import {
|
||||
FTSO_DAI_LP_ADDRESSES,
|
||||
RESERVE_ADDRESSES,
|
||||
FTSO_ADDRESSES,
|
||||
CEX_TICKERS,
|
||||
NATIVE_TICKERS,
|
||||
} from "../../constants/addresses";
|
||||
import { FTSO_DAI_LP_ADDRESSES, DAI_ADDRESSES, FTSO_ADDRESSES } from "../../constants/addresses";
|
||||
|
||||
const cexPriceGetters = new Map();
|
||||
|
||||
function callWithCacheTTL(fn, ttlMs = 10000) {
|
||||
let lastFetchTime = 0;
|
||||
let cachedValue;
|
||||
let inFlight = null;
|
||||
|
||||
return function(...args) {
|
||||
const now = Date.now();
|
||||
|
||||
if ((now - lastFetchTime) < ttlMs) {
|
||||
return inFlight ? inFlight : Promise.resolve(cachedValue);
|
||||
}
|
||||
|
||||
lastFetchTime = now;
|
||||
inFlight = Promise.resolve(fn(...args))
|
||||
.then(res => {
|
||||
cachedValue = res;
|
||||
inFlight = null;
|
||||
return res;
|
||||
})
|
||||
.catch(err => {
|
||||
inFlight = null;
|
||||
throw err;
|
||||
});
|
||||
|
||||
return inFlight;
|
||||
}
|
||||
}
|
||||
|
||||
export const useNativePrice = (chainId) => {
|
||||
const [nativePrice, setNativePrice] = useState(new DecimalBigNumber(1000000000000000000n, 18));
|
||||
const cexApis = NATIVE_TICKERS[chainId];
|
||||
|
||||
useEffect(() => {
|
||||
if (!cexApis) {
|
||||
setNativePrice(new DecimalBigNumber(1000000000000000000n, 18));
|
||||
return;
|
||||
}
|
||||
|
||||
let getCexPriceCached = cexPriceGetters.get(chainId);
|
||||
if (!getCexPriceCached) {
|
||||
getCexPriceCached = callWithCacheTTL(() => {
|
||||
const fetchPromises = cexApis.map(url => fetch(url)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return res.json();
|
||||
})
|
||||
)
|
||||
return Promise.any(fetchPromises);
|
||||
}, 5000);
|
||||
cexPriceGetters.set(chainId, getCexPriceCached);
|
||||
}
|
||||
|
||||
getCexPriceCached()
|
||||
.then(response => {
|
||||
if ('data' in response) {
|
||||
const coinPrice = Number(response?.data?.amount ?? 0.0);
|
||||
const priceInWei = Math.floor(coinPrice * 1e18 / 0.99);
|
||||
setNativePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
|
||||
} else if ('price' in response) {
|
||||
const coinPrice = Number(response?.price ?? 0.0);
|
||||
const priceInWei = Math.floor(coinPrice * 1e18)
|
||||
setNativePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
|
||||
} else {
|
||||
throw Error("Unexpected json in response.");
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setNativePrice(new DecimalBigNumber(0n, 18));
|
||||
});
|
||||
}, [chainId, cexApis])
|
||||
|
||||
return nativePrice;
|
||||
}
|
||||
|
||||
export const useReservePrice = (chainId) => {
|
||||
const [reservePrice, setReservePrice] = useState(new DecimalBigNumber(1000000000000000000n, 18));
|
||||
const cexApis = CEX_TICKERS[chainId];
|
||||
|
||||
useEffect(() => {
|
||||
if (!cexApis) {
|
||||
setReservePrice(new DecimalBigNumber(1000000000000000000n, 18));
|
||||
return;
|
||||
}
|
||||
|
||||
let getCexPriceCached = cexPriceGetters.get(chainId);
|
||||
if (!getCexPriceCached) {
|
||||
getCexPriceCached = callWithCacheTTL(() => {
|
||||
const fetchPromises = cexApis.map(url => fetch(url)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return res.json();
|
||||
})
|
||||
)
|
||||
return Promise.any(fetchPromises);
|
||||
}, 5000);
|
||||
cexPriceGetters.set(chainId, getCexPriceCached);
|
||||
}
|
||||
|
||||
getCexPriceCached()
|
||||
.then(response => {
|
||||
if ('data' in response) {
|
||||
const coinPrice = Number(response?.data?.amount ?? 0.0);
|
||||
const priceInWei = Math.floor(coinPrice * 1e18 / 0.99);
|
||||
setReservePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
|
||||
} else if ('price' in response) {
|
||||
const coinPrice = Number(response?.price ?? 0.0);
|
||||
const priceInWei = Math.floor(coinPrice * 1e18)
|
||||
setReservePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
|
||||
} else {
|
||||
throw Error("Unexpected json in response.");
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setReservePrice(new DecimalBigNumber(0n, 18));
|
||||
});
|
||||
}, [chainId, cexApis])
|
||||
|
||||
return reservePrice;
|
||||
export const useDaiPrice = (chainId) => {
|
||||
const daiPrice = new DecimalBigNumber(1000000000000000000n, 18);
|
||||
return daiPrice;
|
||||
};
|
||||
|
||||
export const useFtsoPrice = (chainId) => {
|
||||
const { reserves, tokens, refetch } = useUniswapV2PairReserves(chainId, FTSO_DAI_LP_ADDRESSES[chainId]);
|
||||
const { reserves, tokens, refetch } = useUniswapV2PairReserves(
|
||||
chainId,
|
||||
FTSO_DAI_LP_ADDRESSES[chainId],
|
||||
9,
|
||||
18,
|
||||
"FTSO",
|
||||
"GDAI",
|
||||
);
|
||||
|
||||
const reservePrice = useReservePrice(chainId);
|
||||
const reserveAddress = RESERVE_ADDRESSES[chainId];
|
||||
const reserveAddress = DAI_ADDRESSES[chainId];
|
||||
const ftsoAddress = FTSO_ADDRESSES[chainId];
|
||||
if (!reserveAddress || !ftsoAddress) {
|
||||
return new DecimalBigNumber(0n, 9);
|
||||
@ -155,9 +37,8 @@ export const useFtsoPrice = (chainId) => {
|
||||
let price = 0n
|
||||
if (ftsoReserves > 0n)
|
||||
price = stableReserves / ftsoReserves;
|
||||
price = price * reservePrice._value;
|
||||
|
||||
return new DecimalBigNumber(price, 27);
|
||||
return new DecimalBigNumber(price, 9);
|
||||
};
|
||||
|
||||
export const useStnkPrice = (chainId) => {
|
||||
|
||||
@ -6,43 +6,8 @@ import { getTokenAbi, getTokenAddress, getTokenDecimals } from "../helpers";
|
||||
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { shorten } from "../../helpers";
|
||||
import { tokenNameConverter } from "../../helpers/tokenConverter";
|
||||
import { config } from "../../config";
|
||||
|
||||
export const usePastVotes = (chainId, name, timepoint, address) => {
|
||||
const decimals = getTokenDecimals(name);
|
||||
const contractAddress = getTokenAddress(chainId, name);
|
||||
|
||||
const { data, refetch, error } = useReadContract({
|
||||
abi: getTokenAbi(name),
|
||||
address: contractAddress,
|
||||
functionName: "getPastVotes",
|
||||
args: [address, timepoint],
|
||||
scopeKey: `getPastVotes-${timepoint}-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const pastVotes = new DecimalBigNumber(data ? data : 0n, decimals);
|
||||
return { pastVotes }
|
||||
}
|
||||
|
||||
export const usePastTotalSupply = (chainId, name, timepoint) => {
|
||||
const decimals = getTokenDecimals(name);
|
||||
const contractAddress = getTokenAddress(chainId, name);
|
||||
|
||||
const { data, refetch, error } = useReadContract({
|
||||
abi: getTokenAbi(name),
|
||||
address: contractAddress,
|
||||
functionName: "getPastTotalSupply",
|
||||
args: [timepoint],
|
||||
scopeKey: `getPastTotalSupply-${timepoint}-${chainId}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const pastTotalSupply = new DecimalBigNumber(data ? data : 0n, decimals);
|
||||
return { pastTotalSupply, refetch };
|
||||
}
|
||||
|
||||
export const useTotalSupply = (chainId, name) => {
|
||||
const contractAddress = getTokenAddress(chainId, name);
|
||||
const { data, refetch } = useToken({
|
||||
@ -60,7 +25,7 @@ export const useTotalSupply = (chainId, name) => {
|
||||
|
||||
export const useBalance = (chainId, name, address) => {
|
||||
const contractAddress = getTokenAddress(chainId, name);
|
||||
const { data, refetch, error } = useInnerBalance({
|
||||
const { data, refetch } = useInnerBalance({
|
||||
address,
|
||||
chainId,
|
||||
scopeKey: `balance-${contractAddress}-${address}-${chainId}`,
|
||||
@ -103,9 +68,7 @@ export const useTokenSymbol = (chainId, name) => {
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
let symbol = data ? data : "";
|
||||
symbol = tokenNameConverter(chainId, symbol);
|
||||
|
||||
const symbol = data ? data : "";
|
||||
return { symbol, refetch };
|
||||
}
|
||||
|
||||
@ -266,53 +229,3 @@ export const burnDai = async (chainId, account, value) => {
|
||||
toast.error("Burning gDAI from the faucet failed. Check logs for error detalization.")
|
||||
}
|
||||
}
|
||||
|
||||
export const depositNative = async (chainId, account, value) => {
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: getTokenAbi("WETH"),
|
||||
address: getTokenAddress(chainId, "WETH"),
|
||||
functionName: 'deposit',
|
||||
account: account,
|
||||
chainId: chainId,
|
||||
value: value
|
||||
});
|
||||
const txHash = await writeContract(config, request);
|
||||
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("WETH9 deposit transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
toast.success("WETH9 successfully minted to your wallet! Check your wallet balance.");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("WETH9 wrapping failed. Check logs for error detalization.")
|
||||
}
|
||||
}
|
||||
|
||||
export const withdrawWeth = async (chainId, account, value) => {
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: getTokenAbi("WETH"),
|
||||
address: getTokenAddress(chainId, "WETH"),
|
||||
functionName: 'withdraw',
|
||||
args: [value],
|
||||
account: account,
|
||||
chainId: chainId
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("WETH9 withdraw transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
toast.success("WETH9 successfully burned for native coins! Check your wallet balance.");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("WETH9 unwrapping failed. Check logs for error detalization.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,23 +3,9 @@ import { useReadContract } from "wagmi";
|
||||
import { DAO_TREASURY_ADDRESSES } from "../../constants/addresses";
|
||||
import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
|
||||
|
||||
import { useReservePrice } from "../prices/index";
|
||||
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { getTokenAddress } from "../helpers";
|
||||
|
||||
export const useOrinalCoefficient = (chainId) => {
|
||||
const { data: original } = useReadContract({
|
||||
abi: TreasuryAbi,
|
||||
address: DAO_TREASURY_ADDRESSES[chainId],
|
||||
functionName: "originalCoefficient",
|
||||
scopeKey: `originalCoefficient-${chainId}`,
|
||||
chainId: chainId
|
||||
});
|
||||
|
||||
return new DecimalBigNumber(original ? original : 1000000000000000000n, 18);
|
||||
}
|
||||
|
||||
export const useTotalReserves = (chainId) => {
|
||||
const { data: totalReservesRaw } = useReadContract({
|
||||
abi: TreasuryAbi,
|
||||
@ -29,13 +15,10 @@ export const useTotalReserves = (chainId) => {
|
||||
chainId: chainId,
|
||||
});
|
||||
|
||||
const original = useOrinalCoefficient(chainId);
|
||||
const price = useReservePrice(chainId);
|
||||
|
||||
const totalReservesPrepared = totalReservesRaw ? totalReservesRaw : 0n;
|
||||
const totalReserves = new DecimalBigNumber(totalReservesPrepared, 9);
|
||||
|
||||
return totalReserves.mul(price).div(original);
|
||||
return totalReserves;
|
||||
};
|
||||
|
||||
export const useLpValuation = (chainId, pairAddress, pairSupply) => {
|
||||
|
||||
@ -128,48 +128,3 @@ a:hover svg {
|
||||
.tooltip {
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
.custom-scrollbar {
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
|
||||
/* For Chrome, Safari, Edge */
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent; /* Hide track */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #888; /* Only visible part */
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* This definitely hides arrows in Chrome/Safari/Edge */
|
||||
&::-webkit-scrollbar-button {
|
||||
display: none; /* ← THIS WORKS */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: transparent; /* Hide corner */
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
scrollbar-width: thin; /* auto | thin | none */
|
||||
scrollbar-color: #fff transparent; /* thumb track */
|
||||
}
|
||||
|
||||
input:-webkit-autofill {
|
||||
-webkit-box-shadow: 0 0 0 1000px transparent inset !important;
|
||||
box-shadow: 0 0 0 1000px transparent inset !important;
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
color: #ffffff !important;
|
||||
-webkit-text-fill-color: #ffffff !important;
|
||||
}
|
||||
|
||||
@ -18,18 +18,9 @@ export const darkPalette = {
|
||||
success: "#60C45B", // idk where this is - done
|
||||
userFeedback: "#49A1F2", // idk where this is
|
||||
error: "#F06F73", // red negative % - done
|
||||
warning: "#ed6c02", // idk where this is - done
|
||||
warning: "#49A1F2", // idk where this is - done
|
||||
pnlGain: "#60C45B", // green positive % - done
|
||||
},
|
||||
bridgeProgress: {
|
||||
error: "#F06F73",
|
||||
warning: "#ed6c02",
|
||||
success: "#60C45B",
|
||||
},
|
||||
validatorsColor: {
|
||||
red: "#fd9b9e",
|
||||
green: "#60c45b",
|
||||
},
|
||||
gray: {
|
||||
800: "#1F4671", // active menu - done
|
||||
700: "#50759E", // menu background color - done
|
||||
|
||||
@ -6,20 +6,11 @@ export const lightPalette = {
|
||||
},
|
||||
background: "linear-gradient(180.37deg, #B3BFC5 0.49%, #D1D5D4 26.3%, #EEEAE3 99.85%)",
|
||||
feedback: {
|
||||
success: "#60C45B", // idk where this is - done
|
||||
userFeedback: "#49A1F2", // idk where this is
|
||||
error: "#F06F73", // red negative % - done
|
||||
warning: "#ed6c02", // idk where this is - done
|
||||
pnlGain: "#60C45B", // green positive % - done
|
||||
},
|
||||
bridgeProgress: {
|
||||
error: "#F06F73",
|
||||
warning: "#ed6c02",
|
||||
success: "#60C45B",
|
||||
},
|
||||
validatorsColor: {
|
||||
red: "#fd9b9e",
|
||||
green: "#60c45b",
|
||||
success: "#94B9A1",
|
||||
userFeedback: "#49A1F2",
|
||||
error: "#FF6767",
|
||||
warning: "#FC8E5F",
|
||||
pnlGain: "#3D9C70",
|
||||
},
|
||||
gray: {
|
||||
700: "#FAFAFB",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user