Compare commits

..

No commits in common. "main" and "extension-integration" have entirely different histories.

103 changed files with 1738 additions and 7716 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "ghost-dao-interface", "name": "ghost-dao-interface",
"private": true, "private": true,
"version": "0.7.7", "version": "0.2.15",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@ -16,7 +16,6 @@
"@ethersproject/bignumber": "^5.8.0", "@ethersproject/bignumber": "^5.8.0",
"@ethersproject/units": "^5.8.0", "@ethersproject/units": "^5.8.0",
"@mui/icons-material": "^6.4.7", "@mui/icons-material": "^6.4.7",
"@mui/lab": "6.0.1-beta.36",
"@mui/material": "^6.4.7", "@mui/material": "^6.4.7",
"@mui/utils": "^6.4.6", "@mui/utils": "^6.4.6",
"@polkadot-api/metadata-builders": "0.13.0", "@polkadot-api/metadata-builders": "0.13.0",

View File

@ -26,9 +26,6 @@ importers:
'@mui/icons-material': '@mui/icons-material':
specifier: ^6.4.7 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) 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': '@mui/material':
specifier: ^6.4.7 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) 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': '@ethersproject/units@5.8.0':
resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} 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': '@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'} engines: {node: '>=18.18.0'}
@ -656,18 +638,6 @@ packages:
resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==} resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==}
engines: {node: '>=16.0.0'} 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': '@mui/core-downloads-tracker@6.4.7':
resolution: {integrity: sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==} resolution: {integrity: sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==}
@ -682,27 +652,6 @@ packages:
'@types/react': '@types/react':
optional: true 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': '@mui/material@6.4.7':
resolution: {integrity: sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==} resolution: {integrity: sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@ -733,16 +682,6 @@ packages:
'@types/react': '@types/react':
optional: true 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': '@mui/styled-engine@6.4.6':
resolution: {integrity: sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==} resolution: {integrity: sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@ -756,19 +695,6 @@ packages:
'@emotion/styled': '@emotion/styled':
optional: true 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': '@mui/system@6.4.7':
resolution: {integrity: sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==} resolution: {integrity: sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@ -785,22 +711,6 @@ packages:
'@types/react': '@types/react':
optional: true 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': '@mui/types@7.2.21':
resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==} resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==}
peerDependencies: peerDependencies:
@ -809,14 +719,6 @@ packages:
'@types/react': '@types/react':
optional: true 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': '@mui/utils@6.4.6':
resolution: {integrity: sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==} resolution: {integrity: sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@ -827,16 +729,6 @@ packages:
'@types/react': '@types/react':
optional: true 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': '@noble/ciphers@1.2.1':
resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==}
engines: {node: ^14.21.3 || >=16} engines: {node: ^14.21.3 || >=16}
@ -3531,23 +3423,6 @@ snapshots:
'@ethersproject/constants': 5.8.0 '@ethersproject/constants': 5.8.0
'@ethersproject/logger': 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/core@0.19.1': {}
'@humanfs/node@0.16.6': '@humanfs/node@0.16.6':
@ -3740,20 +3615,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - 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/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)': '@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: optionalDependencies:
'@types/react': 19.0.10 '@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)': '@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: dependencies:
'@babel/runtime': 7.26.9 '@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)': '@mui/private-theming@6.4.6(@types/react@19.0.10)(react@19.0.0)':
dependencies: 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) '@mui/utils': 6.4.6(@types/react@19.0.10)(react@19.0.0)
prop-types: 15.8.1 prop-types: 15.8.1
react: 19.0.0 react: 19.0.0
optionalDependencies: optionalDependencies:
'@types/react': 19.0.10 '@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)': '@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: 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
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
'@emotion/cache': 11.14.0 '@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3 '@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0 '@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) '@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 '@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)': '@mui/types@7.2.21(@types/react@19.0.10)':
optionalDependencies: optionalDependencies:
'@types/react': 19.0.10 '@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)': '@mui/utils@6.4.6(@types/react@19.0.10)(react@19.0.0)':
dependencies: dependencies:
'@babel/runtime': 7.26.9 '@babel/runtime': 7.26.9
@ -3898,18 +3700,6 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 19.0.10 '@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.2.1': {}
'@noble/ciphers@1.3.0': {} '@noble/ciphers@1.3.0': {}
@ -5252,7 +5042,7 @@ snapshots:
babel-plugin-macros@3.1.0: babel-plugin-macros@3.1.0:
dependencies: dependencies:
'@babel/runtime': 7.27.6 '@babel/runtime': 7.26.9
cosmiconfig: 7.1.0 cosmiconfig: 7.1.0
resolve: 1.22.10 resolve: 1.22.10
@ -5448,7 +5238,7 @@ snapshots:
dom-helpers@5.2.1: dom-helpers@5.2.1:
dependencies: dependencies:
'@babel/runtime': 7.27.6 '@babel/runtime': 7.26.9
csstype: 3.1.3 csstype: 3.1.3
dot-case@3.0.4: dot-case@3.0.4:

View File

@ -5,16 +5,8 @@ import CssBaseline from "@mui/material/CssBaseline";
import { styled, ThemeProvider } from "@mui/material/styles"; import { styled, ThemeProvider } from "@mui/material/styles";
import { lazy, Suspense, useCallback, useEffect, useState } from "react"; import { lazy, Suspense, useCallback, useEffect, useState } from "react";
import toast, { Toaster } from "react-hot-toast"; import toast, { Toaster } from "react-hot-toast";
import { Outlet, Navigate, Route, Routes, useLocation, useParams } from "react-router-dom"; import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import { import { useAccount, useConnect, useChainId, useConfig, usePublicClient, injected } from "wagmi";
useAccount,
useConnect,
useChainId,
useConfig,
usePublicClient,
useSwitchChain,
injected
} from "wagmi";
import Messages from "./components/Messages/Messages"; import Messages from "./components/Messages/Messages";
import NavDrawer from "./components/Sidebar/NavDrawer"; import NavDrawer from "./components/Sidebar/NavDrawer";
@ -22,7 +14,7 @@ import Sidebar from "./components/Sidebar/Sidebar";
import TopBar from "./components/TopBar/TopBar"; import TopBar from "./components/TopBar/TopBar";
import { shouldTriggerSafetyCheck } from "./helpers"; import { shouldTriggerSafetyCheck } from "./helpers";
import { isNetworkAvailable, isGovernanceAvailable } from "./constants"; import { isNetworkAvailable } from "./constants";
import useTheme from "./hooks/useTheme"; import useTheme from "./hooks/useTheme";
import { useUnstableProvider } from "./hooks/ghost"; import { useUnstableProvider } from "./hooks/ghost";
import { dark as darkTheme } from "./themes/dark.js"; import { dark as darkTheme } from "./themes/dark.js";
@ -34,12 +26,10 @@ const Bonds = lazy(() => import("./containers/Bond/Bonds"));
const BondModalContainer = lazy(() => import("./containers/Bond/BondModal")); const BondModalContainer = lazy(() => import("./containers/Bond/BondModal"));
const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer")); const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer"));
const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard")); const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard"));
const Faucet = lazy(() => import("./containers/Faucet/Faucet"));
const Dex = lazy(() => import("./containers/Dex/Dex")); const Dex = lazy(() => import("./containers/Dex/Dex"));
const Bridge = lazy(() => import("./containers/Bridge/Bridge")); const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
const NotFound = lazy(() => import("./containers/NotFound/NotFound")); 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"; const PREFIX = "App";
@ -106,7 +96,6 @@ function App() {
const [theme, toggleTheme] = useTheme(); const [theme, toggleTheme] = useTheme();
const config = useConfig(); const config = useConfig();
const { chains } = useSwitchChain();
const { connect, error: errorMessage } = useConnect(); const { connect, error: errorMessage } = useConnect();
const tryConnectInjected = () => connect({ connector: injected() }); const tryConnectInjected = () => connect({ connector: injected() });
@ -116,7 +105,7 @@ function App() {
const [isSidebarExpanded, setIsSidebarExpanded] = useState(false); const [isSidebarExpanded, setIsSidebarExpanded] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
const { address = "", chainId: addressChainId, status, isConnected, isReconnecting } = useAccount(); const { address = "", chainId: addressChainId, isConnected, isReconnecting } = useAccount();
const provider = usePublicClient(); const provider = usePublicClient();
const chainId = useChainId(); const chainId = useChainId();
@ -160,7 +149,7 @@ function App() {
setWrongNetworkToastId(null); setWrongNetworkToastId(null);
} }
} }
}, [chainId, addressChainId, isConnected, wrongNetworkToastId]) }, [chainId, addressChainId, isConnected])
useEffect(() => { useEffect(() => {
if (errorMessage) { if (errorMessage) {
@ -177,6 +166,7 @@ function App() {
}; };
const themeMode = theme === "light" ? lightTheme : theme === "dark" ? darkTheme : gTheme; const themeMode = theme === "light" ? lightTheme : theme === "dark" ? darkTheme : gTheme;
const chainExists = isNetworkAvailable(chainId, addressChainId); const chainExists = isNetworkAvailable(chainId, addressChainId);
useEffect(() => { useEffect(() => {
@ -193,7 +183,6 @@ function App() {
wrongNetworkToastId={wrongNetworkToastId} wrongNetworkToastId={wrongNetworkToastId}
setWrongNetworkToastId={setWrongNetworkToastId} setWrongNetworkToastId={setWrongNetworkToastId}
address={address} address={address}
status={status}
chainId={addressChainId ? addressChainId : chainId} chainId={addressChainId ? addressChainId : chainId}
chainExists={chainExists} chainExists={chainExists}
handleDrawerToggle={handleDrawerToggle} handleDrawerToggle={handleDrawerToggle}
@ -210,19 +199,17 @@ function App() {
<div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}> <div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}>
<Suspense fallback={<div></div>}> <Suspense fallback={<div></div>}>
<Routes> <Routes>
<Route path="/" element={<Navigate to={chainExists ? `/${chains.at(0).name.toLowerCase()}/dashboard` : "/empty"} />} /> <Route path="/" element={<Navigate to={chainExists ? "/dashboard" : "/empty"} />} />
{chainExists && {chainExists &&
<Route path="/:network" element={<AvailableNetworkGuard allowedNetworks={chains.map(chain => chain.name.toLowerCase())} /> }> <>
<Route path="dashboard" element={<TreasuryDashboard chainId={addressChainId ? addressChainId : chainId} />} /> <Route path="/dashboard" element={<TreasuryDashboard chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="bonds" element={<Bonds connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} /> <Route path="/bonds" element={<Bonds connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="bonds/:id" element={<BondModalContainer config={config} 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} />} /> <Route path="/stake" element={<StakeContainer 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="/faucet" element={<Faucet config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="dex/:name" element={<Dex 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} />} />
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance" element={<Governance 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/: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>
} }
<Route path="/empty" element={<NotFound <Route path="/empty" element={<NotFound
wrongNetworkToastId={wrongNetworkToastId} wrongNetworkToastId={wrongNetworkToastId}
@ -245,12 +232,4 @@ function App() {
); );
} }
const AvailableNetworkGuard = ({ allowedNetworks }) => {
const { network } = useParams();
if (!allowedNetworks.includes(network)) {
return <Navigate to="/empty" replace />;
}
return <Outlet />
}
export default App; export default App;

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

View File

@ -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"}]}

View File

@ -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

View File

@ -1,36 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 32 32">
<svg id="uuid-39ebc51d-db02-45d2-ad4b-e7ae31bb915b" data-name="Ethereum" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"> <g fill="none" fill-rule="evenodd">
<defs> <circle cx="16" cy="16" r="16" fill="#627EEA"/>
<style> <g fill="#FFF" fill-rule="nonzero">
.uuid-e080659c-91a4-4965-9905-603394020b2c { <path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/>
fill: #fff; <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"/>
.uuid-ca9afedd-40fd-4006-99f9-d2c3cf46bc21 { <path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/>
fill: #627eea; <path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/>
}
.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>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 617 B

View 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

View File

@ -9,18 +9,18 @@ const classes = {
const StyledMuiChip = styled(MuiChip, { const StyledMuiChip = styled(MuiChip, {
shouldForwardProp: prop => prop !== "template" && prop !== "strong", shouldForwardProp: prop => prop !== "template" && prop !== "strong",
})(({ theme, template, background, strong }) => { })(({ theme, template, strong }) => {
return { return {
[`&.${classes.chip}`]: { [`&.${classes.chip}`]: {
height: "21px", height: "21px",
borderRadius: "16px", borderRadius: "16px",
backgroundColor: background ? background : template backgroundColor: template
? template === "purple" ? template === "purple"
? theme.colors.special["olyZaps"] ? theme.colors.special["olyZaps"]
: template === "gray" : template === "gray"
? theme.colors.gray[500] ? theme.colors.gray[500]
: template === "darkGray" : template === "darkGray"
? theme.colors.primary[300] ? theme.colors.gray[600]
: theme.colors.feedback[template] : theme.colors.feedback[template]
: theme.palette.mode === "light" : theme.palette.mode === "light"
? theme.colors.gray[90] ? theme.colors.gray[90]
@ -34,7 +34,7 @@ const StyledMuiChip = styled(MuiChip, {
: template === "gray" : template === "gray"
? theme.colors.gray[10] ? theme.colors.gray[10]
: template === "darkGray" : template === "darkGray"
? theme.colors.gray[800] ? theme.colors.gray[90]
: theme.colors.gray[600], : theme.colors.gray[600],
fontWeight: strong ? 700 : 450, fontWeight: strong ? 700 : 450,
}, },
@ -42,8 +42,8 @@ const StyledMuiChip = styled(MuiChip, {
}; };
}); });
const Chip = ({ background, template, strong = false, ...props }) => { const Chip = ({ template, strong = false, ...props }) => {
return <StyledMuiChip className={classes.chip} background={background} template={template} strong={strong} {...props} />; return <StyledMuiChip className={classes.chip} template={template} strong={strong} {...props} />;
}; };
export default Chip; export default Chip;

View File

@ -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;

View File

@ -1,85 +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%",
maxWidth = "100%"
}) => {
const theme = useTheme();
return (
<Box
display="flex"
flexDirection="column"
width={width}
maxWidth={maxWidth}
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
MenuProps={{
PaperProps: {
sx: {
maxHeight: 160,
overflowY: 'auto',
},
},
disableScrollLock: true,
}}
>
{options.map((opt) => (
<MenuItem key={opt.value} value={opt.value}>
{opt.label}
</MenuItem>
))}
</StyledSelectInput>
</Box>
</Box>
);
};
export default Select;

View File

@ -1,4 +1,4 @@
import { useMemo } from "react"; import React from "react";
import "./Sidebar.scss"; import "./Sidebar.scss";
@ -16,7 +16,6 @@ import {
} from "@mui/material"; } from "@mui/material";
import { styled } from "@mui/material/styles"; import { styled } from "@mui/material/styles";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { useSwitchChain } from "wagmi";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import GitHubIcon from '@mui/icons-material/GitHub'; import GitHubIcon from '@mui/icons-material/GitHub';
@ -25,9 +24,7 @@ import TelegramIcon from '@mui/icons-material/Telegram';
import HowToVoteIcon from '@mui/icons-material/HowToVote'; import HowToVoteIcon from '@mui/icons-material/HowToVote';
import HubIcon from '@mui/icons-material/Hub'; import HubIcon from '@mui/icons-material/Hub';
import PublicIcon from '@mui/icons-material/Public'; import PublicIcon from '@mui/icons-material/Public';
import ForkRightIcon from '@mui/icons-material/ForkRight'; import ForumIcon from '@mui/icons-material/Forum';
import GavelIcon from '@mui/icons-material/Gavel';
import CasinoIcon from '@mui/icons-material/Casino';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import BookIcon from '@mui/icons-material/Book'; import BookIcon from '@mui/icons-material/Book';
import CurrencyExchangeIcon from '@mui/icons-material/CurrencyExchange'; import CurrencyExchangeIcon from '@mui/icons-material/CurrencyExchange';
@ -40,20 +37,18 @@ import BondIcon from "../Icon/BondIcon";
import StakeIcon from "../Icon/StakeIcon"; import StakeIcon from "../Icon/StakeIcon";
import WrapIcon from "../Icon/WrapIcon"; import WrapIcon from "../Icon/WrapIcon";
import { isNetworkAvailable, isGovernanceAvailable } from "../../constants"; import { isNetworkAvailable } from "../../constants";
import { AVAILABLE_DEXES } from "../../constants/dexes"; import { AVAILABLE_DEXES } from "../../constants/dexes";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
import { ECOSYSTEM } from "../../constants/ecosystem"; import { ECOSYSTEM } from "../../constants/ecosystem";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { sortBondsByDiscount, formatCurrency } from "../../helpers"; import { sortBondsByDiscount, formatCurrency } from "../../helpers";
import BondDiscount from "../../containers/Bond/components/BondDiscount"; import BondDiscount from "../../containers/Bond/components/BondDiscount";
import Chip from "../Chip/Chip";
import DashboardIcon from '@mui/icons-material/Dashboard'; import DashboardIcon from '@mui/icons-material/Dashboard';
import ShowerIcon from '@mui/icons-material/Shower'; import ShowerIcon from '@mui/icons-material/Shower';
import WifiProtectedSetupIcon from '@mui/icons-material/WifiProtectedSetup';
import { useTokenSymbol } from "../../hooks/tokens"; import { useTokenSymbol } from "../../hooks/tokens";
import { useFtsoPrice, useGhstPrice, useGhostedSupplyPrice } from "../../hooks/prices";
import { useLiveBonds } from "../../hooks/bonds/index"; import { useLiveBonds } from "../../hooks/bonds/index";
import pckg from "../../../package.json" import pckg from "../../../package.json"
@ -70,21 +65,14 @@ const StyledBox = styled(Box)(({ theme }) => ({
})); }));
const NavContent = ({ chainId, addressChainId }) => { const NavContent = ({ chainId, addressChainId }) => {
const { chains } = useSwitchChain();
const chainName = useMemo(() => {
return chains.find(chain => chain.id === chainId).name.toLowerCase();
}, [chains, chainId, addressChainId])
const { liveBonds: ghostBonds } = useLiveBonds(chainId); const { liveBonds: ghostBonds } = useLiveBonds(chainId);
const ftsoPrice = useFtsoPrice(chainId);
const ghstPrice = useGhstPrice(chainId);
const ghostedSupplyPrice = useGhostedSupplyPrice(chainId);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const bridgeNumbers = useMemo(() => {
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
const number = 1 + connectedNetworks * 3;
return `(${number}, ${number})`;
}, [chainId]);
return ( return (
<Paper className="dapp-sidebar"> <Paper className="dapp-sidebar">
<Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column"> <Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column">
@ -101,6 +89,17 @@ const NavContent = ({ chainId, addressChainId }) => {
Version {pckg.version} Version {pckg.version}
</Box> </Box>
</Link> </Link>
<Box display="flex" flexDirection="column" mt="10px">
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
{ftsoSymbol} Price: {formatCurrency(ftsoPrice, 2)}
</Box>
<Box fontSize="12px" fontWeight="500" lineHeight="15px">
{ghstSymbol} Price: {formatCurrency(ghstPrice, 2)}
</Box>
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
GHOSTed Supply: {formatCurrency(ghostedSupplyPrice, 2)}
</Box>
</Box>
</Box> </Box>
<Box className="menu-divider"> <Box className="menu-divider">
@ -111,18 +110,57 @@ const NavContent = ({ chainId, addressChainId }) => {
<div className="dapp-nav" id="navbarNav"> <div className="dapp-nav" id="navbarNav">
{isNetworkAvailable(chainId, addressChainId) && {isNetworkAvailable(chainId, addressChainId) &&
<> <>
<NavItem icon={DashboardIcon} label={`Dashboard`} to={`/${chainName}/dashboard`} /> <NavItem icon={DashboardIcon} label={`Dashboard`} to="/dashboard" />
<NavItem
defaultExpanded
icon={BondIcon}
label={`Bond`}
to="/bonds"
children={
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}>
{sortBondsByDiscount(ghostBonds).map((bond, index) => {
return (
<Link
component={NavLink}
to={`/bonds/${bond.id}`}
key={index}
style={{ textDecoration: "none" }}
>
<Box mb="10px" display="flex" justifyContent="end">
<Typography
style={{
width: "180px",
justifyContent: "space-between",
display: "flex",
gap: "10px"
}}
component="span"
variant="body2"
>
{bond.displayName}
<BondDiscount textOnly discount={bond.discount} />
</Typography>
</Box>
</Link>
)
})}
</AccordionDetails>
}
/>
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
<NavItem icon={ShowerIcon} label={`Faucet`} to="/faucet" />
<NavItem icon={PublicIcon} label={`Bridge`} to="/bridge" />
<NavItem <NavItem
icon={CurrencyExchangeIcon} icon={CurrencyExchangeIcon}
label={`(3, 3) Swap`} label={`Dex`}
to={`/${chainName}/dex/uniswap`} to={'/dex/uniswap'}
children={ children={
AVAILABLE_DEXES[chainId].length > 1 && <AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}> <AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}>
{AVAILABLE_DEXES[chainId].map((dex, index) => { {AVAILABLE_DEXES[chainId].map((dex, index) => {
return ( return (
<Link <Link
component={NavLink} component={NavLink}
to={`/${chainName}/dex/${dex.name.toLowerCase()}`} to={`/dex/${dex.name.toLowerCase()}`}
key={index} key={index}
style={{ textDecoration: "none" }} style={{ textDecoration: "none" }}
> >
@ -136,42 +174,30 @@ const NavContent = ({ chainId, addressChainId }) => {
</AccordionDetails> </AccordionDetails>
} }
/> />
<NavItem icon={StakeIcon} label={`(3, 3) Stake`} to={`/${chainName}/stake`} /> <Box className="menu-divider">
<Divider />
</Box>
</>
}
<NavItem <NavItem
defaultExpanded to=''
icon={BondIcon} icon={PublicIcon}
label={`(1, 1) Bond`} label={`Ecosystem`}
to={`/${chainName}/bonds`}
children={ children={
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}> <AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}>
{ghostBonds.length > 0 && <Box width="180px" mb="10px" ml="auto"> {ECOSYSTEM.map((item, index) => {
<Typography component="span" variant="body2">Bond Discounts</Typography>
</Box>}
{sortBondsByDiscount(ghostBonds).map((bond, index) => {
return ( return (
<Link <Link
component={NavLink} href={item.link}
to={`/${chainName}/bonds/${bond.id}`} target="_blank"
rel="noopener noreferrer"
key={index} key={index}
style={{ textDecoration: "none" }} style={{ textDecoration: "none" }}
> >
<Box mb="10px" display="flex" justifyContent="end"> <Box display="flex" justifyContent="end">
<Typography <Typography style={{ display: "flex", alignItems: "center" }} >
style={{ {item.name}
width: "180px",
justifyContent: "space-between",
alignItems: "center",
display: "flex",
gap: "10px"
}}
component="span"
variant="body2"
>
{bond.displayName}
{bond.soldOut
? <Chip label="Sold Out" template="darkGray" />
: <BondDiscount discount={bond.discount} />
}
</Typography> </Typography>
</Box> </Box>
</Link> </Link>
@ -180,21 +206,18 @@ const NavContent = ({ chainId, addressChainId }) => {
</AccordionDetails> </AccordionDetails>
} }
/> />
<NavItem icon={ForkRightIcon} label={`${bridgeNumbers} Stake\u00B2`} to={`/${chainName}/bridge`} />
{isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to={`/${chainName}/governance`} />}
<Box className="menu-divider">
<Divider />
</Box>
</>
}
</div> </div>
</div> </div>
</div> </div>
<Box className="menu-divider">
<Divider />
</Box>
<Box> <Box>
<NavItem href="https://ghostchain.io/game-theory-3-3" icon={CasinoIcon} label={`${bridgeNumbers} Game Theory`} /> <NavItem href="http://ghostchain.io/builders" icon={ForumIcon} label={`Forum`} />
<NavItem href="http://ecosystem.ghostchain.io" icon={PublicIcon} label={`Ecosystem`} /> <NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Docs`} />
<NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Documentation`} /> <NavItem href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts/issues" icon={ErrorOutlineIcon} label={`Git Issues`} />
<StyledBox display="flex" justifyContent="space-around" paddingY="24px"> <StyledBox display="flex" justifyContent="space-around" paddingY="24px">
<Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer"> <Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer">
<GhostStyledIcon viewBox="0 0 24 24" component={GitHubIcon} className={classes.gray} /> <GhostStyledIcon viewBox="0 0 24 24" component={GitHubIcon} className={classes.gray} />

View File

@ -7,7 +7,7 @@ import Token from "../Token/Token";
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; 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 }) => ({ ({ inputWidth, inputFontSize }) => ({
"& .MuiInputBase-input": { "& .MuiInputBase-input": {
padding: 0, padding: 0,
@ -90,7 +90,7 @@ const SwapCard = ({
</Box> </Box>
)} )}
<Box display="flex" flexDirection="row" marginTop="12px" justifyContent="space-between" alignItems="center"> <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 <StyledInputBase
id={id} id={id}
sx={{ sx={{
@ -99,7 +99,6 @@ const SwapCard = ({
padding: 0, padding: 0,
height: "24px", height: "24px",
maxWidth: inputWidth || "136px", maxWidth: inputWidth || "136px",
width: !info && !endString && !usdValue ? "100%" : inputWidth ? inputWidth : "136px",
}} }}
placeholder={placeholder} placeholder={placeholder}
type={inputType} type={inputType}

View File

@ -28,10 +28,11 @@ const StyledArrow = styled(Box)(
}, },
); );
const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick, iconNotNeeded, maxWidth}) => { const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick, iconNotNeeded }) => {
const theme = useTheme(); const theme = useTheme();
return ( return (
<Box display="flex" flexDirection="column" maxWidth={maxWidth ? maxWidth : "476px"}> <Box display="flex" flexDirection="column" maxWidth="476px">
{UpperSwapCard} {UpperSwapCard}
<Box display="flex" flexDirection="row" justifyContent="center"> <Box display="flex" flexDirection="row" justifyContent="center">
{!iconNotNeeded && (<StyledArrow {!iconNotNeeded && (<StyledArrow

View File

@ -1,12 +1,11 @@
import { SvgIcon, Box } from "@mui/material"; import { SvgIcon } from "@mui/material";
import { styled } from "@mui/material/styles"; import { styled } from "@mui/material/styles";
import FtsoIcon from "../../assets/tokens/FTSO.svg?react"; import FtsoIcon from "../../assets/tokens/FTSO.svg?react";
import StnkIcon from "../../assets/tokens/STNK.svg?react"; import StnkIcon from "../../assets/tokens/STNK.svg?react";
import GhstIcon from "../../assets/tokens/GHST.svg?react"; import GhstIcon from "../../assets/tokens/GHST.svg?react";
import DaiIcon from "../../assets/tokens/DAI.svg?react"; import DaiIcon from "../../assets/tokens/DAI.svg?react";
import EthIcon from "../../assets/tokens/ETH.svg?react"; import WethIcon from "../../assets/tokens/wETH.svg?react";
import EtcIcon from "../../assets/tokens/ETC.svg?react";
import UnknownIcon from "../../assets/tokens/Unknown.svg?react"; import UnknownIcon from "../../assets/tokens/Unknown.svg?react";
const PREFIX = "Token"; const PREFIX = "Token";
@ -24,7 +23,8 @@ const StyledSvgIcon = styled(SvgIcon)(() => ({
}, },
})); }));
export const parseKnownToken = (name) => { const Token = ({ name, viewBox = "0 0 260 260", fontSize = "large", ...props }) => {
const parseKnownToken = (name) => {
let icon; let icon;
switch (name?.toUpperCase()) { switch (name?.toUpperCase()) {
case "FTSO": case "FTSO":
@ -52,16 +52,10 @@ export const parseKnownToken = (name) => {
icon = DaiIcon; icon = DaiIcon;
break; break;
case "ETH": case "ETH":
icon = EthIcon; icon = WethIcon;
break; break;
case "WETH": case "WETH":
icon = EthIcon; icon = WethIcon;
break;
case "METC":
icon = EtcIcon;
break;
case "WMETC":
icon = EtcIcon;
break; break;
default: default:
icon = UnknownIcon; icon = UnknownIcon;
@ -69,31 +63,13 @@ export const parseKnownToken = (name) => {
return icon; return icon;
} }
const Token = ({ chainTokenName, name, viewBox = "0 0 260 260", fontSize = "large", ...props }) => {
return ( return (
<Box display="flex" justifyContent="center" alignItems="center" position="relative">
<StyledSvgIcon <StyledSvgIcon
inheritViewBox viewBox={viewBox}
fontSize={fontSize} fontSize={fontSize}
component={parseKnownToken(name)} component={parseKnownToken(name)}
{...props} {...props}
></StyledSvgIcon> ></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>
); );
}; };

View File

@ -58,7 +58,6 @@ export const TokenAllowanceGuard = ({
height = "auto", height = "auto",
spendAmount, spendAmount,
tokenName, tokenName,
isNative,
owner, owner,
spender, spender,
decimals, decimals,
@ -89,7 +88,7 @@ export const TokenAllowanceGuard = ({
); );
} }
if (!isNative && allowance && spendAmount && allowance.lt(spendAmount)) if (allowance && spendAmount && allowance.lt(spendAmount))
return ( return (
<Grid container alignItems="center"> <Grid container alignItems="center">
<Grid item xs={12} sm={isVertical ? 12 : 8}> <Grid item xs={12} sm={isVertical ? 12 : 8}>

View File

@ -39,7 +39,7 @@ const Tooltip = ({ message, children }) => {
slotProps={undefined} slotProps={undefined}
slots={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" ? ( {typeof message === "string" ? (
<Typography variant="body1" className="info-tooltip-text"> <Typography variant="body1" className="info-tooltip-text">
{message} {message}

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect } from "react";
import { Box, Typography, SvgIcon, useTheme } from "@mui/material"; import { Box, Typography, SvgIcon, useTheme } from "@mui/material";
@ -9,65 +9,31 @@ import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import { isNetworkAvailable } from "../../constants"; import { isNetworkAvailable } from "../../constants";
import { parseKnownToken } from "../../components/Token/Token"; import EthIcon from "../../assets/tokens/ETH.svg?react";
import { useSwitchChain } from 'wagmi'; import { useSwitchChain } from 'wagmi';
import { useNavigate, useLocation } from 'react-router-dom';
import toast from "react-hot-toast"; import toast from "react-hot-toast";
function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small, status }) { function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small }) {
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate();
const hasAttemptedSwitch = useRef(false);
const { chains, switchChain } = useSwitchChain(); const { chains, switchChain } = useSwitchChain();
const { pathname } = useLocation();
const [networkId, setNetworkId] = useState(chainId); const [networkId, setNetworkId] = useState(chainId);
useEffect(() => { useEffect(() => {
if (status === 'reconnecting' || status === 'connecting') return; if (chainId !== networkId) setNetworkId(chainId);
}, [chainId])
const parts = pathname.split('/'); const handleChange = (event) => {
if (!hasAttemptedSwitch.current && parts.length > 2) { const chainName = chains.find((chain) => chain.id === event.target.value).name;
let targetChain = chains.at(0);
const chainName = parts[1].toLowerCase();
const chain = chains.find(chain => chain.name.toLowerCase() === chainName);
if (chain && targetChain.name !== chain.name) {
targetChain = chain;
}
hasAttemptedSwitch.current = true;
changeNetworkId(targetChain.name, targetChain.id);
}
}, [status, chains, pathname]);
const changeNetworkId = (chainName, newNetworkId) => {
toast.dismiss(wrongNetworkToastId); toast.dismiss(wrongNetworkToastId);
const toastId = toast.loading(`Trying to connect to ${chainName} network... Wait please`, { const toastId = toast.loading(`Trying to connect to ${chainName} network... Wait please`, {
position: 'bottom-right' position: 'bottom-right'
}); });
setWrongNetworkToastId(toastId); setWrongNetworkToastId(toastId);
setNetworkId(newNetworkId); setNetworkId(event.target.value);
switchChain({ switchChain({ chainId: event.target.value });
chainId: newNetworkId
}, {
onSuccess: (data) => console.log("Network switched to", data.name),
onError: (error) => console.log("Error during network switching", error),
});
}
const handleChange = (event) => {
const chainName = chains.find((chain) => chain.id === event.target.value).name;
changeNetworkId(chainName, event.target.value);
const parts = pathname.split('/');
if (parts.length > 2) {
parts[1] = chainName.toLowerCase();
const newPath = parts.join("/");
navigate(newPath, { replace: true });
}
} }
return( return(
@ -76,7 +42,6 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
labelId="network-select-helper-label" labelId="network-select-helper-label"
id="network-select-helper" id="network-select-helper"
value={networkId} value={networkId}
disabled={!hasAttemptedSwitch.current}
onChange={handleChange} onChange={handleChange}
sx={{ sx={{
boxShadow: 'none', boxShadow: 'none',
@ -88,11 +53,11 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
} }
}} }}
> >
{chains.map((chain, i) => { {chains.map(chain => {
return ( return (
<MenuItem key={chain.name} value={chain.id}> <MenuItem key={chain.name} value={chain.id}>
<Box gap="10px" display="flex" flexDirection="row" alignItems="center"> <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>} {!small && <Typography>{chain.name}</Typography>}
</Box> </Box>
</MenuItem> </MenuItem>

View File

@ -18,7 +18,6 @@ function TopBar({
address, address,
wrongNetworkToastId, wrongNetworkToastId,
connect, connect,
status,
handleDrawerToggle, handleDrawerToggle,
setWrongNetworkToastId setWrongNetworkToastId
}) { }) {
@ -40,7 +39,6 @@ function TopBar({
setWrongNetworkToastId={setWrongNetworkToastId} setWrongNetworkToastId={setWrongNetworkToastId}
chainId={chainId} chainId={chainId}
small={small} small={small}
status={status}
/> />
<Wallet address={address} connect={connect} chainId={chainId} /> <Wallet address={address} connect={connect} chainId={chainId} />
</Box> </Box>

View File

@ -22,9 +22,9 @@ import { PrimaryButton, SecondaryButton } from "../../Button";
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react"; import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
import { formatCurrency, shorten } from "../../../helpers"; import { formatCurrency, shorten } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses"; import { DAI_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
import { useAccount, useDisconnect, useSwitchChain } from "wagmi"; import { useAccount, useDisconnect } from "wagmi";
const DisconnectButton = ({ onClose }) => { const DisconnectButton = ({ onClose }) => {
const { disconnect } = useDisconnect(); const { disconnect } = useDisconnect();
@ -109,11 +109,10 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const tokens = useWallet(chainId, address); const tokens = useWallet(chainId, address);
const { chains } = useSwitchChain();
const onBtnClick = (dexName, from, to) => { const onBtnClick = (dexName, from, to) => {
navigate({ navigate({
pathname: `${chains.find(chain => chain.id === chainId).name.toLowerCase()}/dex/` + dexName, pathname: "/dex/" + dexName,
search: createSearchParams({ search: createSearchParams({
pool: true, pool: true,
from: from, from: from,
@ -151,6 +150,18 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
<Divider /> <Divider />
</Box> </Box>
<Box
sx={{ display: "flex", flexDirection: "column" }}
style={{ gap: theme.spacing(1.5) }}
>
<SecondaryButton
fullWidth
onClick={() => onBtnClick("uniswap", DAI_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])}
>
<Typography>{`${tokens?.ftso?.symbol}-${tokens?.dai?.symbol} on Uniswap`}</Typography>
</SecondaryButton>
</Box>
<Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}> <Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}>
<DisconnectButton onClose={onClose} /> <DisconnectButton onClose={onClose} />
</Box> </Box>

View File

@ -9,29 +9,19 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { ChangeEvent, useState, useMemo, useEffect } from "react"; import { ChangeEvent, useState, useEffect } from "react";
import { useNavigate, createSearchParams } from "react-router-dom"; import { useNavigate, createSearchParams } from "react-router-dom";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { formatCurrency, formatNumber } from "../../../helpers"; import { formatCurrency, formatNumber } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"
import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { EMPTY_ADDRESS } from "../../../constants/addresses";
import GhostStyledIcon from "../../Icon/GhostIcon"; import GhostStyledIcon from "../../Icon/GhostIcon";
import TokenStack from "../../TokenStack/TokenStack"; import TokenStack from "../../TokenStack/TokenStack";
import { PrimaryButton, SecondaryButton } from "../../Button"; import { PrimaryButton, SecondaryButton } from "../../Button";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens"; import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { import { useDaiPrice, useFtsoPrice, useStnkPrice, useGhstPrice } from "../../../hooks/prices";
useNativePrice,
useReservePrice,
useFtsoPrice,
useGhstPrice
} from "../../../hooks/prices";
import { useLpValuation } from "../../../hooks/treasury"; import { useLpValuation } from "../../../hooks/treasury";
import { useUniswapV2PairReserves } from "../../../hooks/uniswapv2"; import { useAccount } from "wagmi";
import { getTokenIcons } from "../../../hooks/helpers";
import { useAccount, useBalance as useNativeBalance, useConfig, useSwitchChain, useChainId } from "wagmi";
const addTokenToWallet = async (token, userAddress) => { const addTokenToWallet = async (token, userAddress) => {
if (!window.ethereum) return; if (!window.ethereum) return;
@ -70,9 +60,7 @@ const BalanceValue = ({
export const Token = (props) => { export const Token = (props) => {
const { const {
isNative,
symbol, symbol,
faucetPath,
icons, icons,
address, address,
price = 0, price = 0,
@ -80,26 +68,19 @@ export const Token = (props) => {
onAddTokenToWallet, onAddTokenToWallet,
expanded, expanded,
onChangeExpanded, onChangeExpanded,
reserveAddress, daiAddress,
onClose, onClose,
isPool isPool
} = props; } = props;
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const chainId = useChainId();
const { chains } = useSwitchChain();
const chainName = useMemo(() => {
return chains.find(chain => chain.id === chainId).name.toLowerCase();
}, [chains, chainId])
const useLink = (symbol, fromAddress, toAddress, isPool) => { const useLink = (symbol, fromAddress, toAddress, isPool) => {
if (faucetPath) { if (symbol.toUpperCase() === "GDAI") {
navigate({ pathname: faucetPath }) navigate({ pathname: "/faucet" })
} else { } else {
navigate({ navigate({
pathname: `${chainName}/dex/uniswap`, pathname: "/dex/uniswap",
search: isPool ? search: isPool ?
createSearchParams({ createSearchParams({
pool: "true", pool: "true",
@ -117,11 +98,8 @@ export const Token = (props) => {
} }
return ( return (
<Accordion expanded={isNative ? false : expanded} onChange={onChangeExpanded}> <Accordion expanded={expanded} onChange={onChangeExpanded}>
<AccordionSummary <AccordionSummary expandIcon={<GhostStyledIcon component={ExpandMoreIcon} color="disabled" />}>
sx={{ paddingRight: isNative ? "37.43px" : "" }}
expandIcon={isNative ? null : <GhostStyledIcon component={ExpandMoreIcon} color="disabled" />}
>
<Box sx={{ display: "flex", justifyContent: "space-between", width: "100%", marginRight: "10px" }}> <Box sx={{ display: "flex", justifyContent: "space-between", width: "100%", marginRight: "10px" }}>
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}> <Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
<TokenStack <TokenStack
@ -139,7 +117,7 @@ export const Token = (props) => {
/> />
</Box> </Box>
</AccordionSummary> </AccordionSummary>
{!isNative && <AccordionDetails style={{ margin: "auto", padding: theme.spacing(1, 0) }}> <AccordionDetails style={{ margin: "auto", padding: theme.spacing(1, 0) }}>
<Box <Box
sx={{ display: "flex", flexDirection: "column", flex: 1, mx: "32px", justifyContent: "center" }} sx={{ display: "flex", flexDirection: "column", flex: 1, mx: "32px", justifyContent: "center" }}
style={{ gap: theme.spacing(1) }} style={{ gap: theme.spacing(1) }}
@ -152,14 +130,14 @@ export const Token = (props) => {
<Typography>Add to Wallet</Typography> <Typography>Add to Wallet</Typography>
</PrimaryButton> </PrimaryButton>
<SecondaryButton <SecondaryButton
onClick={() => useLink(symbol, reserveAddress, address, isPool)} onClick={() => useLink(symbol, daiAddress, address, isPool)}
fullWidth fullWidth
> >
<Typography>Get {faucetPath ? "for Free" : "on DEX"}</Typography> <Typography>Get on {symbol.toUpperCase() === "GDAI" ? "Faucet" : "Uniswap"}</Typography>
</SecondaryButton> </SecondaryButton>
</Box> </Box>
</Box> </Box>
</AccordionDetails>} </AccordionDetails>
</Accordion> </Accordion>
); );
}; };
@ -169,79 +147,52 @@ const sumObjValues = (obj: Record<string, string> = {}) =>
export const useWallet = (chainId, userAddress) => { export const useWallet = (chainId, userAddress) => {
const { const {
data: nativeBalanceRaw, balance: daiBalance,
refetch: nativeBalanceRefetch refetch: daiRefetch,
} = useNativeBalance({ address: userAddress }); contractAddress: daiAddress,
const nativeBalance = new DecimalBigNumber(nativeBalanceRaw?.value ?? 0n, 18); } = useBalance(chainId, "GDAI", userAddress);
const {
balance: reserveBalance,
refetch: reserveRefetch,
contractAddress: reserveAddress,
} = useBalance(chainId, "RESERVE", userAddress);
const { const {
balance: ftsoBalance, balance: ftsoBalance,
refetch: ftsoRefetch, refetch: ftsoRefetch,
contractAddress: ftsoAddress, contractAddress: ftsoAddress,
} = useBalance(chainId, "FTSO", userAddress); } = useBalance(chainId, "FTSO", userAddress);
const {
balance: stnkBalance,
refetch: stnkRefetch,
contractAddress: stnkAddress,
} = useBalance(chainId, "STNK", userAddress);
const { const {
balance: ghstBalance, balance: ghstBalance,
refetch: ghstRefetch, refetch: ghstRefetch,
contractAddress: ghstAddress, contractAddress: ghstAddress,
} = useBalance(chainId, "GHST", userAddress); } = useBalance(chainId, "GHST", userAddress);
const { const {
balance: lpReserveFtsoBalance, balance: lpDaiFtsoBalance,
refetch: lpReserveFtsoRefetch, refetch: lpDaiFtsoRefetch,
contractAddress: lpReserveFtsoBalanceAddress, contractAddress: lpDaiFtsoBalanceAddress,
} = useBalance(chainId, "RESERVE_FTSO", userAddress); } = useBalance(chainId, "GDAI_FTSO", userAddress);
const {
tokens: lpReserveFtsoTokens,
} = useUniswapV2PairReserves(chainId, "RESERVE_FTSO");
const nativePrice = useNativePrice(chainId); const daiPrice = useDaiPrice(chainId);
const reservePrice = useReservePrice(chainId);
const ftsoPrice = useFtsoPrice(chainId); const ftsoPrice = useFtsoPrice(chainId);
const stnkPrice = useStnkPrice(chainId);
const ghstPrice = useGhstPrice(chainId); const ghstPrice = useGhstPrice(chainId);
const lpReserveFtsoPrice = useLpValuation(chainId, "RESERVE_FTSO", 1000000000000000000n); const lpDaiFtsoPrice = useLpValuation(chainId, "GDAI_FTSO", 1000000000000000000n);
const config = useConfig(); const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { symbol: lpReserveFtsoSymbol } = useTokenSymbol(chainId, "RESERVE_FTSO"); const { symbol: lpDaiFtsoSymbol } = useTokenSymbol(chainId, "GDAI_FTSO");
const lpReserveFtsoTokenNames = useMemo(() => {
const token0 = getTokenIcons(chainId, lpReserveFtsoTokens?.token0 ?? []);
const token1 = getTokenIcons(chainId, lpReserveFtsoTokens?.token1 ?? []);
let tokenAddresses = [lpReserveFtsoTokens?.token1, lpReserveFtsoTokens?.token0];
let tokenNames = [...token1, ...token0];
if (token0?.at(0) === reserveSymbol) {
tokenAddresses = [lpReserveFtsoTokens?.token0, lpReserveFtsoTokens?.token1];
let tokenNames = [...token0, ...token1];
}
return { tokenAddresses, tokenNames }
}, [chainId, reserveSymbol, lpReserveFtsoTokens]);
const tokens = { const tokens = {
native: { dai: {
symbol: nativeSymbol, symbol: daiSymbol,
icons: [nativeSymbol], address: daiAddress,
balance: nativeBalance, balance: daiBalance,
price: nativePrice, price: daiPrice,
refetch: nativeBalanceRefetch, icons: ["GDAI"],
}, externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/gDAI.svg",
reserve: { refetch: daiRefetch,
symbol: reserveSymbol,
address: reserveAddress,
balance: reserveBalance,
price: reservePrice,
icons: [tokenNameConverter(chainId, reserveSymbol)],
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/11/6A-Classic-ETC-Token.svg",
refetch: reserveRefetch,
}, },
ftso: { ftso: {
symbol: ftsoSymbol, symbol: ftsoSymbol,
@ -252,6 +203,15 @@ export const useWallet = (chainId, userAddress) => {
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/eGHST.svg", externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/eGHST.svg",
refetch: ftsoRefetch, refetch: ftsoRefetch,
}, },
stnk: {
symbol: stnkSymbol,
address: stnkAddress,
balance: stnkBalance,
price: stnkPrice,
icons: ["STNK"],
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/sGHST.svg",
refetch: stnkRefetch,
},
ghst: { ghst: {
symbol: ghstSymbol, symbol: ghstSymbol,
address: ghstAddress, address: ghstAddress,
@ -261,15 +221,15 @@ export const useWallet = (chainId, userAddress) => {
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/GHST.svg", externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/GHST.svg",
refetch: ghstRefetch, refetch: ghstRefetch,
}, },
reserveFtso: { daiFtso: {
isPool: true, isPool: true,
symbol: lpReserveFtsoSymbol, symbol: lpDaiFtsoSymbol,
address: lpReserveFtsoTokenNames?.tokenAddresses.at(1) ?? "", address: lpDaiFtsoBalanceAddress,
balance: lpReserveFtsoBalance, balance: lpDaiFtsoBalance,
price: lpReserveFtsoPrice, price: lpDaiFtsoPrice,
icons: lpReserveFtsoTokenNames?.tokenNames, icons: ["GDAI", "FTSO"],
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/uni-v2.svg", externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/uni-v2.svg",
refetch: lpReserveFtsoRefetch, refetch: lpDaiFtsoRefetch,
} }
}; };
@ -286,12 +246,12 @@ export const useWallet = (chainId, userAddress) => {
export const Tokens = ({ address, tokens, onClose }) => { export const Tokens = ({ address, tokens, onClose }) => {
const [expanded, setExpanded] = useState(null); const [expanded, setExpanded] = useState(null);
const alwaysShowTokens = [tokens.native, tokens.reserve, tokens.ftso, tokens.ghst, tokens.reserveFtso]; const alwaysShowTokens = [tokens.dai, tokens.ftso, tokens.stnk, tokens.ghst, tokens.daiFtso];
const tokenProps = (token) => ({ const tokenProps = (token) => ({
...token, ...token,
expanded: expanded === token.symbol, expanded: expanded === token.symbol,
reserveAddress: EMPTY_ADDRESS, daiAddress: tokens.dai.address,
onChangeExpanded: (e, isExpanded) => setExpanded(isExpanded ? token.symbol : null), onChangeExpanded: (e, isExpanded) => setExpanded(isExpanded ? token.symbol : null),
onAddTokenToWallet: () => addTokenToWallet(token, address), onAddTokenToWallet: () => addTokenToWallet(token, address),
onClose: () => onClose(), onClose: () => onClose(),
@ -300,7 +260,7 @@ export const Tokens = ({ address, tokens, onClose }) => {
return ( return (
<> <>
{alwaysShowTokens.map((token, i) => ( {alwaysShowTokens.map((token, i) => (
<Token key={i} isNative={i === 0} {...tokenProps(token)} /> <Token key={i} {...tokenProps(token)} />
))} ))}
</> </>
); );

View File

@ -76,12 +76,7 @@ export function Wallet({ address, chainId, connect }) {
onOpen={openWallet} onOpen={openWallet}
onClose={closeWallet} onClose={closeWallet}
> >
<InitialWalletView <InitialWalletView isWalletOpen={isWalletOpen} address={address} chainId={chainId} onClose={closeWallet} />
isWalletOpen={isWalletOpen}
address={address}
chainId={chainId}
onClose={closeWallet}
/>
</StyledSwipeableDrawer> </StyledSwipeableDrawer>
</> </>
); );

View File

@ -1,28 +1,8 @@
import { defineChain } from 'viem'
import { http, fallback, createConfig } from 'wagmi' import { http, fallback, createConfig } from 'wagmi'
import { sepolia, hoodi } from 'wagmi/chains' 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({ export const config = createConfig({
chains: [sepolia, hoodi, mordor], chains: [sepolia, hoodi],
transports: { transports: {
[sepolia.id]: fallback([ [sepolia.id]: fallback([
http('https://ethereum-sepolia-rpc.publicnode.com'), http('https://ethereum-sepolia-rpc.publicnode.com'),
@ -36,10 +16,6 @@ export const config = createConfig({
[hoodi.id]: fallback([ [hoodi.id]: fallback([
http('https://rpc.hoodi.ethpandaops.io'), http('https://rpc.hoodi.ethpandaops.io'),
http('https://0xrpc.io/hoodi'), http('https://0xrpc.io/hoodi'),
]),
[mordor.id]: fallback([
http('https://rpc.mordor.etccooperative.org'),
http('https://geth-mordor.etc-network.info'),
]) ])
}, },
}) })

View File

@ -1,23 +1,6 @@
export enum NetworkId { export enum NetworkId {
TESTNET_SEPOLIA = 11155111, TESTNET_SEPOLIA = 11155111,
TESTNET_HOODI = 560048, 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;
case 63:
exists = true
break;
default:
break;
}
return exists;
} }
export const isNetworkAvailable = (chainId, addressChainId) => { export const isNetworkAvailable = (chainId, addressChainId) => {
@ -30,56 +13,8 @@ export const isNetworkAvailable = (chainId, addressChainId) => {
case 560048: case 560048:
exists = true exists = true
break; break;
case 63:
exists = true
break;
default: default:
break; break;
} }
return exists; return exists;
} }
export const isNetworkLegacy = (chainId) => {
let isLegacy = false;
switch (chainId) {
case 11155111:
isLegacy = true
break;
case 63:
isLegacy = true
break;
default:
break;
}
return isLegacy;
}
export const isNetworkLegacyType = (chainId) => {
let isLegacyType = false;
switch (chainId) {
case 63:
isLegacyType = true;
break;
default:
break;
}
return isLegacyType
}
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
}

View File

@ -1,129 +1,80 @@
import { NetworkId } from "../constants"; import { NetworkId } from "../constants";
export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
export const STAKING_ADDRESSES = { export const STAKING_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506", [NetworkId.TESTNET_SEPOLIA]: "0xd90E63E88282596E1ea33765b41Ba3d650f4aD52",
[NetworkId.TESTNET_HOODI]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506", [NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86",
[NetworkId.TESTNET_MORDOR]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
}; };
export const BOND_DEPOSITORY_ADDRESSES = { export const BOND_DEPOSITORY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0", [NetworkId.TESTNET_SEPOLIA]: "0xdcE486113280e49ca2fB200258E5Ee1B2D21D495",
[NetworkId.TESTNET_HOODI]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0", [NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571",
[NetworkId.TESTNET_MORDOR]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
}; };
export const DAO_TREASURY_ADDRESSES = { export const DAO_TREASURY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E", [NetworkId.TESTNET_SEPOLIA]: "0x93dd30f819403710de7933B79A74C4A42438458D",
[NetworkId.TESTNET_HOODI]: "0x05D797f9F34844594C956da58f1785997397f02E", [NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8",
[NetworkId.TESTNET_MORDOR]: "0x05D797f9F34844594C956da58f1785997397f02E",
}; };
export const FTSO_DAI_LP_ADDRESSES = { export const FTSO_DAI_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D", [NetworkId.TESTNET_SEPOLIA]: "0x1394dC3f7bABaa2F0CA80353648087DAB1BF3fd6",
[NetworkId.TESTNET_HOODI]: "0x32388605b5E83Ea79CDdC479AA9939DBCF98f59D", [NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50",
[NetworkId.TESTNET_MORDOR]: "0x53B13C4722081c405ce25c7A7629fC326A49a469",
}; };
export const FTSO_STNK_LP_ADDRESSES = { export const FTSO_STNK_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", // TBD
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", [NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
[NetworkId.TESTNET_MORDOR]: "0x0000000000000000000000000000000000000000",
} }
export const RESERVE_ADDRESSES = { export const DAI_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14", [NetworkId.TESTNET_SEPOLIA]: "0x5f63a27a9214a0352F2EF8dAF1eD4974d713192B",
[NetworkId.TESTNET_HOODI]: "0xE69a5c6dd88cA798b93c3C92fc50c51Fd5305eB4", [NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58",
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
}; };
export const WETH_ADDRESSES = { export const WETH_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14", [NetworkId.TESTNET_SEPOLIA]: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
[NetworkId.TESTNET_HOODI]: "0xE69a5c6dd88cA798b93c3C92fc50c51Fd5305eB4", [NetworkId.TESTNET_HOODI]: "0xE69a5c6dd88cA798b93c3C92fc50c51Fd5305eB4",
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
}; };
export const GHST_ADDRESSES = { export const GHST_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392", [NetworkId.TESTNET_SEPOLIA]: "0xdf2e5306A3dCcfA4e21bbF4226C17Ff5B008dDC4",
[NetworkId.TESTNET_HOODI]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392", [NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46",
[NetworkId.TESTNET_MORDOR]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
}; };
export const STNK_ADDRESSES = { export const STNK_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7", [NetworkId.TESTNET_SEPOLIA]: "0x02C296A27eA779d5a16F934337c12062C5E3c0D9",
[NetworkId.TESTNET_HOODI]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7", [NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F",
[NetworkId.TESTNET_MORDOR]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
}; };
export const FTSO_ADDRESSES = { export const FTSO_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A", [NetworkId.TESTNET_SEPOLIA]: "0xcFedFFEB3FdeCd2196820Ba3b71f3F84A1255f93",
[NetworkId.TESTNET_HOODI]: "0x7ebd1224D36d64eA09312073e60f352d1383801A", [NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033",
[NetworkId.TESTNET_MORDOR]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
}; };
export const DISTRIBUTOR_ADDRESSES = { export const DISTRIBUTOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544", [NetworkId.TESTNET_SEPOLIA]: "0x8fbF8eB4Fcd451EF62Aee33508D46FE120963194",
[NetworkId.TESTNET_HOODI]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544", [NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842",
[NetworkId.TESTNET_MORDOR]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
}; };
export const GHOST_GOVERNANCE_ADDRESSES = { export const GHOST_GOVERNANCE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1", [NetworkId.TESTNET_SEPOLIA]: "0xDab0c51918E6990d8763FAC8a04AE159e44e0c4f",
[NetworkId.TESTNET_HOODI]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010", [NetworkId.TESTNET_HOODI]: "0x1B96B792840d4d19d5097ee007392Ed4d851e64F",
[NetworkId.TESTNET_MORDOR]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
}; };
export const BONDING_CALCULATOR_ADDRESSES = { export const BONDING_CALCULATOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3", [NetworkId.TESTNET_SEPOLIA]: "0x4896bFc6256A57Df826d7144E48c9633d51d6319",
[NetworkId.TESTNET_HOODI]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3", [NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd",
[NetworkId.TESTNET_MORDOR]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
} }
export const GATEKEEPER_ADDRESSES = { export const GATEKEEPER_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xd735cA07984a16911222c08411A80e24EB38869B", [NetworkId.TESTNET_SEPOLIA]: "0xc85129A097773B7F8970a7364c928C05f265E6A1",
[NetworkId.TESTNET_HOODI]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
[NetworkId.TESTNET_MORDOR]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
} }
export const UNISWAP_V2_ROUTER = { export const UNISWAP_V2_ROUTER = {
[NetworkId.TESTNET_SEPOLIA]: "0xee567fe1712faf6149d80da1e6934e354124cfe3", [NetworkId.TESTNET_SEPOLIA]: "0xee567fe1712faf6149d80da1e6934e354124cfe3",
[NetworkId.TESTNET_HOODI]: "0xD41daF947c6FFEf344754B99ad09466FBCBb7583", [NetworkId.TESTNET_HOODI]: "0xD41daF947c6FFEf344754B99ad09466FBCBb7583",
[NetworkId.TESTNET_MORDOR]: "0x90ecf6a29798E3cf31EB7DCE64a372AC40d83F83",
}; };
export const UNISWAP_V2_FACTORY = { export const UNISWAP_V2_FACTORY = {
[NetworkId.TESTNET_SEPOLIA]: "0xF62c03E08ada871A0bEb309762E260a7a6a880E6", [NetworkId.TESTNET_SEPOLIA]: "0xF62c03E08ada871A0bEb309762E260a7a6a880E6",
[NetworkId.TESTNET_HOODI]: "0xF140342cB5C29C1468d91Aee408d7b7271C48b5A", [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_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",
],
}

View File

@ -18,11 +18,4 @@ export const AVAILABLE_DEXES = {
viewBox: "0 0 195 230", viewBox: "0 0 195 230",
}, },
], ],
[NetworkId.TESTNET_MORDOR]: [
{
name: "Uniswap",
icon: UniswapIcon,
viewBox: "0 0 195 230",
},
],
}; };

View File

@ -1,9 +1,8 @@
import { ArrowBack } from "@mui/icons-material"; import { ArrowBack } from "@mui/icons-material";
import { Box, Link, Skeleton, Typography } from "@mui/material"; import { Box, Link, Skeleton, Typography } from "@mui/material";
import { useEffect, useState, useMemo, useCallback } from "react"; import { useEffect, useState } from "react";
import { Link as RouterLink, useLocation, useParams, useNavigate } from "react-router-dom"; import { Link as RouterLink, useLocation, useNavigate, useParams } from "react-router-dom";
import { useAccount, useSwitchChain } from "wagmi"; import { useAccount, useChainId } from "wagmi";
import { isAddress } from "viem";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import PageTitle from "../../components/PageTitle/PageTitle"; import PageTitle from "../../components/PageTitle/PageTitle";
@ -18,92 +17,64 @@ import BondSettingsModal from "./components/BondSettingsModal";
import NotFound from "../NotFound/NotFound"; import NotFound from "../NotFound/NotFound";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { isNetworkLegacy } from "../../constants"; import { NetworkId } from "../../constants";
import { formatCurrency } from "../../helpers"; import { formatCurrency } from "../../helpers";
import { useLocalStorage } from "../../hooks/localstorage";
import { useLiveBonds } from "../../hooks/bonds"; import { useLiveBonds } from "../../hooks/bonds";
import { useFtsoPrice } from "../../hooks/prices"; import { useFtsoPrice } from "../../hooks/prices";
const BondModalContainer = ({ chainId, address, config, connect }) => { const BondModalContainer = ({ chainId, address, connect }) => {
const { id, network } = useParams(); const { id } = useParams();
const { liveBonds } = useLiveBonds(chainId, network); const { liveBonds } = useLiveBonds(chainId);
const bond = liveBonds.find(bond => bond.id === Number(id)); const bond = liveBonds.find(bond => bond.id === Number(id));
if (!bond) return <NotFound />; if (!bond) return <NotFound />;
return <BondModal config={config} chainId={chainId} bond={bond} address={address} connect={connect} />; return <BondModal chainId={chainId} bond={bond} address={address} connect={connect} />;
}; };
export const BondModal = ({ bond, chainId, address, config, connect }) => { export const BondModal = ({ bond, chainId, address, connect }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { network } = useParams();
const { pathname } = useLocation(); const { pathname } = useLocation();
const [slippage, setSlippage] = useState(localStorage.getItem("bond-slippage") || "10");
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("bond-decimals") || "5");
const [isSettingsOpen, setSettingsOpen] = useState(false); const [isSettingsOpen, setSettingsOpen] = useState(false);
const { getStorageValue, setStorageValue } = useLocalStorage(); const [recipientAddress, setRecipientAddress] = useState(address);
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "bond-slippage", "10"));
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "bond-decimals", "5"));
const [recipientAddress, setRecipientAddressInner] = useState(() => getStorageValue(chainId, address, "bond-recipient", address));
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: pathname }); ReactGA.send({ hitType: "pageview", page: pathname });
}, []) }, [])
const setRecipientAddress = useCallback((value) => { const setSlippageInner = (value) => {
setRecipientAddressInner(value);
if (isAddress(value)) {
setStorageValue(chainId, address, "bond-recipient", value);
}
}, [chainId, address]);
const setSlippageInner = useCallback((value) => {
const maybeValue = parseFloat(value); const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) { if (!maybeValue || parseFloat(value) <= 100) {
setSlippage(value); setSlippage(value);
setStorageValue(chainId, address, "bond-slippage", value); localStorage.setItem("bond-slippage", value);
}
} }
}, [chainId, address]);
const setFormatDecimalsInner = useCallback((value) => { const setFormatDecimalsInner = (value) => {
if (Number(value) <= 17) { if (Number(value) <= 17) {
setFormatDecimals(value); setFormatDecimals(value);
setStorageValue(chainId, address, "bond-decimals", value); localStorage.setItem("bond-decimals", value);
}
} }
}, [chainId, address]);
useEffect(() => { useEffect(() => {
const handleKeyDown = (event) => { const handleKeyDown = (event) => {
if (event.key === "Escape") isSettingsOpen ? setSettingsOpen(false) : navigate(`/${network}/bonds`); if (event.key === "Escape") isSettingsOpen ? setSettingsOpen(false) : navigate("/bonds");
}; };
window.addEventListener("keydown", handleKeyDown); window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown);
}, [network, navigate, isSettingsOpen]); }, [navigate, isSettingsOpen]);
const chainSymbol = useMemo(() => {
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
if (chainSymbol) return chainSymbol;
return "WTF";
}, [config]);
const preparedQuoteToken = useMemo(() => {
if (isNetworkLegacy(chainId)) {
return {
address: bond.quoteToken.quoteTokenAddress,
icons: bond.quoteToken.icons,
name: bond.quoteToken.name,
}
}
return { address: undefined, icons: [chainSymbol], name: chainSymbol };
}, [bond, chainSymbol, chainId])
return ( return (
<Box> <Box>
<PageTitle <PageTitle
name={ name={
<Box display="flex" flexDirection="row" alignItems="center"> <Box display="flex" flexDirection="row" alignItems="center">
<Link component={RouterLink} to={`/${network}/bonds`}> <Link component={RouterLink} to="/bonds">
<Box display="flex" flexDirection="row"> <Box display="flex" flexDirection="row">
<ArrowBack /> <ArrowBack />
<Typography fontWeight="500" marginLeft="9.5px" marginRight="18px"> <Typography fontWeight="500" marginLeft="9.5px" marginRight="18px">
@ -112,10 +83,10 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
</Box> </Box>
</Link> </Link>
<TokenStack tokens={preparedQuoteToken.icons} sx={{ fontSize: "27px" }} /> <TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "27px" }} />
<Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center"> <Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center">
<Typography variant="h4" fontWeight={500}> <Typography variant="h4" fontWeight={500}>
{preparedQuoteToken.name} {bond.quoteToken.name}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@ -161,12 +132,10 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
<BondInputArea <BondInputArea
chainId={chainId} chainId={chainId}
bond={bond} bond={bond}
config={config}
connect={connect} connect={connect}
address={address} address={address}
slippage={slippage} slippage={slippage}
recipientAddress={recipientAddress} recipientAddress={recipientAddress}
preparedQuoteToken={preparedQuoteToken}
handleSettingsOpen={() => setSettingsOpen(true)} handleSettingsOpen={() => setSettingsOpen(true)}
formatDecimals={formatDecimals} formatDecimals={formatDecimals}
/> />

View File

@ -1,6 +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 { useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import Paper from "../../components/Paper/Paper"; import Paper from "../../components/Paper/Paper";
@ -20,8 +20,8 @@ import { useFtsoPrice } from "../../hooks/prices";
import { useTokenSymbol } from "../../hooks/tokens"; import { useTokenSymbol } from "../../hooks/tokens";
const Bonds = ({ chainId, address, connect }) => { const Bonds = ({ chainId, address, connect }) => {
const { network } = useParams();
const [isZoomed] = useState(false); const [isZoomed] = useState(false);
const navigate = useNavigate();
const [secondsTo, setSecondsTo] = useState(0); const [secondsTo, setSecondsTo] = useState(0);
const isSmallScreen = useMediaQuery("(max-width: 650px)"); const isSmallScreen = useMediaQuery("(max-width: 650px)");
@ -29,9 +29,9 @@ const Bonds = ({ chainId, address, connect }) => {
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/bonds" }); ReactGA.send({ hitType: "pageview", page: "/bonds" });
}, []); }, [])
const { liveBonds } = useLiveBonds(chainId, network); const { liveBonds } = useLiveBonds(chainId);
const totalReserves = useTotalReserves(chainId); const totalReserves = useTotalReserves(chainId);
const ftsoPrice = useFtsoPrice(chainId); const ftsoPrice = useFtsoPrice(chainId);
@ -59,17 +59,7 @@ const Bonds = ({ chainId, address, connect }) => {
}} }}
> >
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column"> <Box display="flex" alignItems="center" justifyContent="center" flexDirection="column">
<Paper <Paper headerText="Active bonds" fullWidth enableBackground>
fullWidth
enableBackground
headerContent={
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
<Typography variant="h6">
Active bonds
</Typography>
</Box>
}
>
<MetricCollection> <MetricCollection>
<Metric <Metric
label={`Treasury Balance`} label={`Treasury Balance`}

View File

@ -37,9 +37,6 @@ const BondConfirmModal = ({
sender, sender,
handleSettingsOpen, handleSettingsOpen,
isOpen, isOpen,
isNative,
bondQuoteTokenName,
bondQuoteTokenIcons,
handleConfirmClose handleConfirmClose
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
@ -56,16 +53,15 @@ const BondConfirmModal = ({
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one; const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS; const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS;
await purchaseBond({ await purchaseBond(
chainId, chainId,
bondId: bond.id, bond.id,
amount: spendAmountValue._value.toBigInt(), spendAmountValue._value.toBigInt(),
maxPrice, maxPrice,
user: recipientAddress, recipientAddress,
sender, sender,
referral, referral
isNative );
});
setIsPending(false); setIsPending(false);
handleConfirmClose(); handleConfirmClose();
@ -78,9 +74,9 @@ const BondConfirmModal = ({
open={isOpen} open={isOpen}
headerContent={ headerContent={
<Box display="flex" flexDirection="row"> <Box display="flex" flexDirection="row">
<TokenStack tokens={bondQuoteTokenIcons} sx={{ fontSize: "27px" }} /> <TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "27px" }} />
<Typography variant="h4" sx={{ marginLeft: "6px" }}> <Typography variant="h4" sx={{ marginLeft: "6px" }}>
{bondQuoteTokenName} {bond.quoteToken.name}
</Typography> </Typography>
</Box> </Box>
} }
@ -95,7 +91,7 @@ const BondConfirmModal = ({
metric={spendAmount} metric={spendAmount}
/> />
<Box display="flex" flexDirection="row" justifyContent="center"> <Box display="flex" flexDirection="row" justifyContent="center">
<Typography>{bondQuoteTokenName}</Typography> <Typography>{bond.quoteToken.name}</Typography>
</Box> </Box>
</Box> </Box>
<GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} /> <GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} />

View File

@ -1,5 +1,5 @@
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material"; import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
import { Box, Checkbox, FormControlLabel, useMediaQuery } from "@mui/material"; import { Box, Checkbox, FormControlLabel } from "@mui/material";
import { useState, useMemo } from "react"; import { useState, useMemo } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
@ -30,15 +30,12 @@ const BondInputArea = ({
formatDecimals, formatDecimals,
handleSettingsOpen, handleSettingsOpen,
address, address,
preparedQuoteToken,
connect connect
}) => { }) => {
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const { pathname } = useLocation(); const { pathname } = useLocation();
const { currentIndex } = useCurrentIndex(chainId); const { currentIndex } = useCurrentIndex(chainId);
const { balance, refetch } = useBalance(chainId, preparedQuoteToken.address, address); const { balance, refetch } = useBalance(chainId, bond.quoteToken.quoteTokenAddress, address);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
@ -98,21 +95,22 @@ const BondInputArea = ({
UpperSwapCard={ UpperSwapCard={
<SwapCard <SwapCard
maxWidth="476px" maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"} inputWidth="280px"
id="from" id="from"
token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />} token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />}
tokenName={preparedQuoteToken.name} tokenName={bond.quoteToken.name}
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)} info={formatCurrency(balance, formatDecimals, bond.quoteToken.name)}
endString={preparedQuoteToken.address && "Max"} endString="Max"
endStringOnClick={setMax} endStringOnClick={setMax}
value={amount} value={amount}
onChange={event => setAmount(event.currentTarget.value)} onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }} inputProps={{ "data-testid": "fromInput" }}
/>} />
}
LowerSwapCard={ LowerSwapCard={
<SwapCard <SwapCard
maxWidth="476px" maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"} inputWidth="280px"
id="to" id="to"
token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />} token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />}
tokenName={bond.baseToken.name} tokenName={bond.baseToken.name}
@ -134,7 +132,6 @@ const BondInputArea = ({
connect={connect} connect={connect}
width="100%" width="100%"
height="60px" height="60px"
isNative={preparedQuoteToken.address === undefined}
isVertical isVertical
message={ message={
<> <>
@ -180,12 +177,12 @@ const BondInputArea = ({
)} )}
</span> </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 <DataRow
title="Max You Can Buy" 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={ balance={
<span> <span>
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase() {bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
@ -198,13 +195,13 @@ const BondInputArea = ({
<DataRow <DataRow
title="Discount" title="Discount"
balance={<BondDiscount discount={bond.discount} textOnly />} 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 <DataRow
title={`Vesting Term`} title={`Vesting Term`}
balance={<BondVesting vesting={bond.vesting} />} 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 && ( {recipientAddress !== address && (
@ -222,9 +219,6 @@ const BondInputArea = ({
spendAmountValue={parsedAmount} spendAmountValue={parsedAmount}
spendAmount={formatNumber(parsedAmount, formatDecimals)} spendAmount={formatNumber(parsedAmount, formatDecimals)}
receiveAmount={formatNumber(amountInBaseToken, formatDecimals)} receiveAmount={formatNumber(amountInBaseToken, formatDecimals)}
bondQuoteTokenName={preparedQuoteToken.name}
bondQuoteTokenIcons={preparedQuoteToken.icons}
isNative={preparedQuoteToken.address === undefined}
handleSettingsOpen={handleSettingsOpen} handleSettingsOpen={handleSettingsOpen}
isOpen={confirmOpen} isOpen={confirmOpen}
sender={address} sender={address}

View File

@ -11,7 +11,7 @@ import {
Typography, Typography,
useMediaQuery useMediaQuery
} from "@mui/material"; } from "@mui/material";
import { NavLink, useParams } from "react-router-dom"; import { NavLink } from "react-router-dom";
import ArrowUp from "../../../assets/icons/arrow-up.svg?react"; import ArrowUp from "../../../assets/icons/arrow-up.svg?react";
import BondDiscount from "./BondDiscount"; import BondDiscount from "./BondDiscount";
@ -26,7 +26,6 @@ import { TertiaryButton } from "../../../components/Button";
export const BondList = ({ bonds, secondsTo, chainId }) => { export const BondList = ({ bonds, secondsTo, chainId }) => {
const isSmallScreen = useScreenSize("md"); const isSmallScreen = useScreenSize("md");
const { network } = useParams();
if (bonds.length === 0) { if (bonds.length === 0) {
return ( return (
@ -44,7 +43,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
</Box> </Box>
{sortBondsByDiscount(bonds).map(bond => ( {sortBondsByDiscount(bonds).map(bond => (
<BondCard key={bond.id} secondsTo={secondsTo} bond={bond} chainName={network} /> <BondCard key={bond.id} secondsTo={secondsTo} bond={bond} />
))} ))}
</> </>
); );
@ -54,7 +53,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
<> <>
<BondTable> <BondTable>
{sortBondsByDiscount(bonds).map(bond => ( {sortBondsByDiscount(bonds).map(bond => (
<BondRow key={bond.id} bond={bond} secondsTo={secondsTo} chainName={network} /> <BondRow key={bond.id} bond={bond} secondsTo={secondsTo} />
))} ))}
</BondTable> </BondTable>
@ -65,7 +64,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
); );
}; };
const BondCard = ({ bond, secondsTo, chainName }) => { const BondCard = ({ bond, secondsTo }) => {
const quoteTokenName = bond.quoteToken.name; const quoteTokenName = bond.quoteToken.name;
const baseTokenName = bond.baseToken.name; const baseTokenName = bond.baseToken.name;
@ -131,11 +130,10 @@ const BondCard = ({ bond, secondsTo, chainName }) => {
<Box mt="16px"> <Box mt="16px">
<Link <Link
component={NavLink} component={NavLink}
to={`/${chainName}/bonds/${bond.id}`} to={`/bonds/${bond.id}`}
sx={{ pointerEvents: bond.isSoldOut ? 'none' : 'auto' }}
> >
<TertiaryButton fullWidth disabled={bond.isSoldOut}> <TertiaryButton fullWidth>
{bond.isSoldOut ? "Sold Out" : `Bond`} Bond
</TertiaryButton> </TertiaryButton>
</Link> </Link>
</Box> </Box>
@ -175,7 +173,7 @@ const payoutTokenCapacity = (bond) => {
return `${formatNumber(payoutTokenCapacity, 4)} ${bond.baseToken.name}`; return `${formatNumber(payoutTokenCapacity, 4)} ${bond.baseToken.name}`;
}; };
const BondRow = ({ bond, secondsTo, chainName }) => { const BondRow = ({ bond, secondsTo }) => {
const quoteTokenName = bond.quoteToken.name; const quoteTokenName = bond.quoteToken.name;
const baseTokenName = bond.baseToken.name; const baseTokenName = bond.baseToken.name;
@ -223,8 +221,7 @@ const BondRow = ({ bond, secondsTo, chainName }) => {
<TableCell style={{ padding: "8px 0" }}> <TableCell style={{ padding: "8px 0" }}>
<Link <Link
component={NavLink} component={NavLink}
to={`/${chainName}/bonds/${bond.id}`} to={`/bonds/${bond.id}`}
sx={{ pointerEvents: bond.isSoldOut ? 'none' : 'auto' }}
> >
<TertiaryButton fullWidth disabled={bond.isSoldOut}> <TertiaryButton fullWidth disabled={bond.isSoldOut}>
{bond.isSoldOut ? "Sold Out" : `Bond`} {bond.isSoldOut ? "Sold Out" : `Bond`}

View File

@ -20,16 +20,17 @@ import { formatCurrency } from "../../../helpers";
import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking"; import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking";
import { useNotes, redeem } from "../../../hooks/bonds"; import { useNotes, redeem } from "../../../hooks/bonds";
import { useTokenSymbol } from "../../../hooks/tokens"; import { useTokenSymbol } from "../../../hooks/tokens";
import { useGhstPrice } from "../../../hooks/prices";
export const ClaimBonds = ({ chainId, address, secondsTo }) => { export const ClaimBonds = ({ chainId, address, secondsTo }) => {
const isSmallScreen = useScreenSize("md"); const isSmallScreen = useScreenSize("md");
const [isPending, setIsPending] = useState(false); const [isPending, setIsPending] = useState(false);
const [isWarmup, setIsWapmup] = useState(false); const [isWarmup, setIsWapmup] = useState(false);
const [isPreClaimConfirmed, setPreClaimConfirmed] = useState(false); const [isPreClaimConfirmed, setPreClaimConfirmed] = useState(false);
const [isPayoutGhst, _] = useState(true); const [isPayoutGhst, setIsPayoutGhst] = useState(localStorage.getItem("bond-isGhstPayout")
? localStorage.getItem("bond-isGhstPayout") === "true"
: true
);
const ghstPrice = useGhstPrice(chainId);
const { epoch } = useEpoch(chainId); const { epoch } = useEpoch(chainId);
const { warmupExists } = useWarmupLength(chainId); const { warmupExists } = useWarmupLength(chainId);
const { warmupInfo } = useWarmupInfo(chainId, BOND_DEPOSITORY_ADDRESSES[chainId]); const { warmupInfo } = useWarmupInfo(chainId, BOND_DEPOSITORY_ADDRESSES[chainId]);
@ -38,6 +39,7 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
const { notes, refetch: notesRefetch } = useNotes(chainId, address); const { notes, refetch: notesRefetch } = useNotes(chainId, address);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
if (!notes || notes.length === 0) return null; if (!notes || notes.length === 0) return null;
@ -50,18 +52,23 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
18 18
); );
const setIsPayoutGhstInner = (value) => {
setIsPayoutGhst(value);
localStorage.setItem("bond-isGhstPayout", value);
}
const onSubmit = async (indexes) => { const onSubmit = async (indexes) => {
const isFundsInWarmup = warmupInfo.deposit._value > 0n; const isFundsInWarmup = warmupInfo.deposit._value > 0n;
if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) { if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) {
setIsWapmup(true); setIsWapmup(true);
} else { } else {
setIsPending(true); setIsPending(true);
await redeem({ await redeem(
chainId, chainId,
user: address, address,
isGhst: isPayoutGhst, isPayoutGhst,
indexes indexes
}); );
await notesRefetch(); await notesRefetch();
setIsPending(false); setIsPending(false);
} }
@ -76,17 +83,25 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
warmupLength={warmupInfo.expiry - epoch.number} warmupLength={warmupInfo.expiry - epoch.number}
setPreClaimConfirmed={() => setPreClaimConfirmed(true)} setPreClaimConfirmed={() => setPreClaimConfirmed(true)}
/> />
<Paper <Paper headerText="Your Bonds" fullWidth enableBackground>
fullWidth <Box display="flex" flexDirection="column" alignItems="center">
enableBackground <Typography variant="h4" align="center" color="textSecondary">
headerContent={ Payout Options
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
<Typography variant="h6">
Your Bonds
</Typography> </Typography>
</Box> <Tabs
} centered
textColor="primary"
indicatorColor="primary"
value={isPayoutGhst ? 1 : 0}
aria-label="Payout token tabs"
onChange={(_, view) => setIsPayoutGhstInner(view === 1)}
TabIndicatorProps={{ style: { display: "none" } }}
> >
<Tab aria-label="payout-stnk-button" label={stnkSymbol} style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-ghst-button" label={ghstSymbol} style={{ fontSize: "1rem" }} />
</Tabs>
</Box>
<Box display="flex" justifyContent="center"> <Box display="flex" justifyContent="center">
<Box display="flex" flexDirection="column" alignItems="center" mt="24px" width={isSmallScreen ? "100%" : "50%"}> <Box display="flex" flexDirection="column" alignItems="center" mt="24px" width={isSmallScreen ? "100%" : "50%"}>
<Typography variant="h5" align="center" color="textSecondary" style={{ fontSize: "1.2rem" }}> <Typography variant="h5" align="center" color="textSecondary" style={{ fontSize: "1.2rem" }}>
@ -100,9 +115,6 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol) : formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol)
} }
</Typography> </Typography>
<Typography variant="subtitle1" align="center">
{formatCurrency(totalClaimableBalance * ghstPrice, 2)}
</Typography>
</Box> </Box>
<PrimaryButton <PrimaryButton
@ -144,15 +156,12 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
<Box display="flex" justifyContent="space-between" mt="8px"> <Box display="flex" justifyContent="space-between" mt="8px">
<Typography>Payout</Typography> <Typography>Payout</Typography>
<Box display="flex" flexDirection="column" alignItems="flex-end">
<Typography> <Typography>
{isPayoutGhst {isPayoutGhst
? formatCurrency(note.payout, 5, ghstSymbol) ? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol) : formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
} }
</Typography> </Typography>
<Typography>{formatCurrency(note.payout * ghstPrice, 2)}</Typography>
</Box>
</Box> </Box>
<Box mt="16px"> <Box mt="16px">
@ -203,15 +212,12 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
</TableCell> </TableCell>
<TableCell style={{ padding: "8px 0" }}> <TableCell style={{ padding: "8px 0" }}>
<Box display="flex" flexDirection="column" alignItems="flex-start">
<Typography> <Typography>
{isPayoutGhst {isPayoutGhst
? formatCurrency(note.payout, 5, ghstSymbol) ? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol) : formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
} }
</Typography> </Typography>
<Typography variant="body2">{formatCurrency(note.payout * ghstPrice, 2)}</Typography>
</Box>
</TableCell> </TableCell>
<TableCell style={{ padding: "8px 0" }}> <TableCell style={{ padding: "8px 0" }}>

View File

@ -44,7 +44,7 @@ const WarmupConfirmModal = ({
minHeight="150px" minHeight="150px"
open={isOpen} open={isOpen}
headerText={ headerText={
warmupLength <= 0 warmupLength < 0
? "Bond Notification" ? "Bond Notification"
: "Bond in Warm-up" : "Bond in Warm-up"
} }
@ -52,7 +52,7 @@ const WarmupConfirmModal = ({
> >
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center"> <Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
<Box display="flex" flexDirection="column"> <Box display="flex" flexDirection="column">
{warmupLength <= 0 {warmupLength < 0
? <FormControlLabel ? <FormControlLabel
control={ control={
<Checkbox <Checkbox
@ -69,7 +69,7 @@ const WarmupConfirmModal = ({
} }
</Box> </Box>
{warmupLength <= 0 && <PrimaryButton fullWidth disabled={!isChecked} onClick={onSubmit}> {warmupLength < 0 && <PrimaryButton fullWidth disabled={!isChecked} onClick={onSubmit}>
Confirm Confirm
</PrimaryButton>} </PrimaryButton>}
</Box> </Box>

File diff suppressed because it is too large Load Diff

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -1,508 +0,0 @@
import { useState, useEffect, useMemo } 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, SecondaryButton } from "../../components/Button";
import { formatCurrency } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
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);
});
};
const bridgeNumbers = () => {
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
const number = 1 + connectedNetworks * 3;
return `(${number}, ${number})`;
};
return (
<Modal
data-testid="transaction-details-modal"
maxWidth="476px"
headerContent={
<Box display="flex" flexDirection="row">
<Typography variant="h5">
TX Hash&nbsp;
<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
width="90%"
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
backgroundColor="#1f4771"
sx={{
position: 'absolute',
top: '130px',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1,
}}
>
<SecondaryButton
fullWidth
sx={{
marginTop: "0 !important",
marginBottom: "0 !important"
}}
onClick={() => window.open(
'https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases',
'_blank',
'noopener,noreferrer'
)}
>
Get GHOST Connect
</SecondaryButton>
</Box>}
<Box
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
sx={{ filter: providerDetail ? '' : 'blur(5px)' }}
>
{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">
{currentRecord && currentRecord.finalization < 1 && currentRecord.applaused && <PrimaryButton
fullWidth
loading={false}
onClick={() => removeStoredRecord()}
>
{`Get ${bridgeNumbers()} Stake\u00B2`}
</PrimaryButton>}
<TertiaryButton
fullWidth
loading={false}
onClick={() => removeStoredRecord()}
>
Erase Record
</TertiaryButton>
<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">
<Box>
<Typography variant="subtitle1">
You are bridging to GHOST Chain. We will guide you towards (10, 10) Stake<sup>2</sup> rewards right after that.
</Typography>
</Box>
<hr style={{ margin: "10px 0", width: "100%" }} />
<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}%.`}&nbsp;
<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>
)
}

View File

@ -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>
)
}

View File

@ -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}
/>
)
}

View File

@ -10,10 +10,9 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import SettingsIcon from '@mui/icons-material/Settings'; import SettingsIcon from '@mui/icons-material/Settings';
import { useEffect, useMemo, useState, useCallback } from "react"; import { useEffect, useState } from "react";
import { useParams, useLocation, useSearchParams } from "react-router-dom"; import { useParams, useLocation, useSearchParams } from "react-router-dom";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { isAddress } from "viem";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import InfoTooltip from "../../components/Tooltip/InfoTooltip"; import InfoTooltip from "../../components/Tooltip/InfoTooltip";
@ -27,25 +26,20 @@ import { Tab, Tabs } from "../../components/Tabs/Tabs";
import { import {
UNISWAP_V2_ROUTER, UNISWAP_V2_ROUTER,
UNISWAP_V2_FACTORY, UNISWAP_V2_FACTORY,
RESERVE_ADDRESSES, DAI_ADDRESSES,
FTSO_ADDRESSES, FTSO_ADDRESSES,
EMPTY_ADDRESS,
WETH_ADDRESSES,
} from "../../constants/addresses"; } from "../../constants/addresses";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol } from "../../hooks/tokens"; import { useTokenSymbol } from "../../hooks/tokens";
import { getTokenAddress } from "../../hooks/helpers";
import PoolContainer from "./PoolContainer"; import PoolContainer from "./PoolContainer";
import SwapContainer from "./SwapContainer"; import SwapContainer from "./SwapContainer";
import TokenModal from "./TokenModal"; import TokenModal from "./TokenModal";
const Dex = ({ chainId, address, connect, config }) => { const Dex = ({ chainId, address, connect }) => {
const location = useLocation(); const location = useLocation();
const pathname = useParams(); const pathname = useParams();
const theme = useTheme(); const theme = useTheme();
const { getStorageValue, setStorageValue } = useLocalStorage();
const isSmallScreen = useMediaQuery("(max-width: 650px)"); const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)"); const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
@ -58,50 +52,15 @@ const Dex = ({ chainId, address, connect, config }) => {
const [topTokenListOpen, setTopTokenListOpen] = useState(false); const [topTokenListOpen, setTopTokenListOpen] = useState(false);
const [bottomTokenListOpen, setBottomTokenListOpen] = useState(false); const [bottomTokenListOpen, setBottomTokenListOpen] = useState(false);
const [secondsToWait, setSecondsToWait] = useState(() => getStorageValue(chainId, address, "dex-deadline", "60")); const [secondsToWait, setSecondsToWait] = useState(localStorage.getItem("dex-deadline") || "60");
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "dex-slippage", "5")); const [slippage, setSlippage] = useState(localStorage.getItem("dex-slippage") || "5");
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "dex-decimals", "5")); const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("dex-decimals") || "5");
const [actualDestinationAddress, setActualDestinationAddress] = useState(() => getStorageValue(chainId, address, "dex-destination", address));
const [destinationAddress, setDestinationAddress] = useState(actualDestinationAddress);
const [tokenAddressTop, setTokenAddressTop] = useState(EMPTY_ADDRESS); const [tokenAddressTop, setTokenAddressTop] = useState(DAI_ADDRESSES[chainId]);
const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]); const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]);
const { symbol: tokenNameTopInner } = useTokenSymbol(chainId, tokenAddressTop); const { symbol: tokenNameTop } = useTokenSymbol(chainId, tokenAddressTop);
const { symbol: tokenNameBottomInner } = useTokenSymbol(chainId, tokenAddressBottom); const { symbol: tokenNameBottom } = useTokenSymbol(chainId, tokenAddressBottom);
const chainSymbol = useMemo(() => {
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
if (chainSymbol) return chainSymbol;
return "WTF";
}, [config])
const tokenNameTop = useMemo(() => {
if (chainSymbol && tokenAddressTop === EMPTY_ADDRESS) {
return chainSymbol;
}
return tokenNameTopInner;
}, [tokenAddressTop, tokenNameTopInner, chainSymbol]);
const tokenNameBottom = useMemo(() => {
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
if (chainSymbol && tokenAddressBottom === EMPTY_ADDRESS) {
return chainSymbol;
}
return tokenNameBottomInner;
}, [tokenAddressBottom, tokenNameBottomInner, config]);
const isWrapping = useMemo(() => {
const isNative = tokenAddressTop === EMPTY_ADDRESS;
const isWrappedNative = tokenAddressBottom === WETH_ADDRESSES[chainId];
return isNative && isWrappedNative;
}, [chainId, tokenAddressTop, tokenAddressBottom]);
const isUnwrapping = useMemo(() => {
const isWrappedNative = tokenAddressTop === WETH_ADDRESSES[chainId];
const isNative = tokenAddressBottom === EMPTY_ADDRESS;
return isNative && isWrappedNative;
}, [chainId, tokenAddressTop, tokenAddressBottom]);
useEffect(() => { useEffect(() => {
if (currentQueryParameters.has("pool")) { if (currentQueryParameters.has("pool")) {
@ -116,8 +75,8 @@ const Dex = ({ chainId, address, connect, config }) => {
setTokenAddressTop(currentQueryParameters.get("from")); setTokenAddressTop(currentQueryParameters.get("from"));
newQueryParameters.set("from", currentQueryParameters.get("from")); newQueryParameters.set("from", currentQueryParameters.get("from"));
} else { } else {
setTokenAddressTop(EMPTY_ADDRESS); setTokenAddressTop(DAI_ADDRESSES[chainId]);
newQueryParameters.set("from", EMPTY_ADDRESS); newQueryParameters.set("from", DAI_ADDRESSES[chainId]);
} }
if (currentQueryParameters.has("to")) { if (currentQueryParameters.has("to")) {
@ -133,7 +92,7 @@ const Dex = ({ chainId, address, connect, config }) => {
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: location.pathname + location.search }); ReactGA.send({ hitType: "pageview", page: location.pathname + location.search });
}, [location]); }, [location])
const dexAddresses = { const dexAddresses = {
router: UNISWAP_V2_ROUTER[chainId], router: UNISWAP_V2_ROUTER[chainId],
@ -154,7 +113,7 @@ const Dex = ({ chainId, address, connect, config }) => {
} }
const changeSwapTab = (swap) => { const changeSwapTab = (swap) => {
if (swap || (isWrapping || isUnwrapping)) newQueryParameters.delete("pool"); if (swap) newQueryParameters.delete("pool");
else newQueryParameters.set("pool", true); else newQueryParameters.set("pool", true);
newQueryParameters.set("from", currentQueryParameters.get("from")); newQueryParameters.set("from", currentQueryParameters.get("from"));
newQueryParameters.set("to", currentQueryParameters.get("to")); newQueryParameters.set("to", currentQueryParameters.get("to"));
@ -183,38 +142,24 @@ const Dex = ({ chainId, address, connect, config }) => {
setSearchParams(newQueryParameters); setSearchParams(newQueryParameters);
} }
const setSlippageInner = useCallback((value) => { const setSlippageInner = (value) => {
const maybeValue = parseFloat(value); const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) { if (!maybeValue || parseFloat(value) <= 100) {
setSlippage(value); setSlippage(value);
setStorageValue(chainId, address, "dex-slippage", value); localStorage.setItem("dex-slippage", value);
}
} }
}, [chainId, address]);
const setSecondsToWaitInner = useCallback((value) => { const setSecondsToWaitInner = (value) => {
localStorage.setItem("dex-deadline", value);
setSecondsToWait(value); setSecondsToWait(value);
setStorageValue(chainId, address, "dex-deadline", value); }
}, [chainId, address]);
const setFormatDecimalsInner = useCallback((value) => { const setFormatDecimalsInner = (value) => {
if (Number(value) <= 17) { if (Number(value) <= 17) {
localStorage.setItem("dex-decimals", value);
setFormatDecimals(value); setFormatDecimals(value);
setStorageValue(chainId, address, "dex-decimals", value);
} }
}, [chainId, address]);
const setDestinationAddressInner = useCallback((value) => {
const cleanedValue = value.trim();
setDestinationAddress(value);
if (isAddress(cleanedValue)) {
setActualDestinationAddress(value);
setStorageValue(chainId, address, "dex-destination", value);
}
}, [chainId, address]);
const handleCloseSetting = () => {
setDestinationAddress(undefined);
handleSettingsOpen(false);
} }
return ( return (
@ -241,7 +186,7 @@ const Dex = ({ chainId, address, connect, config }) => {
<PageTitle <PageTitle
name={`${pathname.name.charAt(0).toUpperCase() + pathname.name.slice(1).toLowerCase()} V2 Classic`} 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 <Container
style={{ style={{
@ -254,11 +199,11 @@ const Dex = ({ chainId, address, connect, config }) => {
}} }}
> >
<Modal <Modal
maxWidth="450px" maxWidth="376px"
minHeight="200px" minHeight="200px"
open={settingsOpen} open={settingsOpen}
headerText={"Settings"} headerText={"Settings"}
onClose={() => handleCloseSetting()} onClose={() => handleSettingsOpen(false)}
> >
<Box> <Box>
<InputLabel htmlFor="slippage">Slippage</InputLabel> <InputLabel htmlFor="slippage">Slippage</InputLabel>
@ -322,35 +267,9 @@ const Dex = ({ chainId, address, connect, config }) => {
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
<Box mt="32px">
<InputLabel htmlFor="recipient">
{`${actualDestinationAddress ? "Custom" : "Default"} destination address`}
</InputLabel>
<Box mt="8px">
<FormControl variant="outlined" color="primary" fullWidth>
<OutlinedInput
inputProps={{ "data-testid": "decimals-to-wait" }}
type="text"
id="destination-to-wait"
value={destinationAddress
? destinationAddress
: actualDestinationAddress ?? address
}
onChange={event => setDestinationAddressInner(event.currentTarget.value)}
/>
</FormControl>
</Box>
<Box mt="8px">
<Typography variant="body2" color="textSecondary">
Recipient address of swapped assets and liquidity tokens
</Typography>
</Box>
</Box>
</Modal> </Modal>
<TokenModal <TokenModal
chainSymbol={chainSymbol}
account={address} account={address}
chainId={chainId} chainId={chainId}
listOpen={topTokenListOpen} listOpen={topTokenListOpen}
@ -358,7 +277,6 @@ const Dex = ({ chainId, address, connect, config }) => {
setTokenAddress={setInnerTokenAddressTop} setTokenAddress={setInnerTokenAddressTop}
/> />
<TokenModal <TokenModal
chainSymbol={chainSymbol}
account={address} account={address}
chainId={chainId} chainId={chainId}
listOpen={bottomTokenListOpen} listOpen={bottomTokenListOpen}
@ -404,14 +322,11 @@ const Dex = ({ chainId, address, connect, config }) => {
dexAddresses={dexAddresses} dexAddresses={dexAddresses}
connect={connect} connect={connect}
slippage={slippage} slippage={slippage}
destination={actualDestinationAddress ? actualDestinationAddress : address}
secondsToWait={secondsToWait} secondsToWait={secondsToWait}
setTopTokenListOpen={setTopTokenListOpen} setTopTokenListOpen={setTopTokenListOpen}
setBottomTokenListOpen={setBottomTokenListOpen} setBottomTokenListOpen={setBottomTokenListOpen}
setIsSwap={setIsSwap} setIsSwap={setIsSwap}
formatDecimals={formatDecimals} formatDecimals={formatDecimals}
isWrapping={isWrapping}
isUnwrapping={isUnwrapping}
/> />
: :
<PoolContainer <PoolContainer
@ -423,7 +338,6 @@ const Dex = ({ chainId, address, connect, config }) => {
dexAddresses={dexAddresses} dexAddresses={dexAddresses}
connect={connect} connect={connect}
slippage={slippage} slippage={slippage}
destination={actualDestinationAddress ? actualDestinationAddress : address}
secondsToWait={secondsToWait} secondsToWait={secondsToWait}
setTopTokenListOpen={setTopTokenListOpen} setTopTokenListOpen={setTopTokenListOpen}
setBottomTokenListOpen={setBottomTokenListOpen} setBottomTokenListOpen={setBottomTokenListOpen}

View File

@ -9,15 +9,10 @@ import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenA
import { SecondaryButton } from "../../components/Button"; import { SecondaryButton } from "../../components/Button";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber, formatCurrency, bigIntSqrt } from "../../helpers"; import { formatNumber, formatCurrency } from "../../helpers";
import { useBalance, useTotalSupply } from "../../hooks/tokens"; import { useBalance, useTotalSupply } from "../../hooks/tokens";
import { import { useUniswapV2Pair, useUniswapV2PairReserves, addLiquidity } from "../../hooks/uniswapv2";
useUniswapV2Pair,
useUniswapV2PairReserves,
addLiquidity,
addLiquidityETH,
} from "../../hooks/uniswapv2";
const PoolContainer = ({ const PoolContainer = ({
tokenNameTop, tokenNameTop,
@ -28,11 +23,10 @@ const PoolContainer = ({
dexAddresses, dexAddresses,
connect, connect,
slippage, slippage,
destination,
secondsToWait, secondsToWait,
setTopTokenListOpen, setTopTokenListOpen,
setBottomTokenListOpen, setBottomTokenListOpen,
formatDecimals, formatDecimals
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)"); const isSmallScreen = useMediaQuery("(max-width: 456px)");
@ -45,13 +39,11 @@ const PoolContainer = ({
balance: balanceTop, balance: balanceTop,
refetch: balanceRefetchTop, refetch: balanceRefetchTop,
contractAddress: addressTop, contractAddress: addressTop,
isNative: topIsNative,
} = useBalance(chainId, tokenNameTop, address); } = useBalance(chainId, tokenNameTop, address);
const { const {
balance: balanceBottom, balance: balanceBottom,
refetch: balanceRefetchBottom, refetch: balanceRefetchBottom,
contractAddress: addressBottom, contractAddress: addressBottom,
isNative: bottomIsNative,
} = useBalance(chainId, tokenNameBottom, address); } = useBalance(chainId, tokenNameBottom, address);
const { const {
@ -83,6 +75,34 @@ const PoolContainer = ({
const setMaxTop = () => setAmountTop(balanceTop.toString()); const setMaxTop = () => setAmountTop(balanceTop.toString());
const setMaxBottom = () => setAmountBottom(balanceBottom.toString()); const setMaxBottom = () => setAmountBottom(balanceBottom.toString());
const bigIntSqrt = (n) => {
if (n < 0n) {
throw new Error("Cannot compute the square root of a negative number.");
}
if (n < 2n) {
return n; // The square root of 0 or 1 is the number itself
}
let low = 0n;
let high = n;
let mid;
while (low <= high) {
mid = (low + high) / 2n;
const midSquared = mid * mid;
if (midSquared === n) {
return mid; // Found the exact square root
} else if (midSquared < n) {
low = mid + 1n; // Move to the right half
} else {
high = mid - 1n; // Move to the left half
}
}
return high; // The integer part of the square root
}
const estimatedAmountOut = useMemo(() => { const estimatedAmountOut = useMemo(() => {
const pairReserves0 = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() const pairReserves0 = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase()
? pairReserves.reserve0 ? pairReserves.reserve0
@ -131,40 +151,14 @@ const PoolContainer = ({
} }
} }
}, [addressBottom, balanceTop, balanceBottom, amountTop, amountBottom, tokenAddresses, pairReserves]); }, [addressBottom, balanceTop, balanceBottom, amountTop, amountBottom, tokenAddresses, pairReserves])
const poolShares = useMemo(() => {
const hundred = new DecimalBigNumber(1n, 2);
if (lpTotalSupply?._value == 0n) {
return { currentShares: lpTotalSupply, nextShares: lpTotalSupply };
}
const currentShares = lpBalance.div(lpTotalSupply).div(hundred);
const nextShares = (lpBalance.add(estimatedAmountOut)).div(lpTotalSupply.add(estimatedAmountOut)).div(hundred);
return { currentShares, nextShares };
}, [lpBalance, lpTotalSupply, estimatedAmountOut]);
const poolPrices = useMemo(() => {
const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase()
? pairReserves.reserve0
: pairReserves.reserve1;
const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase()
? pairReserves.reserve1
: pairReserves.reserve0;
let priceIn = "0";
let priceOut = "0";
if (amountIn?._value > 0n) priceIn = (amountOut.div(amountIn)).toString();
if (amountOut?._value > 0n) priceOut = (amountIn.div(amountOut)).toString();
return { priceIn , priceOut }
}, [addressTop, addressBottom, balanceTop, tokenAddresses, pairReserves]);
const addLiquidityInner = async () => { const addLiquidityInner = async () => {
setIsPending(true); setIsPending(true);
const deadline = Math.floor(Date.now() / 1000) + secondsToWait; const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
const destination = address;
const shares = 100000; const shares = 100000;
const one = BigInt(shares * 100); const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage); const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
@ -177,7 +171,7 @@ const PoolContainer = ({
const amountAMin = amountADesired * bigIntSlippage / one; const amountAMin = amountADesired * bigIntSlippage / one;
const amountBMin = amountBDesired * bigIntSlippage / one; const amountBMin = amountBDesired * bigIntSlippage / one;
const params = { await addLiquidity(
chainId, chainId,
tokenNameTop, tokenNameTop,
tokenNameBottom, tokenNameBottom,
@ -185,16 +179,9 @@ const PoolContainer = ({
amountBDesired, amountBDesired,
amountAMin, amountAMin,
amountBMin, amountBMin,
address,
destination, destination,
deadline, deadline,
} );
if (topIsNative || bottomIsNative) {
await addLiquidityETH(params)
} else {
await addLiquidity(params);
}
await balanceRefetchTop(); await balanceRefetchTop();
await balanceRefetchBottom(); await balanceRefetchBottom();
@ -246,7 +233,7 @@ const PoolContainer = ({
} }
arrowOnClick={onSwap} arrowOnClick={onSwap}
/> />
<Box {!isSmallScreen && <Box
m="10px 0" m="10px 0"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
@ -257,29 +244,24 @@ const PoolContainer = ({
style={{ fontSize: "12px", color: theme.colors.gray[40] }} style={{ fontSize: "12px", color: theme.colors.gray[40] }}
> >
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameTop}`}</Typography> <Typography fontSize="12px" lineHeight="15px">Current Balance:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(poolPrices.priceIn, formatDecimals, tokenNameBottom)}</Typography> <Typography fontSize="12px" lineHeight="15px">{formatNumber(lpBalance, formatDecimals)} LP</Typography>
</Box> </Box>
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom}`}</Typography> <Typography fontSize="12px" lineHeight="15px">Total Supply:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(poolPrices.priceOut, formatDecimals, tokenNameTop)}</Typography> <Typography fontSize="12px" lineHeight="15px">{formatNumber(lpTotalSupply, formatDecimals)} LP</Typography>
</Box> </Box>
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Current Pool Share:</Typography> <Typography fontSize="12px" lineHeight="15px">Extra Balance:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatNumber(poolShares.currentShares, formatDecimals)}%</Typography> <Typography fontSize="12px" lineHeight="15px">~{formatNumber(estimatedAmountOut, formatDecimals)} LP</Typography>
</Box>
<Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Next Pool Share:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatNumber(poolShares.nextShares, formatDecimals)}%</Typography>
</Box>
</Box> </Box>
</Box>}
<TokenAllowanceGuard <TokenAllowanceGuard
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)} spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
tokenName={tokenNameTop} tokenName={tokenNameTop}
owner={address} owner={address}
spender={dexAddresses.router} spender={dexAddresses.router}
decimals={balanceTop._decimals} decimals={balanceTop._decimals}
isNative={topIsNative}
approvalText={"Approve " + tokenNameTop} approvalText={"Approve " + tokenNameTop}
approvalPendingText={"Approving..."} approvalPendingText={"Approving..."}
connect={connect} connect={connect}
@ -292,7 +274,6 @@ const PoolContainer = ({
owner={address} owner={address}
spender={dexAddresses.router} spender={dexAddresses.router}
decimals={balanceBottom._decimals} decimals={balanceBottom._decimals}
isNative={bottomIsNative}
approvalText={"Approve " + tokenNameBottom} approvalText={"Approve " + tokenNameBottom}
approvalPendingText={"Approving..."} approvalPendingText={"Approving..."}
connect={connect} connect={connect}

View File

@ -1,4 +1,4 @@
import { useState, useMemo, useEffect } from "react"; import { useState, useEffect } from "react";
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material"; import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
@ -13,16 +13,8 @@ import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../helpers/timeUtil"; import { prettifySecondsInDays } from "../../helpers/timeUtil";
import { getTokenAddress } from "../../hooks/helpers"; import { getTokenAddress } from "../../hooks/helpers";
import { useBalance, depositNative, withdrawWeth } from "../../hooks/tokens"; import { useBalance } from "../../hooks/tokens";
import { import { useUniswapV2Pair, useUniswapV2PairReserves, swapExactTokensForTokens } from "../../hooks/uniswapv2";
useUniswapV2Pair,
useUniswapV2PairReserves,
swapExactTokensForTokens,
swapExactETHForTokens,
swapExactTokensForETH,
} from "../../hooks/uniswapv2";
import { EMPTY_ADDRESS } from "../../constants/addresses";
const SwapContainer = ({ const SwapContainer = ({
tokenNameTop, tokenNameTop,
@ -35,12 +27,9 @@ const SwapContainer = ({
setTopTokenListOpen, setTopTokenListOpen,
setBottomTokenListOpen, setBottomTokenListOpen,
slippage, slippage,
destination,
secondsToWait, secondsToWait,
setIsSwap, setIsSwap,
formatDecimals, formatDecimals
isWrapping,
isUnwrapping,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)"); const isSmallScreen = useMediaQuery("(max-width: 456px)");
@ -56,14 +45,11 @@ const SwapContainer = ({
balance: balanceTop, balance: balanceTop,
refetch: balanceRefetchTop, refetch: balanceRefetchTop,
contractAddress: addressTop, contractAddress: addressTop,
isNative: topIsNative,
} = useBalance(chainId, tokenNameTop, address); } = useBalance(chainId, tokenNameTop, address);
const { const {
balance: balanceBottom, balance: balanceBottom,
refetch: balanceRefetchBottom, refetch: balanceRefetchBottom,
contractAddress: addressBottom, contractAddress: addressBottom,
isNative: bottomIsNative,
} = useBalance(chainId, tokenNameBottom, address); } = useBalance(chainId, tokenNameBottom, address);
const { const {
@ -87,73 +73,42 @@ const SwapContainer = ({
const setMax = () => setAmountTop(balanceTop.toString()); const setMax = () => setAmountTop(balanceTop.toString());
useEffect(() => { useEffect(() => {
if (isWrapping || isUnwrapping) {
setAmountBottom(amountTop.toString());
setNextPrice("1");
setCurrentPrice("1");
return;
}
const zero = new DecimalBigNumber(0n, 0); const zero = new DecimalBigNumber(0n, 0);
const raw = new DecimalBigNumber(amountTop, balanceTop._decimals); const raw = new DecimalBigNumber(amountTop, balanceTop._decimals);
const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals); const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals);
const amountInWithFee = amountInRaw.mul(new DecimalBigNumber(997n, 3)); const amountInWithFee = amountInRaw.mul(new DecimalBigNumber(997n, 3));
const topAddress = getTokenAddress(chainId, tokenNameTop);
const bottomAddress = getTokenAddress(chainId, tokenNameBottom);
const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1; const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1;
const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0; const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0;
if (amountOut.eq(zero)) { if (amountIn.eq(zero)) {
setCurrentPrice(""); setCurrentPrice("");
} else { } else {
setCurrentPrice(amountIn.div(amountOut).toString()); setCurrentPrice(amountIn.div(amountOut).toString());
} }
if (amountOut.eq(zero) || amountInWithFee.eq(zero)) { if (amountIn.eq(zero) || amountInWithFee.eq(zero)) {
setAmountBottom(""); setAmountBottom("");
setNextPrice(""); setNextPrice("");
} else { } else {
const nominator = amountOut.mul(amountInWithFee); const nominator = amountOut.mul(amountIn);
const denominator = amountIn.add(amountInWithFee); const denominator = amountIn.add(amountInWithFee);
const newAmountOut = nominator.div(denominator); const newAmountOut = nominator.div(denominator);
const newReserveIn = amountIn.add(amountInWithFee); setAmountBottom(amountOut.sub(newAmountOut).toString());
const newReserveOut = amountOut.sub(newAmountOut); setNextPrice(denominator.div(newAmountOut).toString())
const nextPrice = newReserveIn.div(newReserveOut);
setAmountBottom(newAmountOut.toString());
setNextPrice(nextPrice.toString());
} }
}, [pairReserves, addressBottom, amountTop, addressTop, isWrapping, isUnwrapping]); }, [amountTop, addressTop]);
const minReceived = useMemo(() => {
const decimals = 7;
const shares = Math.pow(10, decimals);
const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares));
const slippageDecimalBigNumber = new DecimalBigNumber(bigIntSlippage, 2);
const bigIntAmount = BigInt(Math.round(amountBottom * shares));
const amountDecimalBigNumber = new DecimalBigNumber(bigIntAmount, decimals);
const tmpResult = amountDecimalBigNumber.mul(slippageDecimalBigNumber);
const result = new DecimalBigNumber(tmpResult?._value, tmpResult?._decimals + decimals);
return result?.toString();
}, [amountBottom, amountBottom, slippage, balanceBottom]);
const buttonText = useMemo(() => {
let text = "Swap";
if (isWrapping) text = "Wrap";
else if (isUnwrapping) text = "Unwrap";
else if (pairAddress === EMPTY_ADDRESS) text = "Create Pool";
return text;
}, [isWrapping, isUnwrapping, pairAddress]);
const swapTokens = async () => { const swapTokens = async () => {
setIsPending(true); setIsPending(true);
const deadline = Math.floor(Date.now() / 1000) + secondsToWait; const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
const destination = address;
const shares = 100000; const shares = 100000;
const one = BigInt(shares * 100); const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage); const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
@ -165,30 +120,14 @@ const SwapContainer = ({
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals))); const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
const amountBMin = amountBDesired * bigIntSlippage / one; const amountBMin = amountBDesired * bigIntSlippage / one;
if (isWrapping) { await swapExactTokensForTokens(
await depositNative(chainId, address, amountADesired);
} else if (isUnwrapping) {
await withdrawWeth(chainId, address, amountADesired);
} else {
const params = {
chainId, chainId,
amountADesired, amountADesired,
amountBMin, amountBMin,
tokenNameTop, [tokenNameTop, tokenNameBottom],
tokenNameBottom,
destination, destination,
address,
deadline deadline
}; );
if (topIsNative) {
await swapExactETHForTokens(params)
} else if (bottomIsNative) {
await swapExactTokensForETH(params)
} else {
await swapExactTokensForTokens(params);
}
}
await balanceRefetchTop(); await balanceRefetchTop();
await balanceRefetchBottom(); await balanceRefetchBottom();
@ -236,7 +175,7 @@ const SwapContainer = ({
} }
arrowOnClick={onSwap} arrowOnClick={onSwap}
/> />
<Box {!isSmallScreen && <Box
m="10px 0" m="10px 0"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
@ -247,29 +186,24 @@ const SwapContainer = ({
style={{ fontSize: "12px", color: theme.colors.gray[40] }} style={{ fontSize: "12px", color: theme.colors.gray[40] }}
> >
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom} (Current)`}</Typography> <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>
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom} (Next)`}</Typography> <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>
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Min. Receive:</Typography> <Typography fontSize="12px" lineHeight="15px">Transaction deadline:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(minReceived, formatDecimals, tokenNameBottom)}</Typography>
</Box>
<Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Tx. Deadline:</Typography>
<Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography> <Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography>
</Box> </Box>
</Box> </Box>}
<TokenAllowanceGuard <TokenAllowanceGuard
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)} spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
tokenName={tokenNameTop} tokenName={tokenNameTop}
owner={address} owner={address}
spender={dexAddresses.router} spender={dexAddresses.router}
decimals={balanceTop._decimals} decimals={balanceTop._decimals}
isNative={topIsNative}
approvalText={"Approve " + tokenNameTop} approvalText={"Approve " + tokenNameTop}
approvalPendingText={"Approving..."} approvalPendingText={"Approving..."}
connect={connect} connect={connect}
@ -289,10 +223,17 @@ const SwapContainer = ({
onClick={() => address === "" ? onClick={() => address === "" ?
connect() connect()
: :
(!isWrapping && !isUnwrapping) && pairAddress === EMPTY_ADDRESS ? setIsSwap(false) : swapTokens() pairAddress === "0x0000000000000000000000000000000000000000" ? setIsSwap(false) : swapTokens()
} }
> >
{address === "" ? "Connect" : buttonText } {address === "" ?
"Connect"
:
pairAddress === "0x0000000000000000000000000000000000000000" ?
"Create Pool"
:
"Swap"
}
</SecondaryButton> </SecondaryButton>
</TokenAllowanceGuard> </TokenAllowanceGuard>
</Box> </Box>

View File

@ -1,5 +1,4 @@
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect, useMemo } from "react";
import { useConfig } from "wagmi";
import { import {
Divider, Divider,
Typography, Typography,
@ -20,14 +19,9 @@ import TokenStack from "../../components/TokenStack/TokenStack";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber } from "../../helpers/"; import { formatNumber } from "../../helpers/";
import { useBalance, useTokenSymbol } from "../../hooks/tokens"; import { useBalance, useTokenSymbol } from "../../hooks/tokens";
import { import { DAI_ADDRESSES, FTSO_ADDRESSES, STNK_ADDRESSES, GHST_ADDRESSES } from "../../constants/addresses";
RESERVE_ADDRESSES,
FTSO_ADDRESSES,
GHST_ADDRESSES,
EMPTY_ADDRESS
} from "../../constants/addresses";
const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setTokenAddress }) => { const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }) => {
const isSmallScreen = useMediaQuery("(max-width: 599px)"); const isSmallScreen = useMediaQuery("(max-width: 599px)");
const isVerySmallScreen = useMediaQuery("(max-width: 425px)"); const isVerySmallScreen = useMediaQuery("(max-width: 425px)");
@ -43,13 +37,14 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
const { symbol: searchSymbol } = useTokenSymbol(chainId, address); const { symbol: searchSymbol } = useTokenSymbol(chainId, address);
const { balance: searchBalance } = useBalance(chainId, address, account); const { balance: searchBalance } = useBalance(chainId, address, account);
const { balance: nativeBalance } = useBalance(chainId, chainSymbol, account); const { balance: daiBalance } = useBalance(chainId, "GDAI", account);
const { balance: reserveBalance } = useBalance(chainId, "RESERVE", account);
const { balance: ftsoBalance } = useBalance(chainId, "FTSO", account); const { balance: ftsoBalance } = useBalance(chainId, "FTSO", account);
const { balance: stnkBalance } = useBalance(chainId, "STNK", account);
const { balance: ghstBalance } = useBalance(chainId, "GHST", 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: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const searchToken = useMemo(() => { const searchToken = useMemo(() => {
@ -64,16 +59,10 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
const knownTokens = useMemo(() => { const knownTokens = useMemo(() => {
return [ return [
{ {
name: chainSymbol, name: daiSymbol,
icons: [chainSymbol], icons: ["GDAI"],
balance: nativeBalance, balance: daiBalance,
address: EMPTY_ADDRESS, address: DAI_ADDRESSES[chainId]
},
{
name: reserveSymbol,
icons: [chainSymbol],
balance: reserveBalance,
address: RESERVE_ADDRESSES[chainId]
}, },
{ {
name: ftsoSymbol, name: ftsoSymbol,
@ -81,6 +70,12 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
balance: ftsoBalance, balance: ftsoBalance,
address: FTSO_ADDRESSES[chainId] address: FTSO_ADDRESSES[chainId]
}, },
{
name: stnkSymbol,
icons: ["STNK"],
balance: stnkBalance,
address: STNK_ADDRESSES[chainId]
},
{ {
name: ghstSymbol, name: ghstSymbol,
icons: ["GHST"], icons: ["GHST"],
@ -88,7 +83,7 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
address: GHST_ADDRESSES[chainId] address: GHST_ADDRESSES[chainId]
} }
] ]
}, [reserveSymbol, ftsoSymbol, ghstSymbol, reserveBalance, ftsoBalance, ghstBalance]); }, [daiSymbol, ftsoSymbol, stnkSymbol, ghstSymbol, daiBalance, ftsoBalance, stnkBalance, ghstBalance]);
useEffect(() => { useEffect(() => {
if (isAddress(userInput)) { if (isAddress(userInput)) {

View File

@ -11,7 +11,7 @@ import TokenStack from "../../components/TokenStack/TokenStack";
import { PrimaryButton } from "../../components/Button"; import { PrimaryButton } from "../../components/Button";
import { Tab, Tabs } from "../../components/Tabs/Tabs"; 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 { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatCurrency, formatNumber } from "../../helpers"; import { formatCurrency, formatNumber } from "../../helpers";
@ -44,14 +44,14 @@ const Faucet = ({ chainId, address, config, connect }) => {
symbol: "", symbol: "",
}) })
const reserveConversionRate = useConversionRate(chainId, "RESERVE"); const daiConversionRate = useConversionRate(chainId, "GDAI");
const accumulatedDonation = useAccumulatedDonation(chainId, "RESERVE"); 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: nativeBalance, refetch: balanceRefetch } = useBalance({ address });
const { data: contractBalance, refetch: contractBalanceRefetch } = useBalance({ address: RESERVE_ADDRESSES[chainId] }); const { data: contractBalance, refetch: contractBalanceRefetch } = useBalance({ address: DAI_ADDRESSES[chainId] });
const { totalSupply: reserveTotalSupply, refetch: refetchReserveTotalSupply } = useTotalSupply(chainId, "RESERVE"); const { totalSupply: reserveTotalSupply, refetch: refetchReserveTotalSupply } = useTotalSupply(chainId, "GDAI");
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE"); const { symbol: faucetSymbol } = useTokenSymbol(chainId, "GDAI");
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/faucet" }); ReactGA.send({ hitType: "pageview", page: "/faucet" });
@ -93,10 +93,10 @@ const Faucet = ({ chainId, address, config, connect }) => {
}, [amount, balance, nativeInfo]) }, [amount, balance, nativeInfo])
const estimatedAmountIn = useMemo(() => { const estimatedAmountIn = useMemo(() => {
const rate = new DecimalBigNumber(reserveConversionRate.toString(), 0); const rate = new DecimalBigNumber(daiConversionRate.toString(), 0);
const value = new DecimalBigNumber(amount, nativeInfo.decimals); const value = new DecimalBigNumber(amount, nativeInfo.decimals);
return value.mul(rate); return value.mul(rate);
}, [amount, reserveConversionRate, nativeInfo]); }, [amount, daiConversionRate, nativeInfo]);
const contractBalanceFree = useMemo(() => { const contractBalanceFree = useMemo(() => {
const realContractBalance = contractBalance ? contractBalance.value : 0n; const realContractBalance = contractBalance ? contractBalance.value : 0n;
@ -129,7 +129,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
} }
await balanceRefetch(); await balanceRefetch();
await reserveBalanceRefetch(); await daiBalanceRefetch();
await contractBalanceRefetch(); await contractBalanceRefetch();
await refetchReserveTotalSupply(); await refetchReserveTotalSupply();
setAmount(""); 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" /> <meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
</Helmet> </Helmet>
<PageTitle name={`${reserveSymbol} Faucet`} subtitle={`Swap Sepolia ${nativeInfo.symbol} for ${reserveSymbol}`} /> <PageTitle name={`${faucetSymbol} Faucet`} subtitle={`Swap Sepolia ${nativeInfo.symbol} for ${faucetSymbol}.`} />
<Container <Container
style={{ style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem", paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
@ -188,7 +188,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
</Tabs> </Tabs>
{!isSemiSmallScreen && <PrimaryButton {!isSemiSmallScreen && <PrimaryButton
variant="text" variant="text"
href={`${scanInfo.url}/token/${RESERVE_ADDRESSES[chainId]}`} href={`${scanInfo.url}/token/${DAI_ADDRESSES[chainId]}`}
> >
Check on {scanInfo.name} Check on {scanInfo.name}
</PrimaryButton>} </PrimaryButton>}
@ -211,14 +211,14 @@ const Faucet = ({ chainId, address, config, connect }) => {
{!isMint && <SwapCard {!isMint && <SwapCard
id={`faucet-sepolia-eth`} id={`faucet-sepolia-eth`}
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"} inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
tokenName={reserveSymbol} tokenName={faucetSymbol}
token={<TokenStack tokens={[reserveSymbol]} sx={{ fontSize: "21px" }} />} token={<TokenStack tokens={[faucetSymbol]} sx={{ fontSize: "21px" }} />}
info={`${formatCurrency(reserveBalance.toString(), 4, reserveSymbol)}`} info={`${formatCurrency(daiBalance.toString(), 4, faucetSymbol)}`}
value={amount} value={amount}
onChange={event => setAmount(event.currentTarget.value)} onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }} inputProps={{ "data-testid": "fromInput" }}
endString={"Max"} endString={"Max"}
endStringOnClick={() => setAmount(reserveBalance.toString())} endStringOnClick={() => setAmount(daiBalance.toString())}
/>} />}
<Box <Box
mb="20px" mb="20px"
@ -231,15 +231,15 @@ const Faucet = ({ chainId, address, config, connect }) => {
<> <>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}> <Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">{nativeInfo.symbol} multiplier:</Typography>} {!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>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}> <Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>} {!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>
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}> <Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {reserveSymbol} balance:</Typography>} {!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {faucetSymbol} balance:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(reserveBalance, 5, reserveSymbol)}</Typography> <Typography fontSize="12px" lineHeight="15px">{formatCurrency(daiBalance, 5, faucetSymbol)}</Typography>
</Box> </Box>
</> </>
)} )}
@ -267,7 +267,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
preparedAmount?._value === 0n || preparedAmount?._value === 0n ||
isPending || isPending ||
(isMint && balance?.lt(preparedAmount)) || (isMint && balance?.lt(preparedAmount)) ||
(!isMint && reserveBalance?.lt(preparedAmount)) (!isMint && daiBalance?.lt(preparedAmount))
) )
} }
loading={isPending} loading={isPending}

View File

@ -1,87 +0,0 @@
import { useEffect } from "react";
import ReactGA from "react-ga4";
import { useNavigate, useParams } 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 { network } = useParams();
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
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(`/${network}/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;

View File

@ -1,246 +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 { useLocalStorage } from "../../hooks/localstorage";
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 { getStorageValue, setStorageValue } = useLocalStorage();
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
const [myProposals, setMyProposalsInner] = useState(() => 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);
const setMyProposals = useCallback((proposals) => {
setMyProposalsInner(proposals);
setStorageValue(chainId, address, MY_PROPOSALS_PREFIX, proposals.map(id => id.toString()))
}, [chainId, address]);
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>
}
>
{isSmallScreen
? <Box display="flex" flexDirection="column">
{proposalFunctions?.map((metadata, index) => {
return parseFunctionCalldata({
metadata,
index,
chainId,
removeCalldata,
nativeCoin: nativeCurrency,
isTable: false,
});
})}
</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,
removeCalldata,
nativeCoin: nativeCurrency,
});
})}</TableBody>
</Table>
</TableContainer>
}
</Paper>}
</Box>
</Container>
</Box>
</>
)
}
export default NewProposal;

View File

@ -1,497 +0,0 @@
import { useEffect, useState, useMemo, useCallback } 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 { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
import {
getVoteValue,
getVoteTarget,
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 { getStorageValue, setStorageValue } = useLocalStorage();
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 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 = useCallback(async (support) => {
setIsPending(true);
const result = await castVote(chainId, address, proposalId, support);
if (result) {
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
const proposals = JSON.parse(storedVotedProposals || "[]").map(id => BigInt(id));
proposals.push(proposalId);
setStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, proposals.map(id => id.toString()));
await queryClient.invalidateQueries();
}
setIsPending(false);
}, [chainId, address, proposalId]);
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={`GDP-${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
gap="20px"
display="flex"
justifyContent={"space-between"}
flexDirection={isSmallScreen ? "column" : "row"}
>
<Paper
fullWidth
enableBackground
headerContent={
<Box display="flex" alignItems="center" flexDirection="row" gap="10px">
{!isSmallScreen &&<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={getVoteValue(forVotes?._value ?? 0n, totalVotes?._value ?? 0n)}
target={getVoteTarget(totalVotes?._value ?? 0n, totalSupply?._value ?? 0n)}
/>
</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)}
>
{isPending ? "Voting..." : "For"}
</SecondaryButton>
<SecondaryButton
fullWidth
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
onClick={() => handleVote(0)}
>
{isPending ? "Voting..." : "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>
}
>
{isSmallScreen
? <Box display="flex" flexDirection="column">
{proposalDetails?.map((metadata, index) => {
return parseFunctionCalldata({
metadata,
index,
chainId,
nativeCoin: nativeCurrency,
isTable: false,
});
})}
</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,
nativeCoin: 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
? convertStatusToLabel(state)
: isPending ? "Executing..." : "Execute"
}
</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;

View File

@ -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"
>
ghostDAOs adaptive governance system algorithmically sets minimum collateral based on activity.
&nbsp;<Link
color={theme.colors.primary[300]}
href="http://forum.ghostchain.io/"
target="_blank"
rel="noopener noreferrer"
>Learn more here.</Link>
</Typography>
)
};
export default GovernanceInfoText;

View File

@ -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} />;
}

View File

@ -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.
&nbsp;<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;

View File

@ -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;

View File

@ -1,334 +0,0 @@
import { useState, useMemo } from "react";
import { useNavigate, useParams } 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";
import { useLocalStorage } from "../../../hooks/localstorage";
const MAX_PROPOSALS_TO_SHOW = 10;
const ProposalsList = ({ chainId, address, config }) => {
const isSmallScreen = useScreenSize("md");
const navigate = useNavigate();
const theme = useTheme();
const { network } = useParams();
const { getStorageValue, setStorageValue } = useLocalStorage();
const [proposalsFilter, setProposalFilter] = useState("active");
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
const [myProposals, setMyProposals] = useState(() => myStoredProposals.map(id => BigInt(id)));
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
const [votedProposals, setVotedProposals] = useState(() => 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(`${network}/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(`/${network}/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();
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={proposal?.voteValue ?? 0}
target={proposal?.voteTarget ?? 50}
/>
</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={proposal?.voteValue ?? 0}
target={proposal?.voteTarget ?? 50}
/>
</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 alreadyHappened = blockNumber > deadline;
const diff = alreadyHappened ? blockNumber - deadline : deadline - blockNumber;
const voteSeconds = Number(diff * networkAvgBlockSpeed(chainId));
const result = prettifySeconds(voteSeconds, "min");
if (result === "now") {
return new Date(Date.now()).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
const prefix = alreadyHappened ? "" : "in ";
const postfix = alreadyHappened ? " ago" : "";
return `${prefix}${result}${postfix}`;
}
export default ProposalsList;

View File

@ -1,31 +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 <ParsedCell {...props} />
}

View File

@ -1,331 +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>
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...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[1]"
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}
maxWidth="calc(100vw - 70px)"
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>
)
}

View File

@ -1,85 +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 { GHOST_GOVERNANCE_ADDRESSES } from "../../../../constants/addresses";
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
import { ArgumentInput, ParsedCell } from "./index";
export const prepareSetLateQuorumExtensionCalldata = (chainId, lateQuorumVoteExtension) => {
const value = 0n;
const label = "setLateQuorumVoteExtension";
const target = GHOST_GOVERNANCE_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: GovernorAbi,
functionName: 'setLateQuorumVoteExtension',
args: [lateQuorumVoteExtension]
});
return { label, target, calldata, value };
}
export const prepareSetLateQuorumExtensionDescription = "updates the current value of the vote extension parameter: the number of blocks that are required to pass from the time a proposal reaches quorum until its voting period ends.";
export const SetLateQuorumExtensionParsed = (props) => {
const [isOpened, setIsOpened] = useState(false);
return (
<>
<Modal
headerContent={
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
<Typography variant="h4">View setLateQuorumVoteExtension</Typography>
</Box>
}
open={isOpened}
onClose={() => setIsOpened(false)}
maxWidth="460px"
minHeight="80px"
>
<Box minHeight="100px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
<SetLateQuorumExtensionSteps {...props} />
</Box>
</Modal>
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />
</>
)
}
export const SetLateQuorumExtensionSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
const createMode = args === undefined;
const [lateQuorumVoteExtension, setlateQuorumVoteExtension] = useState(args?.at(0));
const handleProceed = () => {
addCalldata(prepareSetLateQuorumExtensionCalldata(chainId, lateQuorumVoteExtension));
}
return (
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent={"space-between"}>
<ArgumentInput
disabled={!createMode}
endString="blocks"
label="lateQuorumVoteExtension"
value={lateQuorumVoteExtension ?? ""}
setValue={createMode ? setlateQuorumVoteExtension : () => {}}
tooltip="Vote extension defines extension in blocks if a vote causes quorum to be reached, the proposal's voting period may be extended so that it does not end before at least a specified time has passed"
/>
{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={!lateQuorumVoteExtension}
onClick={() => handleProceed()}
fullWidth
>
Create Function
</PrimaryButton>
</Box>}
</Box>
);
}

View File

@ -1,85 +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 { GHOST_GOVERNANCE_ADDRESSES } from "../../../../constants/addresses";
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
import { ArgumentInput, ParsedCell } from "./index";
export const prepareSetProposalThresholdCalldata = (chainId, newThreshold) => {
const value = 0n;
const label = "setProposalThreshold";
const target = GHOST_GOVERNANCE_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: GovernorAbi,
functionName: 'setProposalThreshold',
args: [newThreshold]
});
return { label, target, calldata, value };
}
export const prepareSetProposalThresholdDescription = "updates the minimum amount required to create a proposal. This threshold applies only when there are no Active or Pending proposals; otherwise, a higher threshold is enforced.";
export const SetProposalThresholdParsed = (props) => {
const [isOpened, setIsOpened] = useState(false);
return (
<>
<Modal
headerContent={
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
<Typography variant="h4">View setProposalThreshold</Typography>
</Box>
}
open={isOpened}
onClose={() => setIsOpened(false)}
maxWidth="460px"
minHeight="80px"
>
<Box minHeight="100px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
<SetProposalThresholdSteps {...props} />
</Box>
</Modal>
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />
</>
)
}
export const SetProposalThresholdSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
const createMode = args === undefined;
const [newThreshold, setNewThreshold] = useState(args?.at(0));
const handleProceed = () => {
addCalldata(prepareSetProposalThresholdCalldata(chainId, newThreshold));
}
return (
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent={"space-between"}>
<ArgumentInput
disabled={!createMode}
endString="!CSPR units"
label="newProposalThreshold"
value={newThreshold ?? ""}
setValue={createMode ? setNewThreshold : () => {}}
tooltip="The new proposal threshold defines the minimum amount each proposer must lock for the duration of the proposal. This locked amount serves as the required deposit to create a proposal."
/>
{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={!newThreshold}
onClick={() => handleProceed()}
fullWidth
>
Create Function
</PrimaryButton>
</Box>}
</Box>
);
}

View File

@ -1,108 +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>
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...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 === undefined ? true : args.at(2));
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>
);
}

View File

@ -1,353 +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 { abi as GovernorAbi } from "../../../../abi/GhostGovernor.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";
import { prepareSetProposalThresholdDescription, prepareSetProposalThresholdCalldata, SetProposalThresholdSteps, SetProposalThresholdParsed } from "./ProposalThreshold";
import { prepareSetLateQuorumExtensionDescription, prepareSetLateQuorumExtensionCalldata, SetLateQuorumExtensionSteps, SetLateQuorumExtensionParsed } from "./LateQuorumExtension";
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" },
{ value: "setProposalThreshold", label: "setProposalThreshold" },
{ value: "setLateQuorumVoteExtension", label: "setLateQuorumVoteExtension" },
];
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi, GovernorAbi];
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,
isTable = true,
}) => {
const { label, calldata, target, value } = metadata;
const { functionName, args } = identifyAction(calldata);
const labelOrName = label ?? (functionName ?? "Unknown");
const remove = removeCalldata && (() => removeCalldata(index));
const props = {
isTable,
calldata,
label: labelOrName,
chainId: chainId,
remove: removeCalldata,
nativeCoin,
value,
target,
args,
id: index,
};
switch (functionName) {
case allPossibleFunctions[0].value:
return <AuditReservesParsed key={index} {...props} />;
case allPossibleFunctions[1].value:
return <SetAdjustmentParsed key={index} {...props} />;
case allPossibleFunctions[2].value:
return <CreateBondParsed {...props} />;
case allPossibleFunctions[3].value:
return <SetProposalThresholdParsed key={index} {...props} />;
case allPossibleFunctions[4].value:
return <SetLateQuorumExtensionParsed key={index} {...props} />;
default:
return <ParsedCell key={index} {...props} />;
}
}
export const getFunctionArguments = (functionName) => {
switch (functionName) {
case allPossibleFunctions[1].value:
return SetAdjustmentSteps;
case allPossibleFunctions[2].value:
return CreateBondSteps;
case allPossibleFunctions[3].value:
return SetProposalThresholdSteps;
case allPossibleFunctions[4].value:
return SetLateQuorumExtensionSteps;
default:
return null;
}
}
export const getFunctionCalldata = (functionName, chainId) => {
switch (functionName) {
case allPossibleFunctions[0].value:
return prepareAuditReservesCalldata(chainId);
case allPossibleFunctions[1].value:
return prepareSetAdjustmentCalldata(chainId);
case allPossibleFunctions[2].value:
return prepareCreateBondCalldata(chainId);
case allPossibleFunctions[3].value:
return prepareSetProposalThresholdCalldata(chainId);
case allPossibleFunctions[4].value:
return prepareSetLateQuorumExtensionCalldata(chainId);
default:
return null;
}
}
export const getFunctionDescription = (functionName) => {
switch (functionName) {
case allPossibleFunctions[0].value:
return prepareAuditReservesDescription;
case allPossibleFunctions[1].value:
return prepareSetAdjustmentDescription;
case allPossibleFunctions[2].value:
return prepareCreateBondDescription;
case allPossibleFunctions[3].value:
return prepareSetProposalThresholdDescription;
case allPossibleFunctions[4].value:
return prepareSetLateQuorumExtensionDescription;
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);
}
};
if (props.isTable) {
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>
)
}
return (
<Box
id={props.id + `--proposalFunction`}
data-testid={props.id + `--proposalFunction`}
display="flex"
flexDirection="column"
>
<Box display="flex" justifyContent="space-between">
<Typography>{`Function ${props.id + 1}`}</Typography>
<Typography>{props.label}</Typography>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography>Target</Typography>
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.target)}</Typography>
</Link>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography>Calldata</Typography>
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.calldata)}</Typography>
</Link>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography>Value</Typography>
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
</Link>
</Box>
{(props.args || props.remove) && <Box
display="flex"
flexDirection="row"
justifyContent="space-between"
gap="10px"
marginTop="10px"
>
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
</Box>}
<hr width="100%" style={{ margin: "20px 0" }} />
</Box>
)
}

View File

@ -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";
}
}

View File

@ -7,7 +7,6 @@ import Paper from "../../components/Paper/Paper";
import GhostIcon from "../../assets/icons/ghost-nav-header.svg?react"; import GhostIcon from "../../assets/icons/ghost-nav-header.svg?react";
import EthIcon from "../../assets/tokens/ETH.svg?react"; import EthIcon from "../../assets/tokens/ETH.svg?react";
import { parseKnownToken } from "../../components/Token/Token"
import { useSwitchChain } from 'wagmi'; import { useSwitchChain } from 'wagmi';
import toast from "react-hot-toast"; import toast from "react-hot-toast";
@ -31,6 +30,7 @@ export default function NotFound({
}); });
setWrongNetworkToastId(toastId); setWrongNetworkToastId(toastId);
setNetworkId(newChainId); setNetworkId(newChainId);
switchChain({ chainId: newChainId }); switchChain({ chainId: newChainId });
} }
@ -92,7 +92,7 @@ export default function NotFound({
alignItems="center" alignItems="center"
gap="15px" 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> <Typography fontSize="1.2em">{chain.name}</Typography>
</Box> </Box>
</Button> </Button>

View File

@ -19,6 +19,7 @@ const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [settingsModalOpen, setSettingsModalOpen] = useState(false);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const [upperToken, setUpperToken] = useState(ftsoSymbol); const [upperToken, setUpperToken] = useState(ftsoSymbol);
@ -30,16 +31,28 @@ const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
useEffect(() => { useEffect(() => {
switch (true) { switch (true) {
case (upperToken === ftsoSymbol && bottomToken === stnkSymbol):
setAction("STAKE")
break;
case (upperToken === ftsoSymbol && bottomToken === ghstSymbol): case (upperToken === ftsoSymbol && bottomToken === ghstSymbol):
setAction("STAKE") setAction("STAKE")
break; break;
case (upperToken === stnkSymbol && bottomToken === ftsoSymbol):
setAction("UNSTAKE")
break;
case (upperToken === ghstSymbol && bottomToken === ftsoSymbol): case (upperToken === ghstSymbol && bottomToken === ftsoSymbol):
setAction("UNSTAKE") setAction("UNSTAKE")
break; break;
case (upperToken === stnkSymbol && bottomToken === ghstSymbol):
setAction("WRAP")
break;
case (upperToken === ghstSymbol && bottomToken === stnkSymbol):
setAction("UNWRAP")
break;
default: default:
setAction("STAKE") setAction("STAKE")
} }
}, [ftsoSymbol, ghstSymbol, upperToken, bottomToken]) }, [upperToken, bottomToken])
return ( return (
<Modal <Modal

View File

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction, useState, useEffect } from "react"; 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 ReactGA from "react-ga4";
import Paper from "../../components/Paper/Paper"; import Paper from "../../components/Paper/Paper";

View File

@ -21,15 +21,14 @@ import InfoTooltip from "../../../components/Tooltip/InfoTooltip";
import Paper from "../../../components/Paper/Paper"; import Paper from "../../../components/Paper/Paper";
import Token from "../../../components/Token/Token"; import Token from "../../../components/Token/Token";
import { PrimaryButton, SecondaryButton } from "../../../components/Button"; import { PrimaryButton, SecondaryButton } from "../../../components/Button";
import { Tab, Tabs } from "../../../components/Tabs/Tabs";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../../helpers/timeUtil"; import { prettifySecondsInDays } from "../../../helpers/timeUtil";
import { formatNumber, formatCurrency } from "../../../helpers"; import { formatNumber } from "../../../helpers";
import { STAKING_ADDRESSES } from "../../../constants/addresses"; import { STAKING_ADDRESSES } from "../../../constants/addresses";
import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking"; import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking";
import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens"; import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens";
import { useGhstPrice, useStnkPrice } from "../../../hooks/prices";
import { isNetworkLegacy } from "../../../constants";
import ClaimConfirmationModal from "./ClaimConfirmationModal"; import ClaimConfirmationModal from "./ClaimConfirmationModal";
@ -53,10 +52,10 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
const isSmallScreen = useMediaQuery("(max-width: 745px)"); const isSmallScreen = useMediaQuery("(max-width: 745px)");
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false); const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const [isPayoutGhst, _] = useState(true); const [isPayoutGhst, setIsPayoutGhst] = useState(localStorage.getItem("stake-isGhstPayout")
? localStorage.getItem("stake-isGhstPayout")
const ghstPrice = useGhstPrice(chainId); : false
const stnkPrice = useStnkPrice(chainId); );
const { warmupInfo: claim, refetch: claimRefetch } = useWarmupInfo(chainId, address); const { warmupInfo: claim, refetch: claimRefetch } = useWarmupInfo(chainId, address);
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId); const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
@ -66,14 +65,6 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK"); const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const claimableBalance = useMemo(() => {
if (isNetworkLegacy(chainId)) {
return isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares;
}
const toClaim = new DecimalBigNumber(claim.shares, 18);
return isPayoutGhst ? toClaim : toClaim.mul(currentIndex);
}, [chainId, claim, currentIndex, balanceForShares]);
const closeConfirmationModal = () => { const closeConfirmationModal = () => {
setConfirmationModalOpen(false); setConfirmationModalOpen(false);
claimRefetch(); claimRefetch();
@ -82,6 +73,11 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
if (claim.shares === 0n) return <></>; if (claim.shares === 0n) return <></>;
const setIsPayoutGhstInner = (value) => {
setIsPayoutGhst(value);
localStorage.setitem("bond-isGhstPayout", value);
}
const warmupTooltip = `Your claim earns rebases during warmup. You can emergency withdraw, but this forfeits the rebases`; const warmupTooltip = `Your claim earns rebases during warmup. You can emergency withdraw, but this forfeits the rebases`;
return ( return (
@ -91,7 +87,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
onClose={() => closeConfirmationModal()} onClose={() => closeConfirmationModal()}
chainid={chainId} chainid={chainId}
receiver={address} receiver={address}
receiveAmount={claim.expiry > epoch.number ? claim.deposit : claimableBalance} receiveAmount={claim.expiry > epoch.number ? claim.deposit : isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
outputToken={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol} outputToken={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol}
stnkSymbol={stnkSymbol} stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol} ghstSymbol={ghstSymbol}
@ -110,6 +106,18 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
flexDirection={isSmallScreen ? "column" : "row"} flexDirection={isSmallScreen ? "column" : "row"}
> >
<Typography variant="h6">Your active {isPayoutGhst ? ghstSymbol : stnkSymbol} claim</Typography> <Typography variant="h6">Your active {isPayoutGhst ? ghstSymbol : stnkSymbol} claim</Typography>
<Tabs
centered
textColor="primary"
indicatorColor="primary"
value={isPayoutGhst ? 1 : 0}
aria-label="Claim token tabs"
onChange={(_, view) => setIsPayoutGhstInner(view === 1)}
TabIndicatorProps={{ style: { display: "none" } }}
>
<Tab aria-label="payout-stnk-button" label={stnkSymbol} style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-ghst-button" label={ghstSymbol} style={{ fontSize: "1rem" }} />
</Tabs>
</Box> </Box>
} }
tooltip={warmupTooltip} tooltip={warmupTooltip}
@ -118,35 +126,33 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
{isSmallScreen ? ( {isSmallScreen ? (
<MobileClaimInfo <MobileClaimInfo
setConfirmationModalOpen={setConfirmationModalOpen} setConfirmationModalOpen={setConfirmationModalOpen}
prepareBalance={claimableBalance} prepareBalance={isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
isPayoutGhst={isPayoutGhst} isPayoutGhst={isPayoutGhst}
claim={claim} claim={claim}
epoch={epoch} epoch={epoch}
isClaimable={claim.expiry > epoch.number} isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol} stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol} ghstSymbol={ghstSymbol}
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
/> />
) : ( ) : (
<Table> <Table>
<StyledTableHeader className={classes.stakePoolHeaderText}> <StyledTableHeader className={classes.stakePoolHeaderText}>
<TableRow> <TableRow>
<TableCell style={{ width: "200px", padding: "8px 0" }}>Asset</TableCell> <TableCell style={{ width: "200px", padding: "8px 0" }}>Asset</TableCell>
<TableCell style={{ width: "200px", padding: "8px 0" }}>Staked Amount</TableCell> <TableCell style={{ width: "200px", padding: "8px 0" }}>Amount</TableCell>
<TableCell style={{ width: "150px", padding: "8px 0" }}>Claimable In</TableCell> <TableCell style={{ width: "150px", padding: "8px 0" }}>Claimable In</TableCell>
<TableCell></TableCell> <TableCell></TableCell>
</TableRow> </TableRow>
</StyledTableHeader> </StyledTableHeader>
<ClaimInfo <ClaimInfo
setConfirmationModalOpen={setConfirmationModalOpen} setConfirmationModalOpen={setConfirmationModalOpen}
prepareBalance={claimableBalance} prepareBalance={isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
isPayoutGhst={isPayoutGhst} isPayoutGhst={isPayoutGhst}
claim={claim} claim={claim}
epoch={epoch} epoch={epoch}
isClaimable={claim.expiry > epoch.number} isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol} stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol} ghstSymbol={ghstSymbol}
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
/> />
</Table> </Table>
@ -156,17 +162,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
); );
}; };
const ClaimInfo = ({ const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isClaimable, isPayoutGhst, stnkSymbol, ghstSymbol }) => {
setConfirmationModalOpen,
prepareBalance,
claim,
epoch,
isClaimable,
isPayoutGhst,
stnkSymbol,
ghstSymbol,
tokenPrice
}) => {
return ( return (
<TableBody> <TableBody>
<TableRow> <TableRow>
@ -180,10 +176,7 @@ const ClaimInfo = ({
</TableCell> </TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}> <TableCell style={{ padding: "8px 8px 8px 0" }}>
<Typography gutterBottom={false} style={{ lineHeight: 1.4 }}> <Typography gutterBottom={false} style={{ lineHeight: 1.4 }}>
{formatCurrency(prepareBalance, 5, isPayoutGhst ? ghstSymbol : stnkSymbol)} {`${formatNumber(prepareBalance, 5)} ${isPayoutGhst ? ghstSymbol : stnkSymbol}`}
</Typography>
<Typography variant="body2" gutterBottom={false} style={{ lineHeight: 1.4 }}>
{formatCurrency(prepareBalance * tokenPrice, 2)}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}> <TableCell style={{ padding: "8px 8px 8px 0" }}>
@ -200,17 +193,7 @@ const ClaimInfo = ({
); );
}; };
const MobileClaimInfo = ({ const MobileClaimInfo = ({ setConfirmationModalOpen, prepareBalance, epoch, claim, isPayoutGhst, isClaimable, ghstSymbol, stnkSymbol }) => {
setConfirmationModalOpen,
prepareBalance,
epoch,
claim,
isPayoutGhst,
isClaimable,
ghstSymbol,
stnkSymbol,
tokenPrice,
}) => {
return ( return (
<Box mt="10px"> <Box mt="10px">
<Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}> <Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}>
@ -221,15 +204,10 @@ const MobileClaimInfo = ({
</Box> </Box>
<DataRow <DataRow
title="Staked Amount" title="Amount"
balance={formatNumber(prepareBalance, 7)} balance={formatNumber(prepareBalance, 7)}
/> />
<DataRow
title="Price Estimation"
balance={formatCurrency(prepareBalance * tokenPrice, 2)}
/>
<DataRow <DataRow
title="Claimed in" title="Claimed in"
balance={prettifySecondsInDays(epoch.length * (claim.expiry - epoch.number))} balance={prettifySecondsInDays(epoch.length * (claim.expiry - epoch.number))}

View File

@ -9,40 +9,41 @@ import {
Box, Box,
useMediaQuery, useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { useNavigate, useParams, createSearchParams } from "react-router-dom"; import { useNavigate, createSearchParams } from "react-router-dom";
import Paper from "../../../components/Paper/Paper"; import Paper from "../../../components/Paper/Paper";
import { SecondaryButton } from "../../../components/Button"; import { SecondaryButton } from "../../../components/Button";
import TokenStack from "../../../components/TokenStack/TokenStack"; import TokenStack from "../../../components/TokenStack/TokenStack";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { formatCurrency } from "../../../helpers"; import { formatCurrency } from "../../../helpers";
import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { useLpValuation } from "../../../hooks/treasury"; import { useLpValuation } from "../../../hooks/treasury";
import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens"; import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens";
import { EMPTY_ADDRESS, FTSO_ADDRESSES } from "../../../constants/addresses"; import {
DAI_ADDRESSES,
FTSO_ADDRESSES,
} from "../../../constants/addresses";
const FarmPools = ({ chainId }) => { const FarmPools = ({ chainId }) => {
const isSmallScreen = useMediaQuery("(max-width: 775px)"); const isSmallScreen = useMediaQuery("(max-width: 775px)");
const { network } = useParams();
const { totalSupply: reserveFtsoUniTotalSupply } = useTotalSupply(chainId, "RESERVE_FTSO"); const { totalSupply: daiFtsoUniTotalSupply } = useTotalSupply(chainId, "GDAI_FTSO");
const reserveFtsoUniValuation = useLpValuation(chainId, "RESERVE_FTSO", reserveFtsoUniTotalSupply._value); 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 { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const pools = [ const pools = [
{ {
icons: ["FTSO", tokenNameConverter(chainId, reserveSymbol)], icons: ["FTSO", "GDAI"],
name: `${ftsoSymbol}-${reserveSymbol}`, name: `${ftsoSymbol}-${daiSymbol}`,
dex: "Uniswap V2", dex: "Uniswap V2",
url: `/${network}/dex/uniswap`, url: "/dex/uniswap",
tvl: reserveFtsoUniValuation, tvl: daiFtsoUniValuation,
params: createSearchParams({ params: createSearchParams({
pool: "true", pool: "true",
from: `${EMPTY_ADDRESS}`, from: `${DAI_ADDRESSES[chainId]}`,
to: `${FTSO_ADDRESSES[chainId]}`, to: `${FTSO_ADDRESSES[chainId]}`,
}) })
}, },
@ -61,17 +62,7 @@ const FarmPools = ({ chainId }) => {
} }
return ( return (
<Paper <Paper headerText="Farm Pools" fullWidth enableBackground>
fullWidth
enableBackground
headerContent={
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
<Typography variant="h6">
Farm Pools
</Typography>
</Box>
}
>
<TableContainer> <TableContainer>
<Table aria-label="Farm pools" style={{ tableLayout: "fixed" }}> <Table aria-label="Farm pools" style={{ tableLayout: "fixed" }}>
<TableHead> <TableHead>

View File

@ -52,7 +52,7 @@ export const TotalDeposit = props => {
const _props = { const _props = {
...props, ...props,
label: "Total Deposit", 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)}`; if (deposit) _props.metric = `${formatCurrency(deposit, 2)}`;

View File

@ -1,7 +1,7 @@
import { Avatar, Box, Link } from "@mui/material"; import { Avatar, Box, Link } from "@mui/material";
import { styled } from "@mui/material/styles"; import { styled } from "@mui/material/styles";
import React, { useEffect, useState, useCallback } from "react"; import React, { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
@ -23,7 +23,6 @@ import {
STAKING_ADDRESSES, STAKING_ADDRESSES,
} from "../../../constants/addresses"; } from "../../../constants/addresses";
import { useCurrentIndex } from "../../../hooks/staking"; import { useCurrentIndex } from "../../../hooks/staking";
import { useLocalStorage } from "../../../hooks/localstorage";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens"; import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { formatNumber } from "../../../helpers"; import { formatNumber } from "../../../helpers";
@ -83,11 +82,20 @@ export const StakeInputArea = ({
const [bottomTokenModalOpen, setBottomTokenModalOpen] = useState(false); const [bottomTokenModalOpen, setBottomTokenModalOpen] = useState(false);
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false); const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const { getStorageValue, setStorageValue } = useLocalStorage(); const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("stake-decimals")
? localStorage.getItem("stake-decimals")
: "5"
);
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "stake-decimals", "5")); const [isClaim, setIsClaim] = useState(localStorage.getItem("stake-isClaim")
const [isClaim, setIsClaim] = useState(() => getStorageValue(chainId, address, "stake-isClaim", true)); ? localStorage.getItem("stake-isClaim") === 'true'
const [isTrigger, setIsTrigger] = useState(() => getStorageValue(chainId, address, "stake-isTrigger", true)); : true
);
const [isTrigger, setIsTrigger] = useState(localStorage.getItem("stake-isTrigger")
? localStorage.getItem("stake-isTrigger") === 'true'
: true
);
const [amount, setAmount] = useState(""); const [amount, setAmount] = useState("");
const [receiveAmount, setReceiveAmount] = useState(""); const [receiveAmount, setReceiveAmount] = useState("");
@ -102,22 +110,23 @@ export const StakeInputArea = ({
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK"); const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const setIsClaimInner = useCallback((value) => { const setIsClaimInner = (value) => {
setIsClaim(value); setIsClaim(value);
setStorageValue(chainId, address, "stake-isClaim", value); localStorage.setItem("stake-isClaim", value);
}, [chainId, address]);
const setIsTriggerInner = useCallback((value) => { }
const setIsTriggerInner = (value) => {
setIsTrigger(value); setIsTrigger(value);
setStorageValue(chainId, address, "stake-isTrigger", value); localStorage.setItem("stake-isTrigger", value);
}, [chainId, address]); }
const setFormatDecimalsInner = useCallback((value) => { const setFormatDecimalsInner = (value) => {
if (Number(value) <= 17) { if (Number(value) <= 17) {
setFormatDecimals(value); setFormatDecimals(value);
setStorageValue(chainId, address, "stake-decimals", value); localStorage.setItem("staking-decimals", value);
}
} }
}, [chainId, address]);
useEffect(() => { useEffect(() => {
const innerBalance = upperToken === ghstSymbol ? const innerBalance = upperToken === ghstSymbol ?
@ -233,6 +242,36 @@ export const StakeInputArea = ({
/> />
</Box> </Box>
{upperTokenModalOpen && (
<TokenModal
open={upperTokenModalOpen}
handleSelect={data => handleTokenModalInput(data.token, data.isUpper)}
handleClose={() => setUpperTokenModalOpen(false)}
ftsoBalance={formatNumber(ftsoBalance, formatDecimals)}
stnkBalance={formatNumber(stnkBalance, formatDecimals)}
ghstBalance={formatNumber(ghstBalance, formatDecimals)}
isUpper={true}
ftsoSymbol={ftsoSymbol}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
)}
{bottomTokenModalOpen && (
<TokenModal
open={bottomTokenModalOpen}
handleSelect={data => handleTokenModalInput(data.token, data.isUpper)}
handleClose={() => setBottomTokenModalOpen(false)}
ftsoBalance={formatNumber(ftsoBalance, formatDecimals)}
stnkBalance={formatNumber(stnkBalance, formatDecimals)}
ghstBalance={formatNumber(ghstBalance, formatDecimals)}
tokenToExclude={upperToken}
isUpper={false}
ftsoSymbol={ftsoSymbol}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
)}
<Box> <Box>
<PrimaryButton <PrimaryButton
fullWidth fullWidth

View File

@ -84,7 +84,7 @@ export const FatsoBacking = props => {
const _props = { const _props = {
...props, ...props,
label: `Backing per ${props.ftsoSymbol}`, 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); if (backing) _props.metric = formatCurrency(backing, 2);

View File

@ -1,6 +1,6 @@
import { Grid, Box, Typography, useTheme } from "@mui/material"; import { Grid, Box, Typography, useTheme } from "@mui/material";
import { useAccount, useConfig, useBalance as useBalanceNative } from "wagmi"; import { useAccount } from "wagmi";
import { useNavigate, useParams, createSearchParams } from "react-router-dom"; import { useNavigate, createSearchParams } from "react-router-dom";
import Token from "../../../components/Token/Token"; import Token from "../../../components/Token/Token";
import { SecondaryButton } from "../../../components/Button"; import { SecondaryButton } from "../../../components/Button";
@ -10,13 +10,10 @@ import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens"; import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { import {
useFtsoPrice, useFtsoPrice,
useStnkPrice,
useGhstPrice, useGhstPrice,
useReservePrice, useDaiPrice,
useNativePrice,
} from "../../../hooks/prices"; } from "../../../hooks/prices";
import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { EMPTY_ADDRESS, WETH_ADDRESSES } from "../../../constants/addresses";
const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams, balance, price, description }) => { const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams, balance, price, description }) => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -58,13 +55,10 @@ const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams,
<Box display="flex" justifyContent="center" width="100%"> <Box display="flex" justifyContent="center" width="100%">
<SecondaryButton <SecondaryButton
onClick={() => tokenUrlParams onClick={() => navigate({
? navigate({
pathname: tokenUrl, pathname: tokenUrl,
search: tokenUrlParams.toString() search: tokenUrlParams.toString()
}) })}
: window.open(tokenUrl, '_blank')
}
fullWidth fullWidth
> >
Get {tokenName} Get {tokenName}
@ -78,35 +72,31 @@ const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams,
const TokenInfo = ({ chainId, isMobileScreen }) => { const TokenInfo = ({ chainId, isMobileScreen }) => {
const theme = useTheme(); const theme = useTheme();
const { network } = useParams();
const { address } = useAccount(); 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 ftsoPrice = useFtsoPrice(chainId);
const stnkPrice = useStnkPrice(chainId);
const ghstPrice = useGhstPrice(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: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { data: nativeBalance } = useBalanceNative({ address });
const { balance: ftsoBalance, contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", 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: 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 ( return (
<Grid container spacing={0} justifyContent={"center"}> <Grid container spacing={0} justifyContent={"center"}>
<Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px"> <Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px">
<TokenTab <TokenTab
isMobileScreen={isMobileScreen} isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`} tokenUrl="/dex/uniswap"
tokenUrlParams={createSearchParams({ tokenUrlParams={createSearchParams({
from: `${reserveAddress}`, from: `${daiAddress}`,
to: `${ftsoAddress}`, to: `${ftsoAddress}`,
})} })}
theme={theme} theme={theme}
@ -117,38 +107,39 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
/> />
<TokenTab <TokenTab
isMobileScreen={isMobileScreen} isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`} tokenUrl="/dex/uniswap"
tokenUrlParams={createSearchParams({ tokenUrlParams={createSearchParams({
from: `${reserveAddress}`, from: `${daiAddress}`,
to: `${stnkAddress}`,
})}
theme={theme}
tokenName={stnkSymbol}
balance={stnkBalance}
price={stnkPrice}
description={`${stnkSymbol} is a receipt for staked ${ftsoSymbol}, growing with staking rewards. When unstaked, its burned for ${ftsoSymbol} at a 1:1 ratio.`}
/>
<TokenTab
isMobileScreen={isMobileScreen}
tokenUrl="/dex/uniswap"
tokenUrlParams={createSearchParams({
from: `${daiAddress}`,
to: `${ghstAddress}`, to: `${ghstAddress}`,
})} })}
theme={theme} theme={theme}
tokenName={ghstSymbol} tokenName={ghstSymbol}
balance={ghstBalance} balance={ghstBalance}
price={ghstPrice} 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 <TokenTab
isMobileScreen={isMobileScreen} isMobileScreen={isMobileScreen}
tokenUrl={"https://ghostchain.io/faucet/"} tokenUrl="/faucet"
tokenUrlParams=""
theme={theme} theme={theme}
tokenName={nativeSymbol} tokenName={daiSymbol}
balance={new DecimalBigNumber(nativeBalance?.value ?? 0n, 18)} balance={daiBalance}
price={reservePrice} price={daiPrice}
description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`} description={`${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${daiSymbol} being the primary and most liquid asset.`}
/>
<TokenTab
isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`}
tokenUrlParams={createSearchParams({
from: `${EMPTY_ADDRESS}`,
to: `${WETH_ADDRESSES[chainId]}`,
})}
theme={theme}
tokenName={reserveSymbol}
balance={reserveBalance}
price={reservePrice}
description={`${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${reserveSymbol} being the primary and most liquid asset.`}
/> />
</Box> </Box>
</Grid> </Grid>

View File

@ -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;

View File

@ -41,27 +41,3 @@ export const formatNumber = (number, precision = 0) => {
export const sortBondsByDiscount = (bonds) => { export const sortBondsByDiscount = (bonds) => {
return Array.from(bonds).filter((bond) => !bond.isSoldOut).sort((a, b) => (a.discount.gt(b.discount) ? -1 : 1)); 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`;
}
}
export const bigIntSqrt = (n) => {
if (n < 0n) return 0n;
if (n < 2n) return n;
let x = n / 2n + 1n;
let y = (x + n / x) / 2n;
while (y < x) {
x = y;
y = (x + n / x) / 2n;
}
return x;
}

View File

@ -1,12 +0,0 @@
export const tokenNameConverter = (chainId, name, address) => {
if (name?.toUpperCase() === "WETH") {
switch (chainId) {
case 63:
name = "wmETC"
break;
default:
name = "wETH";
}
}
return name;
}

View File

@ -2,7 +2,6 @@ import { useReadContract, useReadContracts } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core"; import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { isNetworkLegacyType } from "../../constants";
import { config } from "../../config"; import { config } from "../../config";
import { import {
@ -14,18 +13,14 @@ import { abi as BondAbi } from "../../abi/GhostBondDepository.json";
import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json"; import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json"; import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json";
import { useReservePrice, useFtsoPrice } from "../prices"; import { useFtsoPrice } from "../prices";
import { useOrinalCoefficient } from "../treasury";
import { useTokenSymbol, useTokenSymbols } from "../tokens"; import { useTokenSymbol, useTokenSymbols } from "../tokens";
import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers"; import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { shorten } from "../../helpers"; import { shorten } from "../../helpers";
import { tokenNameConverter } from "../../helpers/tokenConverter";
export const useLiveBonds = (chainId, chainName) => { export const useLiveBonds = (chainId) => {
const ftsoPrice = useFtsoPrice(chainId); const baseTokenPerUsd = useFtsoPrice(chainId);
const baseTokenPerUsd = useReservePrice(chainId);
const originalCoefficient = useOrinalCoefficient(chainId);
const { data: liveIndexesRaw, refetch } = useReadContract({ const { data: liveIndexesRaw, refetch } = useReadContract({
abi: BondAbi, abi: BondAbi,
@ -131,11 +126,10 @@ export const useLiveBonds = (chainId, chainName) => {
const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : ""; const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : "";
const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9); const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9);
const priceInUsd = quoteTokenPerBaseToken.mul(baseTokenPerUsd).mul(quoteTokenPerUsd).mul(markdown).div(originalCoefficient); const priceInUsd = quoteTokenPerUsd.mul(quoteTokenPerBaseToken).mul(markdown);
const discount = baseTokenPerUsd._value > 0n
const discount = ftsoPrice._value > 0n ? baseTokenPerUsd.sub(priceInUsd).div(baseTokenPerUsd)
? ftsoPrice.sub(priceInUsd).div(ftsoPrice) : new DecimalBigNumber("0", baseTokenPerUsd._decimals);
: new DecimalBigNumber("0", ftsoPrice._decimals);
const capacityInBaseToken = capacityInQuote const capacityInBaseToken = capacityInQuote
? new DecimalBigNumber(marketPrice > 0n ? marketCapacity / marketPrice : 0n, 9) ? new DecimalBigNumber(marketPrice > 0n ? marketCapacity / marketPrice : 0n, 9)
@ -151,20 +145,21 @@ export const useLiveBonds = (chainId, chainName) => {
); );
const zero = new DecimalBigNumber(0n, 0); const zero = new DecimalBigNumber(0n, 0);
const bondName = `${baseTokenSymbol}/${quoteTokenSymbol}`;
return { return {
id, id,
discount, discount,
displayName: getBondNameDisplayName(chainId, quoteTokenAddress, baseTokenSymbol), displayName: getBondNameDisplayName(chainId, bondName, quoteTokenAddress),
baseToken: { baseToken: {
name: baseTokenSymbol, name: baseTokenSymbol,
purchaseUrl: getTokenPurchaseLink(chainId, "", chainName), purchaseUrl: getTokenPurchaseLink(chainId, ""),
icons: ["FTSO"], icons: ["FTSO"],
tokenAddress: getTokenAddress(chainId, "FTSO") tokenAddress: getTokenAddress(chainId, "FTSO")
}, },
quoteToken: { quoteToken: {
name: tokenNameConverter(chainId, quoteTokenSymbol), name: quoteTokenSymbol,
purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress, chainName), purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress),
icons: getTokenIcons(chainId, quoteTokenAddress), icons: getTokenIcons(chainId, quoteTokenAddress),
decimals: quoteTokenDecimals, decimals: quoteTokenDecimals,
quoteTokenAddress, quoteTokenAddress,
@ -172,11 +167,11 @@ export const useLiveBonds = (chainId, chainName) => {
duration: terms?.at(index).result?.at(3) ? terms.at(index).result.at(3) : 0, duration: terms?.at(index).result?.at(3) ? terms.at(index).result.at(3) : 0,
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0, vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
isFixedTerm: terms?.at(index).result?.at(0) ? terms.at(index).result.at(0) : true, isFixedTerm: terms?.at(index).result?.at(0) ? terms.at(index).result.at(0) : true,
isSoldOut: capacityInBaseToken.eq(zero), isSoldOut: capacityInBaseToken.eq(zero) || capacityInBaseToken.eq(zero),
price: { price: {
inUsd: priceInUsd, inUsd: priceInUsd,
inBaseToken: quoteTokenPerBaseToken, inBaseToken: quoteTokenPerBaseToken,
marketPriceInUsd: ftsoPrice marketPriceInUsd: baseTokenPerUsd
}, },
capacity: { capacity: {
inBaseToken: capacityInBaseToken, inBaseToken: capacityInBaseToken,
@ -250,7 +245,7 @@ export const useNotes = (chainId, address) => {
return { return {
id, id,
quoteToken: { quoteToken: {
name: tokenNameConverter(chainId, quoteTokenSymbol), name: quoteTokenSymbol,
icons: getTokenIcons(chainId, quoteTokenAddress), icons: getTokenIcons(chainId, quoteTokenAddress),
}, },
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0, vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
@ -266,7 +261,7 @@ export const useNotes = (chainId, address) => {
return { notes, refetch }; return { notes, refetch };
} }
export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, referral, isNative }) => { export const purchaseBond = async (chainId, bondId, amount, maxPrice, user, sender, referral) => {
const args = [ const args = [
bondId, bondId,
amount, amount,
@ -284,12 +279,11 @@ export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, se
"deposit", "deposit",
args, args,
sender, sender,
messages, messages
isNative ? amount : undefined
); );
} }
export const redeem = async ({ chainId, user, isGhst, indexes }) => { export const redeem = async (chainId, user, isGhst, indexes) => {
const args = [ const args = [
user, user,
isGhst, isGhst,
@ -314,8 +308,7 @@ const executeOnChainTransaction = async (
functionName, functionName,
args, args,
account, account,
messages, messages
value
) => { ) => {
try { try {
const { request } = await simulateContract(config, { const { request } = await simulateContract(config, {
@ -324,9 +317,7 @@ const executeOnChainTransaction = async (
functionName, functionName,
args, args,
account, account,
chainId, chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
value: value,
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);

View File

@ -8,24 +8,15 @@ import { useUnstableProvider } from "./UnstableProvider"
const MetadataProvider = createContext(null) const MetadataProvider = createContext(null)
export const useMetadata = () => useContext(MetadataProvider) export const useMetadata = () => useContext(MetadataProvider)
const CACHE_VERSION = "v2"
export const MetadataProviderProvider = ({ children }) => { export const MetadataProviderProvider = ({ children }) => {
const { client, chainId } = useUnstableProvider() const { client, chainId } = useUnstableProvider()
const { data: metadata } = useSWR( const { data: metadata } = useSWR(
client && chainId ? ["metadata", client, chainId] : null, client && chainId ? ["metadata", client, chainId] : null,
async ([_, client]) => { async ([_, client]) => {
const storageKey = `metadata-${chainId}-${CACHE_VERSION}` const storageKey = `metadata-${chainId}`
const storedMetadata = sessionStorage.getItem(storageKey) const storedMetadata = sessionStorage.getItem(storageKey)
if (storedMetadata) return unifyMetadata(decAnyMetadata(storedMetadata)) if (storedMetadata) return unifyMetadata(decAnyMetadata(storedMetadata))
Object.keys(sessionStorage).forEach(key => {
if (key.startsWith("metadata-")) {
sessionStorage.removeItem(key);
}
});
const metadata = await new Promise((resolve, reject) => const metadata = await new Promise((resolve, reject) =>
client._request("state_getMetadata", [], { client._request("state_getMetadata", [], {
onSuccess: resolve, onSuccess: resolve,

View File

@ -4,7 +4,7 @@ import { createClient } from "@polkadot-api/substrate-client"
import { getObservableClient } from "@polkadot-api/observable-client" import { getObservableClient } from "@polkadot-api/observable-client"
import useSWR from "swr" import useSWR from "swr"
const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3" const DEFAULT_CHAIN_ID = "0xa217f4ee58a944470e9633ca5bd6d28a428ed64cd9b6f3e413565f359f89af90"
const UnstableProvider = createContext(null) const UnstableProvider = createContext(null)
export const useUnstableProvider = () => useContext(UnstableProvider) export const useUnstableProvider = () => useContext(UnstableProvider)

View File

@ -6,11 +6,4 @@ export * from "./useClapsInSession";
export * from "./useApplauseThreshold"; export * from "./useApplauseThreshold";
export * from "./useReceivedClaps"; export * from "./useReceivedClaps";
export * from "./useAuthorities"; export * from "./useAuthorities";
export * from "./useValidators"; export * from "./useApplausesForTransaction";
export * from "./useDisabledValidators";
export * from "./useBlockCommitments";
export * from "./useApplauseDetails";
export * from "./useBabeSlots";
export * from "./useErasTotalStaked";
export * from "./useLatestBlockNumber";
export * from "./useEraIndex";

View File

@ -6,41 +6,42 @@ import { fromHex } from "@polkadot-api/utils";
import { useUnstableProvider } from "./UnstableProvider" import { useUnstableProvider } from "./UnstableProvider"
import { useMetadata } from "./MetadataProvider" import { useMetadata } from "./MetadataProvider"
export const useApplauseDetails = ({ currentSession, argsHash }) => { export const useApplausesForTransaction = ({ currentSession, txHash, argsHash }) => {
const { chainHead$, chainId } = useUnstableProvider() const { chainHead$, chainId } = useUnstableProvider()
const metadata = useMetadata() const metadata = useMetadata()
const { data: applauseDetails } = useSWRSubscription( const { data: applausesForTransaction } = useSWRSubscription(
chainHead$ && argsHash && currentSession && chainId && metadata chainHead$ && txHash && argsHash && currentSession && chainId && metadata
? ["applauseDetails", chainHead$, argsHash, currentSession, chainId, metadata] ? ["applausesForTransaction", chainHead$, txHash, argsHash, currentSession, chainId, metadata]
: null, : null,
([_, chainHead$, argsHash, currentSession, chainId, metadata], { next }) => { ([_, chainHead$, txHash, argsHash, currentSession, chainId, metadata], { next }) => {
const { finalized$, storage$ } = chainHead$ const { finalized$, storage$ } = chainHead$
const subscription = finalized$.pipe( const subscription = finalized$.pipe(
filter(Boolean), filter(Boolean),
mergeMap((blockInfo) => { mergeMap((blockInfo) => {
const builder = getDynamicBuilder(getLookupFn(metadata)) const builder = getDynamicBuilder(getLookupFn(metadata))
const applauseDetails = builder.buildStorage("GhostSlowClaps", "ApplauseDetails") const applausesForTransaction = builder.buildStorage("GhostSlowClaps", "ApplausesForTransaction")
return storage$(blockInfo?.hash, "value", () => return storage$(blockInfo?.hash, "value", () =>
applauseDetails?.keys.enc( applausesForTransaction?.keys.enc(
currentSession, currentSession,
{ asBytes: () => fromHex(txHash) },
{ asBytes: () => fromHex(argsHash) }, { asBytes: () => fromHex(argsHash) },
) )
).pipe( ).pipe(
filter(Boolean), filter(Boolean),
distinct(), distinct(),
map((value) => applauseDetails?.value.dec(value)) map((value) => applausesForTransaction?.value.dec(value))
) )
}), }),
) )
.subscribe({ .subscribe({
next(applauseDetails) { next(applausesForTransaction) {
next(null, applauseDetails) next(null, applausesForTransaction)
}, },
error: next, error: next,
}) })
return () => subscription.unsubscribe() return () => subscription.unsubscribe()
} }
) )
return applauseDetails return applausesForTransaction
} }

View File

@ -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
}

View File

@ -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;
}, []);
}

View File

@ -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 : []
}

View File

@ -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 useEraIndex = () => {
const { chainHead$, chainId } = useUnstableProvider()
const metadata = useMetadata()
const { data: eraIndex } = useSWRSubscription(
chainHead$ && chainId && metadata
? ["eraIndex", 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 eraIndex = builder.buildStorage("Staking", "ActiveEra")
return storage$(blockInfo?.hash, "value", () =>
eraIndex?.keys.enc()
).pipe(
filter(Boolean),
distinct(),
map((value) => eraIndex?.value.dec(value))
)
}),
)
.subscribe({
next(eraIndex) {
next(null, eraIndex)
},
error: next,
})
return () => subscription.unsubscribe()
}
)
return eraIndex;
}

View File

@ -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 = ({ epochIndex }) => {
const { chainHead$, chainId } = useUnstableProvider()
const metadata = useMetadata()
const { data: eraTotalStake } = useSWRSubscription(
chainHead$ && chainId && metadata
? ["eraTotalStake", chainHead$, epochIndex, chainId, metadata]
: null,
([_, chainHead$, epochIndex, 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(epochIndex)
).pipe(
filter(Boolean),
distinct(),
map((value) => eraTotalStake?.value.dec(value))
)
}),
)
.subscribe({
next(eraTotalStake) {
next(null, eraTotalStake)
},
error: next,
})
return () => subscription.unsubscribe()
}
)
return eraTotalStake;
}

View File

@ -1,18 +0,0 @@
import { useState, useEffect } from "react";
import useSWRSubscription from "swr/subscription"
import { useUnstableProvider } from "./UnstableProvider"
export const useLatestBlockNumber = () => {
const { chainHead$ } = useUnstableProvider();
const [blockNumber, setBlockNumber] = useState(null);
useEffect(() => {
if (!chainHead$) return;
const subscription = chainHead$.best$.subscribe((block) => {
setBlockNumber(block.number);
});
return () => subscription.unsubscribe();
}, [chainHead$]);
return blockNumber;
}

View File

@ -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
}

View File

@ -1,654 +0,0 @@
import { useMemo } from "react";
import { useReadContract, useReadContracts } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast";
import { keccak256, stringToBytes } from 'viem'
import { isNetworkLegacyType } from "../../constants";
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 getVoteValue = (forVotes, totalVotes) => {
if (totalVotes == 0n) return 0;
const value = forVotes * 100n / totalVotes;
return Math.floor(Number(value.toString()));
}
export const getVoteTarget = (totalVotes, totalSupply) => {
if (totalSupply == 0n) return 80;
const precision = 10n ** 3n;
const valueRaw = (totalVotes * precision) / totalSupply;
const value = Number(valueRaw) / Number(precision);
const result = (5 - Math.sqrt(1 + 80/9 * (value - 0.1) )) / 4;
return Math.floor(result * 100);
}
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(proposalId => {
return {
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetails",
args: [proposalId],
scopeKey: `proposalDetails-${chainId}-${proposalId}`,
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 = useMemo(() => {
return 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;
});
}, [indexes, searchedIndexes, proposalsDetailsAt]);
const voteValues = useMemo(() => {
return indexes?.map((_, idx) => {
const index = indexes.length - idx - 1;
const againstVotes = proposalVotes?.at(index)?.result?.at(0) ?? 0n
const forVotes = proposalVotes?.at(index)?.result?.at(1) ?? 0n;
const totalVotes = againstVotes + forVotes;
return getVoteValue(forVotes, totalVotes);
}) ?? [];
}, [indexes, proposalVotes]);
const voteTargets = useMemo(() => {
return indexes?.map((_, idx) => {
const index = indexes.length - idx - 1;
const againstVotes = proposalVotes?.at(index)?.result?.at(0) ?? 0n
const forVotes = proposalVotes?.at(index)?.result?.at(1) ?? 0n;
const totalSupply = pastTotalSupplies?.at(index)?.result ?? 0n;
const totalVotes = againstVotes + forVotes;
return getVoteTarget(totalVotes, totalSupply);
}) ?? [];
}, [indexes, proposalVotes, pastTotalSupplies]);
const proposalDetailsPrepared = useMemo(() => {
if (!searchedIndexes) return proposalsDetailsAt;
return proposalDetails?.map((obj, index) => {
if (!obj?.result) return obj;
const proposalId = searchedIndexes.at(index);
return {
...obj,
result: [proposalId, ...obj.result],
};
}) ?? [];
}, [searchedIndexes, proposalsDetailsAt, proposalDetails]);
const proposals = indexes?.map((_, index) => ({
hashes: hashes?.at(index) ?? { short: "", full: "" },
proposer: proposalProposer?.at(index)?.result,
details: proposalDetailsPrepared?.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)),
voteValue: voteValues?.at(index),
voteTarget: voteTargets?.at(index),
}));
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,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
});
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,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
});
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,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
});
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,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
});
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;
}
}

View File

@ -1,20 +1,16 @@
import { import {
RESERVE_ADDRESSES, DAI_ADDRESSES,
FTSO_ADDRESSES, FTSO_ADDRESSES,
STNK_ADDRESSES, STNK_ADDRESSES,
GHST_ADDRESSES, GHST_ADDRESSES,
FTSO_DAI_LP_ADDRESSES, FTSO_DAI_LP_ADDRESSES,
WETH_ADDRESSES,
} from "../constants/addresses"; } from "../constants/addresses";
import { tokenNameConverter } from "../helpers/tokenConverter";
import { abi as DaiAbi } from "../abi/Reserve.json"; import { abi as DaiAbi } from "../abi/Reserve.json";
import { abi as FatsoAbi } from "../abi/Fatso.json"; import { abi as FatsoAbi } from "../abi/Fatso.json";
import { abi as StinkyAbi } from "../abi/Stinky.json"; import { abi as StinkyAbi } from "../abi/Stinky.json";
import { abi as GhostAbi } from "../abi/Ghost.json"; import { abi as GhostAbi } from "../abi/Ghost.json";
import { abi as Erc20Abi } from "../abi/ERC20.json"; import { abi as Erc20Abi } from "../abi/ERC20.json";
import { abi as WethAbi } from "../abi/WETH9.json";
// TBD: should be extended on new tokens // TBD: should be extended on new tokens
export const getTokenAbi = (name) => { export const getTokenAbi = (name) => {
@ -26,9 +22,6 @@ export const getTokenAbi = (name) => {
case "GDAI": case "GDAI":
abi = DaiAbi; abi = DaiAbi;
break; break;
case "RESERVE":
abi = DaiAbi;
break;
case "FTSO": case "FTSO":
abi = FatsoAbi; abi = FatsoAbi;
break; break;
@ -47,12 +40,6 @@ export const getTokenAbi = (name) => {
case "CSPR": case "CSPR":
abi = GhostAbi; abi = GhostAbi;
break; break;
case "WETH":
abi = WethAbi;
break;
case "WMWETH":
abi = WethAbi;
break;
} }
return abi; return abi;
} }
@ -67,9 +54,6 @@ export const getTokenDecimals = (name) => {
case "GDAI": case "GDAI":
decimals = 18; decimals = 18;
break; break;
case "RESERVE":
decimals = 18;
break;
case "FTSO": case "FTSO":
decimals = 9; decimals = 9;
break; break;
@ -88,12 +72,6 @@ export const getTokenDecimals = (name) => {
case "CSPR": case "CSPR":
decimals = 18; decimals = 18;
break; break;
case "WETH":
decimals = 18;
break;
case "WMWETH":
decimals = 18;
break;
} }
return decimals; return decimals;
} }
@ -103,13 +81,10 @@ export const getTokenAddress = (chainId, name) => {
let address = name; let address = name;
switch (name?.toUpperCase()) { switch (name?.toUpperCase()) {
case "DAI": case "DAI":
address = RESERVE_ADDRESSES[chainId]; address = DAI_ADDRESSES[chainId];
break; break;
case "GDAI": case "GDAI":
address = RESERVE_ADDRESSES[chainId]; address = DAI_ADDRESSES[chainId];
break;
case "RESERVE":
address = RESERVE_ADDRESSES[chainId];
break; break;
case "FTSO": case "FTSO":
address = FTSO_ADDRESSES[chainId]; address = FTSO_ADDRESSES[chainId];
@ -132,33 +107,16 @@ export const getTokenAddress = (chainId, name) => {
case "GDAI_FTSO": case "GDAI_FTSO":
address = FTSO_DAI_LP_ADDRESSES[chainId]; address = FTSO_DAI_LP_ADDRESSES[chainId];
break; 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;
case "ETH":
address = undefined;
break;
case "METC":
address = undefined;
break;
} }
return address; return address;
} }
// TBD: should be extended on new tokens
export const getTokenIcons = (chainId, address) => { export const getTokenIcons = (chainId, address) => {
let icons = [""]; let icons = [""];
switch (address) { switch (address) {
case RESERVE_ADDRESSES[chainId]: case DAI_ADDRESSES[chainId]:
icons = [tokenNameConverter(chainId, "WETH")]; icons = ["GDAI"];
break; break;
case FTSO_ADDRESSES[chainId]: case FTSO_ADDRESSES[chainId]:
icons = ["FTSO"]; icons = ["FTSO"];
@ -170,33 +128,27 @@ export const getTokenIcons = (chainId, address) => {
icons = ["GHST"]; icons = ["GHST"];
break; break;
case FTSO_DAI_LP_ADDRESSES[chainId]: case FTSO_DAI_LP_ADDRESSES[chainId]:
icons = ["FTSO", "WETH"]; icons = ["FTSO", "GDAI"];
break; break;
default:
icons = [""]
} }
return icons; return icons;
} }
export const getBondNameDisplayName = (chainId, tokenAddress, baseTokenSymbol) => { export const getBondNameDisplayName = (chainId, stringValue, tokenAddress) => {
let stringValue = tokenNameConverter(chainId, "WETH")
if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) { if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) {
stringValue = `${baseTokenSymbol}-${stringValue} LP`; stringValue = `LP ${stringValue}`;
} }
return stringValue; return stringValue;
} }
export const getTokenPurchaseLink = (chainId, tokenAddress, chainName) => { export const getTokenPurchaseLink = (chainId, tokenAddress) => {
let purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/dex/uniswap`; let purchaseUrl = "https://app.dao.ghostchain.io/#/dex/uniswap";
switch (tokenAddress) { switch (tokenAddress) {
case RESERVE_ADDRESSES[chainId]: case DAI_ADDRESSES[chainId]:
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/faucet`; purchaseUrl = "https://app.dao.ghostchain.io/#/faucet";
if (chainId == 63) {
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/wrapper`;
}
break; break;
case FTSO_DAI_LP_ADDRESSES[chainId]: 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; break;
} }
return purchaseUrl; return purchaseUrl;

View File

@ -1,27 +0,0 @@
import { createContext, useContext, useState, useEffect } from "react";
const LocalStorageContext = createContext();
export const useLocalStorage = () => useContext(LocalStorageContext);
export const LocalStorageProvider = ({ children }) => {
const getStorageKey = (chainId, address, target) => {
return `${chainId}:${address}:${target}`;
}
const getStorageValue = (chainId, address, target, defaultValue) => {
const key = getStorageKey(chainId, address, target);
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : defaultValue;
}
const setStorageValue = (chainId, address, target, value) => {
const key = getStorageKey(prefix, chainId, address, target);
localStorage.setItem(key, JSON.stringify(value));
}
return (
<LocalStorageContext.Provider value={{ getStorageValue, setStorageValue }}>
{children}
</LocalStorageContext.Provider>
)
}

View File

@ -1,144 +1,26 @@
import { useState, useEffect } from "react";
import { useReadContract } from "wagmi"; import { useReadContract } from "wagmi";
import { useCurrentIndex, useGhostedSupply } from "../staking"; import { useCurrentIndex, useGhostedSupply } from "../staking";
import { useUniswapV2PairReserves } from "../uniswapv2"; import { useUniswapV2PairReserves } from "../uniswapv2";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { import { FTSO_DAI_LP_ADDRESSES, DAI_ADDRESSES, FTSO_ADDRESSES } from "../../constants/addresses";
FTSO_DAI_LP_ADDRESSES,
RESERVE_ADDRESSES,
FTSO_ADDRESSES,
CEX_TICKERS,
NATIVE_TICKERS,
} from "../../constants/addresses";
const cexPriceGetters = new Map(); export const useDaiPrice = (chainId) => {
const daiPrice = new DecimalBigNumber(1000000000000000000n, 18);
function callWithCacheTTL(fn, ttlMs = 10000) { return daiPrice;
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);
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 useFtsoPrice = (chainId) => { 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 = DAI_ADDRESSES[chainId];
const reserveAddress = RESERVE_ADDRESSES[chainId];
const ftsoAddress = FTSO_ADDRESSES[chainId]; const ftsoAddress = FTSO_ADDRESSES[chainId];
if (!reserveAddress || !ftsoAddress) { if (!reserveAddress || !ftsoAddress) {
return new DecimalBigNumber(0n, 9); return new DecimalBigNumber(0n, 9);
@ -155,9 +37,8 @@ export const useFtsoPrice = (chainId) => {
let price = 0n let price = 0n
if (ftsoReserves > 0n) if (ftsoReserves > 0n)
price = stableReserves / ftsoReserves; price = stableReserves / ftsoReserves;
price = price * reservePrice._value;
return new DecimalBigNumber(price, 27); return new DecimalBigNumber(price, 9);
}; };
export const useStnkPrice = (chainId) => { export const useStnkPrice = (chainId) => {

View File

@ -4,7 +4,6 @@ import toast from "react-hot-toast";
import { config } from "../../config"; import { config } from "../../config";
import { isNetworkLegacyType } from "../../constants";
import { STAKING_ADDRESSES } from "../../constants/addresses"; import { STAKING_ADDRESSES } from "../../constants/addresses";
import { abi as StakingAbi } from "../../abi/GhostStaking.json"; import { abi as StakingAbi } from "../../abi/GhostStaking.json";
@ -20,7 +19,7 @@ export const useCurrentIndex = (chainId) => {
chainId: chainId, chainId: chainId,
}); });
const currentIndexRaw = index ? index : 1000000000n; const currentIndexRaw = index ? index : 0n;
const currentIndex = new DecimalBigNumber(currentIndexRaw, 9); const currentIndex = new DecimalBigNumber(currentIndexRaw, 9);
return { currentIndex, refetch }; return { currentIndex, refetch };
@ -69,16 +68,16 @@ export const useWarmupInfo = (chainId, address) => {
const { data: info, refetch } = useReadContract({ const { data: info, refetch } = useReadContract({
abi: StakingAbi, abi: StakingAbi,
address: STAKING_ADDRESSES[chainId], address: STAKING_ADDRESSES[chainId],
functionName: "warmupInfo", functionName: "getWarmupInfo",
args: [address], args: [address],
scopeKey: `warmupInfo-${address}-${chainId}`, scopeKey: `getWarmupInfo-${address}-${chainId}`,
chainId: chainId, chainId: chainId,
}); });
const warmupInfo = { const warmupInfo = {
deposit: info ? new DecimalBigNumber(info.at(0), 9) : new DecimalBigNumber(0n, 9), deposit: info ? new DecimalBigNumber(info.deposit, 9) : new DecimalBigNumber(0n, 9),
shares: info ? info.at(1) : 0n, shares: info ? info.shares : 0n,
expiry: info ? Number(info.at(2)) : 0, expiry: info ? Number(info.expiry) : 0,
} }
return { warmupInfo, refetch } return { warmupInfo, refetch }
@ -249,8 +248,7 @@ const executeOnChainTransaction = async (
functionName, functionName,
args, args,
account, account,
chainId, chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);

View File

@ -4,46 +4,9 @@ import toast from "react-hot-toast";
import { getTokenAbi, getTokenAddress, getTokenDecimals } from "../helpers"; import { getTokenAbi, getTokenAddress, getTokenDecimals } from "../helpers";
import { isNetworkLegacyType } from "../../constants";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { shorten } from "../../helpers"; import { shorten } from "../../helpers";
import { tokenNameConverter } from "../../helpers/tokenConverter";
import { config } from "../../config"; import { config } from "../../config";
import { WETH_ADDRESSES } from "../../constants/addresses";
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) => { export const useTotalSupply = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name); const contractAddress = getTokenAddress(chainId, name);
@ -61,29 +24,20 @@ export const useTotalSupply = (chainId, name) => {
}; };
export const useBalance = (chainId, name, address) => { export const useBalance = (chainId, name, address) => {
let contractAddress = getTokenAddress(chainId, name); const contractAddress = getTokenAddress(chainId, name);
let isNative = false; const { data, refetch } = useInnerBalance({
let requestObj = {
address, address,
chainId, chainId,
scopeKey: `balance-${contractAddress}-${address}-${chainId}`, scopeKey: `balance-${contractAddress}-${address}-${chainId}`,
}; token: contractAddress,
});
if (contractAddress !== undefined) {
requestObj.token = contractAddress;
} else {
contractAddress = WETH_ADDRESSES[chainId];
isNative = true;
}
const { data, refetch, error } = useInnerBalance(requestObj);
const balancePrepared = data ? data.value : 0n; const balancePrepared = data ? data.value : 0n;
const decimals = data ? data.decimals : getTokenDecimals(name); const decimals = data ? data.decimals : getTokenDecimals(name);
const balance = new DecimalBigNumber(balancePrepared, decimals); const balance = new DecimalBigNumber(balancePrepared, decimals);
return { balance, refetch, contractAddress, isNative }; return { balance, refetch, contractAddress };
} }
export const useAllowance = (chainId, name, owner, spender, decimals) => { export const useAllowance = (chainId, name, owner, spender, decimals) => {
@ -106,7 +60,6 @@ export const useAllowance = (chainId, name, owner, spender, decimals) => {
export const useTokenSymbol = (chainId, name) => { export const useTokenSymbol = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name); const contractAddress = getTokenAddress(chainId, name);
const { data, refetch } = useReadContract({ const { data, refetch } = useReadContract({
abi: getTokenAbi(name), abi: getTokenAbi(name),
address: contractAddress, address: contractAddress,
@ -115,9 +68,7 @@ export const useTokenSymbol = (chainId, name) => {
chainId: chainId, chainId: chainId,
}); });
let symbol = data ? data : ""; const symbol = data ? data : "";
symbol = tokenNameConverter(chainId, symbol);
return { symbol, refetch }; return { symbol, refetch };
} }
@ -211,8 +162,7 @@ export const approveTokens = async (chainId, name, owner, spender, value) => {
functionName: 'approve', functionName: 'approve',
args: [spender, value], args: [spender, value],
account: owner, account: owner,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -238,8 +188,7 @@ export const mintDai = async (chainId, account, value) => {
args: [account], args: [account],
account: account, account: account,
value: value, value: value,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -264,8 +213,7 @@ export const burnDai = async (chainId, account, value) => {
functionName: 'burn', functionName: 'burn',
args: [value], args: [value],
account: account, account: account,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -281,55 +229,3 @@ export const burnDai = async (chainId, account, value) => {
toast.error("Burning gDAI from the faucet failed. Check logs for error detalization.") 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,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
});
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,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
});
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.")
}
}

View File

@ -3,24 +3,9 @@ import { useReadContract } from "wagmi";
import { DAO_TREASURY_ADDRESSES } from "../../constants/addresses"; import { DAO_TREASURY_ADDRESSES } from "../../constants/addresses";
import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json"; import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
import { useReservePrice } from "../prices/index";
import { bigIntSqrt } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { getTokenAddress } from "../helpers"; 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) => { export const useTotalReserves = (chainId) => {
const { data: totalReservesRaw } = useReadContract({ const { data: totalReservesRaw } = useReadContract({
abi: TreasuryAbi, abi: TreasuryAbi,
@ -30,20 +15,14 @@ export const useTotalReserves = (chainId) => {
chainId: chainId, chainId: chainId,
}); });
const original = useOrinalCoefficient(chainId);
const price = useReservePrice(chainId);
const totalReservesPrepared = totalReservesRaw ? totalReservesRaw : 0n; const totalReservesPrepared = totalReservesRaw ? totalReservesRaw : 0n;
const totalReserves = new DecimalBigNumber(totalReservesPrepared, 9); const totalReserves = new DecimalBigNumber(totalReservesPrepared, 9);
return totalReserves.mul(price).div(original); return totalReserves;
}; };
export const useLpValuation = (chainId, pairAddress, pairSupply) => { export const useLpValuation = (chainId, pairAddress, pairSupply) => {
const convertedPairAddress = getTokenAddress(chainId, pairAddress); const convertedPairAddress = getTokenAddress(chainId, pairAddress);
const originalCoefficient = useOrinalCoefficient(chainId);
const reservePrice = useReservePrice(chainId);
const { data: valuationRaw } = useReadContract({ const { data: valuationRaw } = useReadContract({
abi: TreasuryAbi, abi: TreasuryAbi,
address: DAO_TREASURY_ADDRESSES[chainId], address: DAO_TREASURY_ADDRESSES[chainId],
@ -56,18 +35,7 @@ export const useLpValuation = (chainId, pairAddress, pairSupply) => {
chainId, chainId,
}); });
const sqrtReservePrice = reservePrice?._value const valuationPrepared = valuationRaw ? valuationRaw : 0n;
? bigIntSqrt(reservePrice._value)
: 0n;
const sqrtOriginalCoefficient = originalCoefficient?._value
? bigIntSqrt(originalCoefficient._value)
: 1n;
const valuationPrepared = valuationRaw
? valuationRaw * sqrtReservePrice / sqrtOriginalCoefficient
: 0n;
const valuation = new DecimalBigNumber(valuationPrepared, 9); const valuation = new DecimalBigNumber(valuationPrepared, 9);
return valuation; return valuation;

View File

@ -4,12 +4,14 @@ import { abi as UniswapV2Factory } from "../../abi/UniswapV2Factory.json";
import { UNISWAP_V2_FACTORY } from "../../constants/addresses"; import { UNISWAP_V2_FACTORY } from "../../constants/addresses";
export const useUniswapV2Pair = (chainId, factoryAddress, token0, token1) => { export const useUniswapV2Pair = (chainId, factoryAddress, token0, token1) => {
const t0 = token0 > token1 ? token0 : token1;
const t1 = token0 > token1 ? token1 : token0;
const { data, refetch } = useReadContract({ const { data, refetch } = useReadContract({
abi: UniswapV2Factory, abi: UniswapV2Factory,
address: factoryAddress, address: factoryAddress,
functionName: "getPair", functionName: "getPair",
args: [token0, token1], args: [token0, token1],
scopeKey: `getPair-${token0}-${token1}-${chainId}`, scopeKey: `getPair-${t0}-${t1}-${chainId}`,
chainId: chainId, chainId: chainId,
}); });

View File

@ -6,9 +6,7 @@ import { abi as Erc20Abi } from "../../abi/ERC20.json";
import { getTokenAddress } from "../helpers"; import { getTokenAddress } from "../helpers";
export const useUniswapV2PairReserves = (chainId, rawContractAddress) => { export const useUniswapV2PairReserves = (chainId, contractAddress) => {
const contractAddress = getTokenAddress(chainId, rawContractAddress);
const { data: pairReserves, refetch: pairReservesRefetch } = useReadContract({ const { data: pairReserves, refetch: pairReservesRefetch } = useReadContract({
abi: UniswapV2Pair, abi: UniswapV2Pair,
address: contractAddress, address: contractAddress,

View File

@ -1,190 +1,91 @@
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core"; import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { isNetworkLegacyType } from "../../constants"; import { UNISWAP_V2_ROUTER } from "../../constants/addresses";
import { UNISWAP_V2_ROUTER, WETH_ADDRESSES } from "../../constants/addresses";
import { abi as RouterAbi } from "../../abi/UniswapV2Router.json"; import { abi as RouterAbi } from "../../abi/UniswapV2Router.json";
import { getTokenAddress } from "../helpers"; import { getTokenAddress } from "../helpers";
import { config } from "../../config"; import { config } from "../../config";
const swapMessages = { export const swapExactTokensForTokens = async (
chainId,
amountDesired,
amountMin,
pathRaw,
destination,
deadline
) => {
const path = pathRaw.map(tokenName => getTokenAddress(chainId, tokenName));
const args = [
amountDesired,
amountMin,
path,
destination,
deadline
];
const messages = {
replacedMsg: "Swap transaction was replaced. Wait for inclusion please.", replacedMsg: "Swap transaction was replaced. Wait for inclusion please.",
successMsg: "Swap executed successfully! Wait for balances update.", successMsg: "Swap executed successfully! Wait for balances update.",
errorMsg: "Swap tokens failed. Check logs for error detalization.", errorMsg: "Swap tokens failed. Check logs for error detalization.",
}; };
const addMessages = { await executeOnChainTransaction(
chainId,
"swapExactTokensForTokens",
args,
destination,
messages
);
}
export const addLiquidity = async (
chainId,
tokenA,
tokenB,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
to,
deadline,
) => {
const token1 = getTokenAddress(chainId, tokenA);
const token2 = getTokenAddress(chainId, tokenB);
const args = [
token1,
token2,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
to,
deadline
];
const messages = {
replacedMsg: "Add liquidity transaction was replaced. Wait for inclusion please.", replacedMsg: "Add liquidity transaction was replaced. Wait for inclusion please.",
successMsg: "Liquidity added successfully! You should get LP tokens to your wallet.", successMsg: "Liquidity added successfully! You should get LP tokens to your wallet.",
errorMsg: "Adding liquidity failed. Check logs for error detalization.", errorMsg: "Adding liquidity failed. Check logs for error detalization.",
}; };
export const swapExactETHForTokens = async ({ await executeOnChainTransaction(
chainId, chainId,
amountADesired, "addLiquidity",
amountBMin,
tokenNameTop,
tokenNameBottom,
destination,
deadline
}) => {
const args = [
amountBMin,
[WETH_ADDRESSES[chainId], getTokenAddress(chainId, tokenNameBottom)],
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "swapExactETHForTokens",
args, args,
account: destination, to,
messages: swapMessages, messages
value: amountADesired, );
});
} }
export const swapExactTokensForETH = async ({ const executeOnChainTransaction = async (
chainId,
amountADesired,
amountBMin,
tokenNameTop,
tokenNameBottom,
destination,
address,
deadline
}) => {
const args = [
amountADesired,
amountBMin,
[getTokenAddress(chainId, tokenNameTop), WETH_ADDRESSES[chainId]],
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "swapExactTokensForETH",
args,
account: address,
messages: swapMessages,
});
}
export const swapExactTokensForTokens = async ({
chainId,
amountADesired,
amountBMin,
tokenNameTop,
tokenNameBottom,
destination,
address,
deadline
}) => {
const args = [
amountADesired,
amountBMin,
[getTokenAddress(chainId, tokenNameTop), getTokenAddress(chainId, tokenNameBottom)],
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "swapExactTokensForTokens",
args,
account: address,
messages: swapMessages
});
}
export const addLiquidity = async ({
chainId,
tokenNameTop,
tokenNameBottom,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
address,
destination,
deadline,
}) => {
const args = [
getTokenAddress(chainId, tokenNameTop),
getTokenAddress(chainId, tokenNameBottom),
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "addLiquidity",
args,
account: address,
messages: addMessages
});
}
export const addLiquidityETH = async ({
chainId,
tokenNameTop,
tokenNameBottom,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
address,
destination,
deadline,
}) => {
let token = getTokenAddress(chainId, tokenNameTop);
let amountTokenDesired = amountADesired;
let amountETHDesired = amountBDesired;
let amountTokenMin = amountAMin;
let amountETHMin = amountBMin;
if (token === undefined) {
token = getTokenAddress(chainId, tokenNameBottom);
amountTokenDesired = amountBDesired;
amountETHDesired = amountADesired;
amountTokenMin = amountBMin;
amountETHMin = amountAMin;
}
const args = [
token,
amountTokenDesired,
amountTokenMin,
amountETHMin,
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "addLiquidityETH",
args,
account: address,
messages: addMessages,
value: amountETHDesired,
});
}
const executeOnChainTransaction = async ({
chainId, chainId,
functionName, functionName,
args, args,
account, account,
messages, messages
value, ) => {
}) => {
try { try {
const { request } = await simulateContract(config, { const { request } = await simulateContract(config, {
abi: RouterAbi, abi: RouterAbi,
@ -192,9 +93,7 @@ const executeOnChainTransaction = async ({
functionName, functionName,
args, args,
account, account,
chainId, chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
value: value ?? 0n,
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);

View File

@ -9,7 +9,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { WagmiProvider } from "wagmi"; import { WagmiProvider } from "wagmi";
import { config } from "./config"; import { config } from "./config";
import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost" import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
import { LocalStorageProvider } from "./hooks/localstorage";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@ -25,9 +24,7 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
<UnstableProviderProvider> <UnstableProviderProvider>
<MetadataProviderProvider> <MetadataProviderProvider>
<LocalStorageProvider>
<App /> <App />
</LocalStorageProvider>
</MetadataProviderProvider> </MetadataProviderProvider>
</UnstableProviderProvider> </UnstableProviderProvider>
</StyledEngineProvider> </StyledEngineProvider>

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