From 20f2e78ae7aea1a9921cd2500566cbbb76f1f185 Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Fri, 30 Jan 2026 15:13:52 +0300 Subject: [PATCH 01/20] draft implementation of governance page Signed-off-by: Uncle Fatso --- package.json | 3 +- pnpm-lock.yaml | 218 +++++++++++- src/App.jsx | 4 + src/components/Progress/LinearProgressBar.jsx | 39 +++ src/components/Sidebar/NavContent.jsx | 5 +- src/containers/Bond/Bonds.jsx | 4 +- src/containers/Governance/Governance.jsx | 92 +++++ src/containers/Governance/ProposalDetails.jsx | 244 ++++++++++++++ .../components/GovernanceInfoText.jsx | 23 ++ .../Governance/components/Metric.jsx | 53 +++ .../components/ProposalDiscussion.jsx | 29 ++ .../components/ProposalDiscussionModal.jsx | 64 ++++ .../components/ProposalInfoText.jsx | 23 ++ .../Governance/components/ProposalsList.jsx | 316 ++++++++++++++++++ src/containers/Governance/helpers.js | 14 + src/containers/Stake/StakeContainer.jsx | 4 +- src/hooks/governance/index.js | 108 ++++++ 17 files changed, 1232 insertions(+), 11 deletions(-) create mode 100644 src/components/Progress/LinearProgressBar.jsx create mode 100644 src/containers/Governance/Governance.jsx create mode 100644 src/containers/Governance/ProposalDetails.jsx create mode 100644 src/containers/Governance/components/GovernanceInfoText.jsx create mode 100644 src/containers/Governance/components/Metric.jsx create mode 100644 src/containers/Governance/components/ProposalDiscussion.jsx create mode 100644 src/containers/Governance/components/ProposalDiscussionModal.jsx create mode 100644 src/containers/Governance/components/ProposalInfoText.jsx create mode 100644 src/containers/Governance/components/ProposalsList.jsx create mode 100644 src/containers/Governance/helpers.js create mode 100644 src/hooks/governance/index.js diff --git a/package.json b/package.json index 9e60d39..c86a47e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.4.4", + "version": "0.5.1", "type": "module", "scripts": { "dev": "vite", @@ -16,6 +16,7 @@ "@ethersproject/bignumber": "^5.8.0", "@ethersproject/units": "^5.8.0", "@mui/icons-material": "^6.4.7", + "@mui/lab": "6.0.1-beta.36", "@mui/material": "^6.4.7", "@mui/utils": "^6.4.6", "@polkadot-api/metadata-builders": "0.13.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 183862c..28d471f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@mui/icons-material': specifier: ^6.4.7 version: 6.4.7(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@mui/lab': + specifier: 6.0.1-beta.36 + version: 6.0.1-beta.36(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': specifier: ^6.4.7 version: 6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -525,6 +528,21 @@ packages: '@ethersproject/units@5.8.0': resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} + '@floating-ui/core@1.7.4': + resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} + + '@floating-ui/dom@1.7.5': + resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} + + '@floating-ui/react-dom@2.1.7': + resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -638,6 +656,18 @@ packages: resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==} engines: {node: '>=16.0.0'} + '@mui/base@5.0.0-beta.70': + resolution: {integrity: sha512-Tb/BIhJzb0pa5zv/wu7OdokY9ZKEDqcu1BDFnohyvGCoHuSXbEr90rPq1qeNW3XvTBIbNWHEF7gqge+xpUo6tQ==} + engines: {node: '>=14.0.0'} + deprecated: This package has been replaced by @base-ui/react + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/core-downloads-tracker@6.4.7': resolution: {integrity: sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==} @@ -652,6 +682,27 @@ packages: '@types/react': optional: true + '@mui/lab@6.0.1-beta.36': + resolution: {integrity: sha512-af9lDmA9SZGEWF1XXk0EVBpfCITk9IKsvh9lLOZGdYaaHfQeCsqxGEDMvNO66j0P8EYoxpyry84LFCJYuLVtVw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material': ^6.5.0 + '@mui/material-pigment-css': ^6.5.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + '@mui/material@6.4.7': resolution: {integrity: sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==} engines: {node: '>=14.0.0'} @@ -682,6 +733,16 @@ packages: '@types/react': optional: true + '@mui/private-theming@6.4.9': + resolution: {integrity: sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/styled-engine@6.4.6': resolution: {integrity: sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==} engines: {node: '>=14.0.0'} @@ -695,6 +756,19 @@ packages: '@emotion/styled': optional: true + '@mui/styled-engine@6.5.0': + resolution: {integrity: sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/system@6.4.7': resolution: {integrity: sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==} engines: {node: '>=14.0.0'} @@ -711,6 +785,22 @@ packages: '@types/react': optional: true + '@mui/system@6.5.0': + resolution: {integrity: sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + '@mui/types@7.2.21': resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==} peerDependencies: @@ -719,6 +809,14 @@ packages: '@types/react': optional: true + '@mui/types@7.2.24': + resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/utils@6.4.6': resolution: {integrity: sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==} engines: {node: '>=14.0.0'} @@ -729,6 +827,16 @@ packages: '@types/react': optional: true + '@mui/utils@6.4.9': + resolution: {integrity: sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@noble/ciphers@1.2.1': resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} engines: {node: ^14.21.3 || >=16} @@ -3423,6 +3531,23 @@ snapshots: '@ethersproject/constants': 5.8.0 '@ethersproject/logger': 5.8.0 + '@floating-ui/core@1.7.4': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.5': + dependencies: + '@floating-ui/core': 1.7.4 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/dom': 1.7.5 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@floating-ui/utils@0.2.10': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -3615,6 +3740,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@mui/base@5.0.0-beta.70(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@floating-ui/react-dom': 2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/types': 7.2.24(@types/react@19.0.10) + '@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0) + '@popperjs/core': 2.11.8 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@mui/core-downloads-tracker@6.4.7': {} '@mui/icons-material@6.4.7(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)': @@ -3625,6 +3764,23 @@ snapshots: optionalDependencies: '@types/react': 19.0.10 + '@mui/lab@6.0.1-beta.36(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/base': 5.0.0-beta.70(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/material': 6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@mui/types': 7.2.24(@types/react@19.0.10) + '@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0) + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@types/react': 19.0.10 + '@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.9 @@ -3648,16 +3804,38 @@ snapshots: '@mui/private-theming@6.4.6(@types/react@19.0.10)(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.9 + '@babel/runtime': 7.27.6 '@mui/utils': 6.4.6(@types/react@19.0.10)(react@19.0.0) prop-types: 15.8.1 react: 19.0.0 optionalDependencies: '@types/react': 19.0.10 + '@mui/private-theming@6.4.9(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0) + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@mui/styled-engine@6.4.6(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.9 + '@babel/runtime': 7.27.6 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + + '@mui/styled-engine@6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 '@emotion/sheet': 1.4.0 @@ -3684,10 +3862,30 @@ snapshots: '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) '@types/react': 19.0.10 + '@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/private-theming': 6.4.9(@types/react@19.0.10)(react@19.0.0) + '@mui/styled-engine': 6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0) + '@mui/types': 7.2.24(@types/react@19.0.10) + '@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@types/react': 19.0.10 + '@mui/types@7.2.21(@types/react@19.0.10)': optionalDependencies: '@types/react': 19.0.10 + '@mui/types@7.2.24(@types/react@19.0.10)': + optionalDependencies: + '@types/react': 19.0.10 + '@mui/utils@6.4.6(@types/react@19.0.10)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.9 @@ -3700,6 +3898,18 @@ snapshots: optionalDependencies: '@types/react': 19.0.10 + '@mui/utils@6.4.9(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/types': 7.2.24(@types/react@19.0.10) + '@types/prop-types': 15.7.14 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.0.0 + react-is: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@noble/ciphers@1.2.1': {} '@noble/ciphers@1.3.0': {} @@ -5042,7 +5252,7 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.26.9 + '@babel/runtime': 7.27.6 cosmiconfig: 7.1.0 resolve: 1.22.10 @@ -5238,7 +5448,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.26.9 + '@babel/runtime': 7.27.6 csstype: 3.1.3 dot-case@3.0.4: diff --git a/src/App.jsx b/src/App.jsx index 3363c68..f6b3f03 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -31,6 +31,8 @@ const Wrapper = lazy(() => import("./containers/WethWrapper/WethWrapper")); const Dex = lazy(() => import("./containers/Dex/Dex")); const Bridge = lazy(() => import("./containers/Bridge/Bridge")); const NotFound = lazy(() => import("./containers/NotFound/NotFound")); +const Governance = lazy(() => import("./containers/Governance/Governance")); +const ProposalDetails = lazy(() => import("./containers/Governance/ProposalDetails")); const PREFIX = "App"; @@ -213,6 +215,8 @@ function App() { } } /> } /> + } /> + } /> } 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 ( + + + {props.target && } + + ) +} + +export default LinearProgressBar; diff --git a/src/components/Sidebar/NavContent.jsx b/src/components/Sidebar/NavContent.jsx index a4f7ab4..6d0f834 100644 --- a/src/components/Sidebar/NavContent.jsx +++ b/src/components/Sidebar/NavContent.jsx @@ -24,6 +24,8 @@ import TelegramIcon from '@mui/icons-material/Telegram'; import HowToVoteIcon from '@mui/icons-material/HowToVote'; import HubIcon from '@mui/icons-material/Hub'; import PublicIcon from '@mui/icons-material/Public'; +import ForkRightIcon from '@mui/icons-material/ForkRight'; +import GavelIcon from '@mui/icons-material/Gavel'; import ForumIcon from '@mui/icons-material/Forum'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import BookIcon from '@mui/icons-material/Book'; @@ -177,7 +179,8 @@ const NavContent = ({ chainId, addressChainId }) => { } /> - + + diff --git a/src/containers/Bond/Bonds.jsx b/src/containers/Bond/Bonds.jsx index 058c831..e880a4b 100644 --- a/src/containers/Bond/Bonds.jsx +++ b/src/containers/Bond/Bonds.jsx @@ -1,6 +1,5 @@ import { Box, Tab, Tabs, Container, useMediaQuery } from "@mui/material"; import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; import ReactGA from "react-ga4"; import Paper from "../../components/Paper/Paper"; @@ -21,7 +20,6 @@ import { useTokenSymbol } from "../../hooks/tokens"; const Bonds = ({ chainId, address, connect }) => { const [isZoomed] = useState(false); - const navigate = useNavigate(); const [secondsTo, setSecondsTo] = useState(0); const isSmallScreen = useMediaQuery("(max-width: 650px)"); @@ -29,7 +27,7 @@ const Bonds = ({ chainId, address, connect }) => { useEffect(() => { ReactGA.send({ hitType: "pageview", page: "/bonds" }); - }, []) + }, []); const { liveBonds } = useLiveBonds(chainId); const totalReserves = useTotalReserves(chainId); diff --git a/src/containers/Governance/Governance.jsx b/src/containers/Governance/Governance.jsx new file mode 100644 index 0000000..fe46b63 --- /dev/null +++ b/src/containers/Governance/Governance.jsx @@ -0,0 +1,92 @@ +import { useEffect } from "react"; +import ReactGA from "react-ga4"; + +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 { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); + + const handleModal = () => { + alert("proposal modal here"); + } + + useEffect(() => { + ReactGA.send({ hitType: "pageview", page: "/governance" }); + }, []); + + return ( + + + + + + + Proposal Requirements + + + } + > + + + + + + + + + + + + + + Claimes for locked funds could be here + + + + handleModal(true)} + sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} + > + Create Proposal + + + + + + + + + + + ) +} + +export default Governance; diff --git a/src/containers/Governance/ProposalDetails.jsx b/src/containers/Governance/ProposalDetails.jsx new file mode 100644 index 0000000..bff84fa --- /dev/null +++ b/src/containers/Governance/ProposalDetails.jsx @@ -0,0 +1,244 @@ +import { useEffect, useState, useMemo } from "react"; +import ReactGA from "react-ga4"; +import { useParams } from 'react-router-dom'; + +import { Box, Container, Typography, 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 { SecondaryButton } from "../../components/Button"; + +import { formatNumber } from "../../helpers"; +import { prettifySecondsInDays } from "../../helpers/timeUtil"; +import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; + +import ProposalDiscussionModal from "./components/ProposalDiscussionModal"; +import ProposalDiscussion from "./components/ProposalDiscussion"; +import { convertStatusToTemplate } from "./helpers"; + +import { useTokenSymbol, useTotalSupply, useBalance } from "../../hooks/tokens"; +import { + useProposalStatus, + useProposalProposer, + useProposalLocked, + useProposalQuorum, + useProposalVotes, + useProposalSnapshot, + useProposalDeadline, + useProposalVotingDelay +} from "../../hooks/governance"; + +/////////////////////////////////////////////////////// +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 }) => { + const { id } = useParams(); + 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 { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); + const { balance } = useBalance(chainId, "GHST", address); + const { totalSupply } = useTotalSupply(chainId, "GHST"); // TODO: revisit + + const { status: proposalStatus } = useProposalStatus(chainId, id); + const { proposer: proposalProposer } = useProposalProposer(chainId, id); + const { locked: proposalLocked } = useProposalLocked(chainId, id); + const { quorum: proposalQuorum } = useProposalQuorum(chainId, id); + const { forVotes, againstVotes } = useProposalVotes(chainId, id); + + useEffect(() => { + ReactGA.send({ hitType: "pageview", page: `/governance/${id}` }); + }, []); + + const isDiscussionModalOpened = useMemo(() => { + return selectedDiscussionUrl !== undefined; + }, [selectedDiscussionUrl]); + + return ( + <> + setSelectedDiscussionUrl(undefined)} + /> + + + + + + + + Progress + + + + } + topRight={ + setSelectedDiscussionUrl("dicks")} /> + } + > + + + + + For: {formatNumber(forVotes.toString(), 2)} ({formatNumber(forVotes * HUNDRED / proposalQuorum, 1)}%) + + + Against: {formatNumber(againstVotes.toString(), 2)} ({formatNumber(againstVotes * HUNDRED / proposalQuorum, 1)}%) + + + + + + + + + Quorum + + + {formatNumber(proposalQuorum.toString(), 4)} + + + + + Total + + + {formatNumber(totalSupply.toString(), 4)} + + + + + Votes + + + {formatNumber(balance.toString(), 4)} + + + + + alert("For vote casted")}>For + alert("Against vote casted")}>Against + + + + + + + Timeline + + + } + > + + + + + + + + Executable Code + + + } + > + Here will be a list of decoded calldatas + + + + + ) +} + +const VotingTimeline = ({ proposalId, chainId }) => { + 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 0; + }, [proposalSnapshot, propsalVotingDelay]); + + return ( + + + + + + ) +} + +const VotingTimelineItem = ({ isFirst, isLast, time, message }) => { + return ( + + + + {!isFirst && } + + {!isLast && } + + + + {message} + {new Date(time * 1000).toLocaleString()} + + + ) +} + +export default ProposalDetails; diff --git a/src/containers/Governance/components/GovernanceInfoText.jsx b/src/containers/Governance/components/GovernanceInfoText.jsx new file mode 100644 index 0000000..114c516 --- /dev/null +++ b/src/containers/Governance/components/GovernanceInfoText.jsx @@ -0,0 +1,23 @@ +import { Link, Typography, useTheme } from "@mui/material"; + +const GovernanceInfoText = () => { + const theme = useTheme(); + return ( + + ghostDAO's adaptive governance system algorithmically sets proposal threshold based on activity. +  Learn more here. + + ) +}; + +export default GovernanceInfoText; diff --git a/src/containers/Governance/components/Metric.jsx b/src/containers/Governance/components/Metric.jsx new file mode 100644 index 0000000..8c39cdd --- /dev/null +++ b/src/containers/Governance/components/Metric.jsx @@ -0,0 +1,53 @@ +import Metric from "../../../components/Metric/Metric"; + +import { formatCurrency, formatNumber } from "../../../helpers"; +import { + useMinQuorum, + useProposalThreshold, + useProposalCount +} from "../../../hooks/governance"; + +export const MinQuorumPercentage = props => { + const { percentage } = useMinQuorum(props.chainId); + + const _props = { + ...props, + label: `Min Quorum`, + tooltip: `Minimum quorum needed for proposal to be succeeded.`, + }; + + if (percentage) _props.metric = `${formatNumber(percentage, 2)}%`; + else _props.isLoading = true; + + return ; +}; + +export const ProposalThreshold = props => { + const { threshold } = useProposalThreshold(props.chainId, props.ghstSymbol); + + const _props = { + ...props, + label: `$${props.ghstSymbol} Threshold`, + tooltip: `Minimum $${props.ghstSymbol} amount to be locked to create proposal.`, + }; + + if (threshold) _props.metric = `${formatCurrency(threshold.toString(), 0, props.ghstSymbol)}`; + else _props.isLoading = true; + + return ; +} + +export const ProposalsCount = props => { + const { proposalsCount } = useProposalCount(props.chainId); + + const _props = { + ...props, + label: `Proposals Count`, + tooltip: `How much proposals already passed.`, + }; + + if (proposalsCount) _props.metric = proposalsCount.toString(); + else _props.isLoading = true; + + return ; +} diff --git a/src/containers/Governance/components/ProposalDiscussion.jsx b/src/containers/Governance/components/ProposalDiscussion.jsx new file mode 100644 index 0000000..9bf829b --- /dev/null +++ b/src/containers/Governance/components/ProposalDiscussion.jsx @@ -0,0 +1,29 @@ +import { Link } from "@mui/material"; +import GhostStyledIcon from "../../../components/Icon/GhostIcon"; +import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react"; + +const ProposalDiscussion = (linkProps) => { + return ( + + Learn more  + + + ) +} + +export default ProposalDiscussion; diff --git a/src/containers/Governance/components/ProposalDiscussionModal.jsx b/src/containers/Governance/components/ProposalDiscussionModal.jsx new file mode 100644 index 0000000..ac2e902 --- /dev/null +++ b/src/containers/Governance/components/ProposalDiscussionModal.jsx @@ -0,0 +1,64 @@ +import { useState } from "react"; +import { Box, Typography, Link } from "@mui/material"; + +import Modal from "../../../components/Modal/Modal"; +import { PrimaryButton } from "../../../components/Button"; + +const ProposalDiscussionModal = ({ isOpened, closeModal, url }) => { + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = () => { + navigator.clipboard.writeText(url) + .then(() => { + isCopied(true); + setTimeout(() => setIsCopied(false), 2000); + }) + .catch(err => console.error(err)); + } + + return ( + + Discussion URL + + } + open={isOpened} + onClose={closeModal} + maxWidth="460px" + minHeight="200px" + > + + + + You are leaving the ghost dao app. Check the link on your own, we are not in charge of your destiny. + + + {url} + + + + window.open(url, '_blank', 'noopener,noreferrer')} + > + Open + + + + ) +} + +export default ProposalDiscussionModal; diff --git a/src/containers/Governance/components/ProposalInfoText.jsx b/src/containers/Governance/components/ProposalInfoText.jsx new file mode 100644 index 0000000..a2ea4c9 --- /dev/null +++ b/src/containers/Governance/components/ProposalInfoText.jsx @@ -0,0 +1,23 @@ +import { Link, Typography, useTheme } from "@mui/material"; + +const ProposalInfoText = () => { + const theme = useTheme(); + return ( + + Important: We display only the 10 most recent proposals. Only one proposal can be active at a time. +  Learn more here. + + ) +}; + +export default ProposalInfoText; diff --git a/src/containers/Governance/components/ProposalsList.jsx b/src/containers/Governance/components/ProposalsList.jsx new file mode 100644 index 0000000..9f0b567 --- /dev/null +++ b/src/containers/Governance/components/ProposalsList.jsx @@ -0,0 +1,316 @@ +import { useState, useMemo } from "react"; +import { useNavigate } from "react-router-dom"; + +import { + Box, + Link, + Tabs, + Tab, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + useTheme, + useMediaQuery +} from "@mui/material"; +import { NavLink } from "react-router-dom"; +import { getBlockNumber } from "@wagmi/core"; + +import { networkAvgBlockSpeed } from "../../../constants"; +import { prettifySecondsInDays } from "../../../helpers/timeUtil"; + +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 ProposalDiscussionModal from "./ProposalDiscussionModal"; +import ProposalDiscussion from "./ProposalDiscussion"; +import ProposalInfoText from "./ProposalInfoText"; +import { convertStatusToTemplate } from "../helpers"; + +import { useScreenSize } from "../../../hooks/useScreenSize"; + +import { + useProposals, +} from "../../../hooks/governance"; + +const MAX_PROPOSALS_TO_SHOW = 10; + +const ProposalsList = ({ chainId, config }) => { + const isSmallScreen = useScreenSize("md"); + const navigate = useNavigate(); + + const [blockNumber, setBlockNumber] = useState(0n); + const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined); + const [proposalsFilter, setProposalFilter] = useState("active"); + const { proposals } = useProposals(chainId, MAX_PROPOSALS_TO_SHOW); + + getBlockNumber(config).then(block => setBlockNumber(block)); + + const isDiscussionModalOpened = useMemo(() => { + return selectedDiscussionUrl !== undefined; + }, [selectedDiscussionUrl]); + + const filteredProposals = useMemo(() => { + switch (proposalsFilter) { + case "voted": + return proposals.filter(obj => obj.status === "Succeeded" || obj.status === "Defeated"); + case "created": + return proposals.filter(obj => obj.status === "Executed"); + default: + return proposals; + } + }, [proposals, proposalsFilter]); + + if (proposals?.length === 0) { + return ( + + No proposals yet + + ); + } + + if (isSmallScreen) { + return ( + <> + setSelectedDiscussionUrl(undefined)} + /> + + + + + + + {filteredProposals?.map(proposal => ( + navigate(`/governance/${proposal.id}`)} + /> + ))} + + + + ); + } + + return ( + <> + setSelectedDiscussionUrl(undefined)} + /> + + + + + {filteredProposals?.map(proposal => ( + navigate(`/governance/${proposal.id}`)} + /> + ))} + + + + + + + + ); +} + +const ProposalTable = ({ children }) => ( + + + + + Proposal ID + Status + Discussion + Vote Ends + Voting Stats + + + + + {children} +
+
+); + +const ProposalRow = ({ proposal, setActive, blockNumber, openProposal, chainId }) => { + const theme = useTheme(); + + return ( + + + GDP-{proposal.id} + + + + + + + + setActive(proposal.discussion)} /> + + + + + {convertVoteEnds( + proposal.id % 2n === 0n, + proposal.voteEnds, + blockNumber, + chainId + )} + + + + + + + + + + + {(proposal.status === "Active" || proposal.status === "Succeeded") && openProposal()} + sx={{ maxWidth: "150px" }} + > + {proposal.status === "Succeeded" ? "Execute" : "Vote"} + } + {(proposal.status !== "Active" && proposal.status !== "Succeeded") && openProposal()} + sx={{ maxWidth: "150px" }} + > + View + } + + + ); +} + +const ProposalCard = ({ proposal, setActive, blockNumber, openProposal, chainId }) => { + const theme = useTheme(); + const isSmallScreen = useMediaQuery('(max-width: 450px)'); + + return ( + + + + + GIP-{proposal.id} + + + + {convertVoteEnds( + proposal.id % 2n === 0n, + proposal.voteEnds, + blockNumber, + chainId + )} + + + + setActive(proposal.discussion)} + /> + + + + + + + + {(proposal.status === "Active" || proposal.status === "Succeeded") && openProposal()} + > + {proposal.status === "Succeeded" ? "Execute" : "Vote"} + } + {(proposal.status !== "Active" && proposal.status !== "Succeeded") && openProposal()} + > + View + } + + + ); +}; + +const ProposalFilterTrigger = ({ trigger, setTrigger }) => { + return ( + setTrigger(view)} + TabIndicatorProps={{ style: { display: "none" } }} + > + + + + + ) +} + +const convertVoteEnds = (tmp, voteEnds, blockNumber, chainId) => { + const tmpVoteSeconds = Number(voteEnds * networkAvgBlockSpeed(chainId)); + const tmpSeconds = (tmp ? tmpVoteSeconds : -tmpVoteSeconds); + + const result = prettifySecondsInDays(tmpSeconds); + if (result === "now") { + return new Date(Date.now()).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + } + + return `in ${result}`; +} + +export default ProposalsList; diff --git a/src/containers/Governance/helpers.js b/src/containers/Governance/helpers.js new file mode 100644 index 0000000..505d17a --- /dev/null +++ b/src/containers/Governance/helpers.js @@ -0,0 +1,14 @@ +export const convertStatusToTemplate = (status) => { + switch (status.toUpperCase()) { + case "EXECUTED": + return 'info'; + case "CANCELED": + return 'warning'; + case "SUCCEEDED": + return 'success'; + case "DEFEATED": + return 'error'; + default: + return 'info'; + } +} diff --git a/src/containers/Stake/StakeContainer.jsx b/src/containers/Stake/StakeContainer.jsx index 49e3f4a..e404bcc 100644 --- a/src/containers/Stake/StakeContainer.jsx +++ b/src/containers/Stake/StakeContainer.jsx @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction, useState, useEffect } from "react"; -import { Box, Container, Grid, Divider, Typography, Link, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Container, Grid, Divider, Typography, useMediaQuery, useTheme } from "@mui/material"; import ReactGA from "react-ga4"; import Paper from "../../components/Paper/Paper"; @@ -47,7 +47,7 @@ export const StakeContainer = ({ chainId, address, connect }) => { } return ( - + { + const numerator = 69n; + const denominator = 100n; + + let percentage = 0; + + if (numerator && denominator && denominator > 0n) { + percentage = Number(100n * numerator / denominator) / 100; + } + + return { numerator, denominator, percentage } +} + +export const useProposalThreshold = (chainId, name) => { + const decimals = getTokenDecimals(name); + const threshold = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals); + + return { threshold }; +} + +export const useProposalCount = (chainId) => { + const proposalsCount = 1337n; + return { proposalsCount }; +} + +export const useProposalStatus = (chainId, proposalId) => { + const status = "Succeeded"; + return { status }; +} + +export const useProposalProposer = (chainId, proposalId) => { + const proposer = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"; + return { proposer }; +} + +export const useProposalLocked = (chainId, proposalId) => { + const decimals = getTokenDecimals(name); + const locked = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals); + return { locked } +} + +export const useProposalQuorum = (chainId, proposalId) => { + const decimals = getTokenDecimals(name); + const quorum = new DecimalBigNumber(1337_000_000_000_000_000_000n, decimals); + return { quorum } +} + +export const useProposalVotes = (chainId, proposalId) => { + const decimals = getTokenDecimals(name); + const forVotes = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals); + const againstVotes = new DecimalBigNumber(69_000_000_000_000_000_000n, decimals); + return { forVotes, againstVotes } +} + +export const useProposalSnapshot = (chainId, proposalId) => { + const snapshot = Math.floor((Date.now() - (3 * 24 * 60 * 60 * 1000)) / 1000); + return { snapshot }; +} + +export const useProposalDeadline = (chainId, proposalId) => { + const deadline = Math.floor(Date.now() / 1000); + return { deadline }; +} + +export const useProposalVotingDelay = (chainId, proposalId) => { + const delay = 1; + return { delay }; +} + +export const useProposals = (chainId, depth) => { + const decimals = getTokenDecimals(name); + const { proposalsCount } = useProposalCount(chainId); + + let iterator = proposalsCount ? proposalsCount : 0n; + const bigIntDepth = BigInt(depth); + const edgeProposalId = iterator > bigIntDepth ? iterator - bigIntDepth : 0n; + + const statuses = [ + "Active", + "Executed", + "Canceled", + "Succeeded", + "Defeated" + ]; + let proposals = []; + + while (iterator > proposalsCount - bigIntDepth) { + iterator -= 1n; + + const voteEnds = 50n; + const yesVotes = new DecimalBigNumber(1337_000_000_000_000_000_000, decimals); + const noVotes = new DecimalBigNumber(420_000_000_000_000_000_000, decimals); + + proposals.push({ + id: iterator, + discussion: "https://google.com", + status: statuses[Number(iterator) % statuses.length], + voteEnds, + yesVotes, + noVotes + }); + } + + return { proposals }; +} From 1fbaf94c24c1d294b1a3454603a6352ff46f3066 Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Tue, 3 Feb 2026 17:01:19 +0300 Subject: [PATCH 02/20] revision v3 from the designers Signed-off-by: Uncle Fatso --- package.json | 2 +- src/components/Chip/Chip.jsx | 4 +- src/containers/Bond/Bonds.jsx | 12 +++- src/containers/Bond/components/ClaimBonds.jsx | 12 +++- src/containers/Bridge/Bridge.jsx | 4 +- src/containers/Dex/Dex.jsx | 2 +- src/containers/Faucet/Faucet.jsx | 2 +- src/containers/Governance/Governance.jsx | 4 +- src/containers/Governance/ProposalDetails.jsx | 39 ++++++++---- .../components/GovernanceInfoText.jsx | 4 +- .../Governance/components/Metric.jsx | 20 +++++-- .../components/ProposalDiscussion.jsx | 2 +- .../components/ProposalInfoText.jsx | 2 +- .../Governance/components/ProposalsList.jsx | 60 +++++++++++++++---- src/containers/Governance/helpers.js | 4 +- src/containers/Stake/components/FarmPools.jsx | 12 +++- src/containers/WethWrapper/WethWrapper.jsx | 2 +- 17 files changed, 141 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index c86a47e..97e8da4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.5.1", + "version": "0.5.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/Chip/Chip.jsx b/src/components/Chip/Chip.jsx index a4d9117..f19cbe8 100644 --- a/src/components/Chip/Chip.jsx +++ b/src/components/Chip/Chip.jsx @@ -20,7 +20,7 @@ const StyledMuiChip = styled(MuiChip, { : template === "gray" ? theme.colors.gray[500] : template === "darkGray" - ? theme.colors.gray[600] + ? theme.colors.primary[300] : theme.colors.feedback[template] : theme.palette.mode === "light" ? theme.colors.gray[90] @@ -34,7 +34,7 @@ const StyledMuiChip = styled(MuiChip, { : template === "gray" ? theme.colors.gray[10] : template === "darkGray" - ? theme.colors.gray[90] + ? theme.colors.gray[800] : theme.colors.gray[600], fontWeight: strong ? 700 : 450, }, diff --git a/src/containers/Bond/Bonds.jsx b/src/containers/Bond/Bonds.jsx index e880a4b..20ee595 100644 --- a/src/containers/Bond/Bonds.jsx +++ b/src/containers/Bond/Bonds.jsx @@ -57,7 +57,17 @@ const Bonds = ({ chainId, address, connect }) => { }} > - + + + Active bonds + + + } + > { warmupLength={warmupInfo.expiry - epoch.number} setPreClaimConfirmed={() => setPreClaimConfirmed(true)} /> - + + + Your Bonds + + + } + > Payout Options diff --git a/src/containers/Bridge/Bridge.jsx b/src/containers/Bridge/Bridge.jsx index d159b5f..3fd6340 100644 --- a/src/containers/Bridge/Bridge.jsx +++ b/src/containers/Bridge/Bridge.jsx @@ -291,7 +291,7 @@ const Bridge = ({ chainId, address, config, connect }) => { return ( <> - + { onClick={() => setBridgeAction(!bridgeAction)} />)} { - bridgeAction ? `Bridge $${ghstSymbol}` : "Transaction History" + bridgeAction ? `Bridge-In $${ghstSymbol}` : "Transaction History" } } diff --git a/src/containers/Dex/Dex.jsx b/src/containers/Dex/Dex.jsx index 073d8a7..9ab8196 100644 --- a/src/containers/Dex/Dex.jsx +++ b/src/containers/Dex/Dex.jsx @@ -186,7 +186,7 @@ const Dex = ({ chainId, address, connect }) => { { - + { return ( - + { - + diff --git a/src/containers/Governance/ProposalDetails.jsx b/src/containers/Governance/ProposalDetails.jsx index bff84fa..88a3e06 100644 --- a/src/containers/Governance/ProposalDetails.jsx +++ b/src/containers/Governance/ProposalDetails.jsx @@ -115,13 +115,13 @@ const ProposalDetails = ({ chainId, address }) => { setSelectedDiscussionUrl("dicks")} /> } > - + - + For: {formatNumber(forVotes.toString(), 2)} ({formatNumber(forVotes * HUNDRED / proposalQuorum, 1)}%) - + Against: {formatNumber(againstVotes.toString(), 2)} ({formatNumber(againstVotes * HUNDRED / proposalQuorum, 1)}%) @@ -218,7 +218,7 @@ const VotingTimeline = ({ proposalId, chainId }) => { - + ) } @@ -226,16 +226,33 @@ const VotingTimeline = ({ proposalId, chainId }) => { const VotingTimelineItem = ({ isFirst, isLast, time, message }) => { return ( - + + {message} + + - {!isFirst && } - - {!isLast && } + + + - - {message} - {new Date(time * 1000).toLocaleString()} + + + {new Date(time * 1000).toLocaleString()} + ) diff --git a/src/containers/Governance/components/GovernanceInfoText.jsx b/src/containers/Governance/components/GovernanceInfoText.jsx index 114c516..f551d7f 100644 --- a/src/containers/Governance/components/GovernanceInfoText.jsx +++ b/src/containers/Governance/components/GovernanceInfoText.jsx @@ -9,10 +9,10 @@ const GovernanceInfoText = () => { fontSize="0.875em" lineHeight="15px" > - ghostDAO's adaptive governance system algorithmically sets proposal threshold based on activity. + ghostDAO’s adaptive governance system algorithmically sets minimum collateral based on activity.  Learn more here. diff --git a/src/containers/Governance/components/Metric.jsx b/src/containers/Governance/components/Metric.jsx index 8c39cdd..1b79ad7 100644 --- a/src/containers/Governance/components/Metric.jsx +++ b/src/containers/Governance/components/Metric.jsx @@ -1,6 +1,9 @@ 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, @@ -8,15 +11,22 @@ import { } from "../../../hooks/governance"; export const MinQuorumPercentage = props => { - const { percentage } = useMinQuorum(props.chainId); + 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 quorum needed for proposal to be succeeded.`, + tooltip: `Minimum $${props.ghstSymbol} turnout required for the proposal to become valid`, }; - if (percentage) _props.metric = `${formatNumber(percentage, 2)}%`; + if (percentage) _props.metric = `${formatCurrency(value?.toString(), 2, props.ghstSymbol)} (${formatNumber(percentage * 100, 2)}%)`; else _props.isLoading = true; return ; @@ -28,7 +38,7 @@ export const ProposalThreshold = props => { const _props = { ...props, label: `$${props.ghstSymbol} Threshold`, - tooltip: `Minimum $${props.ghstSymbol} amount to be locked to create proposal.`, + tooltip: `Minimum $${props.ghstSymbol} required to be locked to create a proposal`, }; if (threshold) _props.metric = `${formatCurrency(threshold.toString(), 0, props.ghstSymbol)}`; @@ -43,7 +53,7 @@ export const ProposalsCount = props => { const _props = { ...props, label: `Proposals Count`, - tooltip: `How much proposals already passed.`, + tooltip: `Total proposals created`, }; if (proposalsCount) _props.metric = proposalsCount.toString(); diff --git a/src/containers/Governance/components/ProposalDiscussion.jsx b/src/containers/Governance/components/ProposalDiscussion.jsx index 9bf829b..56d4f54 100644 --- a/src/containers/Governance/components/ProposalDiscussion.jsx +++ b/src/containers/Governance/components/ProposalDiscussion.jsx @@ -7,7 +7,7 @@ const ProposalDiscussion = (linkProps) => { { fontSize="0.875em" lineHeight="15px" > - Important: We display only the 10 most recent proposals. Only one proposal can be active at a time. + Important: Only the 10 most recent proposals are displayed. Only one proposal can be active at a time.   { const isSmallScreen = useScreenSize("md"); const navigate = useNavigate(); + const theme = useTheme(); const [blockNumber, setBlockNumber] = useState(0n); const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined); @@ -83,11 +83,17 @@ const ProposalsList = ({ chainId, config }) => { isOpened={isDiscussionModalOpened} closeModal={() => setSelectedDiscussionUrl(undefined)} /> - - - - - + + + Proposals + + + } + > {filteredProposals?.map(proposal => ( { /> ))} + + {proposalsFilter === "active" && + + } ); @@ -112,7 +122,35 @@ const ProposalsList = ({ chainId, config }) => { isOpened={isDiscussionModalOpened} closeModal={() => setSelectedDiscussionUrl(undefined)} /> - + + + Proposals + + + + + Discussion Forum + + + + } + > @@ -128,9 +166,9 @@ const ProposalsList = ({ chainId, config }) => { ))} - + {proposalsFilter === "active" && - + } ); @@ -141,7 +179,7 @@ const ProposalTable = ({ children }) => ( - Proposal ID + Proposal ID Status Discussion Vote Ends @@ -160,7 +198,7 @@ const ProposalRow = ({ proposal, setActive, blockNumber, openProposal, chainId } return ( - + GDP-{proposal.id} diff --git a/src/containers/Governance/helpers.js b/src/containers/Governance/helpers.js index 505d17a..925852d 100644 --- a/src/containers/Governance/helpers.js +++ b/src/containers/Governance/helpers.js @@ -1,7 +1,7 @@ export const convertStatusToTemplate = (status) => { switch (status.toUpperCase()) { case "EXECUTED": - return 'info'; + return 'darkGray'; case "CANCELED": return 'warning'; case "SUCCEEDED": @@ -9,6 +9,6 @@ export const convertStatusToTemplate = (status) => { case "DEFEATED": return 'error'; default: - return 'info'; + return 'darkGray'; } } diff --git a/src/containers/Stake/components/FarmPools.jsx b/src/containers/Stake/components/FarmPools.jsx index d8b24bc..e56943a 100644 --- a/src/containers/Stake/components/FarmPools.jsx +++ b/src/containers/Stake/components/FarmPools.jsx @@ -61,7 +61,17 @@ const FarmPools = ({ chainId }) => { } return ( - + + + Farm Pools + + + } + >
diff --git a/src/containers/WethWrapper/WethWrapper.jsx b/src/containers/WethWrapper/WethWrapper.jsx index 79cbdcd..8656d41 100644 --- a/src/containers/WethWrapper/WethWrapper.jsx +++ b/src/containers/WethWrapper/WethWrapper.jsx @@ -137,7 +137,7 @@ const WethWrapper = ({ chainId, address, config, connect }) => { - + Date: Tue, 3 Feb 2026 17:05:08 +0300 Subject: [PATCH 03/20] re-apply session overlap fix Signed-off-by: Uncle Fatso --- package.json | 2 +- src/containers/Bridge/Bridge.jsx | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 97e8da4..41dd590 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.5.2", + "version": "0.5.3", "type": "module", "scripts": { "dev": "vite", diff --git a/src/containers/Bridge/Bridge.jsx b/src/containers/Bridge/Bridge.jsx index 3fd6340..8760792 100644 --- a/src/containers/Bridge/Bridge.jsx +++ b/src/containers/Bridge/Bridge.jsx @@ -129,10 +129,18 @@ const Bridge = ({ chainId, address, config, connect }) => { }); const blockCommitments = useBlockCommitments({ evmChainId: chainId }); const disabledValidators = useDisabledValidators(); - const transactionApplaused = useApplauseDetails({ + const transactionApplausedDirect = useApplauseDetails({ currentSession: watchTransaction?.sessionIndex ?? currentSession, argsHash: hashedArguments }); + const transactionApplausedIncremented = useApplauseDetails({ + currentSession: watchTransaction ? watchTransaction.sessionIndex + 1 : undefined, + argsHash: hashedArguments + }); + + const transactionApplaused = useMemo(() => { + return transactionApplausedDirect || transactionApplausedIncremented; + }, [transactionApplausedDirect, transactionApplausedIncremented]); const finalityDelay = Number(evmNetwork?.finality_delay ?? 0n); @@ -272,8 +280,11 @@ const Bridge = ({ chainId, address, config, connect }) => { } const storeTransactionHash = (txHash, receiver, amount) => { + const expectedSessionIndex = (currentSession ?? 0) + (evmNetwork + ? Number((evmNetwork.avg_block_speed * evmNetwork.finality_delay) / (1000n * 14400n)) + : 0); const transaction = { - sessionIndex: currentSession ?? 0, + sessionIndex: expectedSessionIndex, transactionHash: txHash, receiverAddress: receiver, amount: amount, From 5e19d626f801440124d0461241987702881a13fc Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Wed, 4 Feb 2026 21:29:50 +0300 Subject: [PATCH 04/20] revision v4 from the designers Signed-off-by: Uncle Fatso --- package.json | 2 +- src/App.jsx | 2 + src/containers/Dex/Dex.jsx | 2 +- src/containers/Governance/Governance.jsx | 6 +- src/containers/Governance/NewProposal.jsx | 76 +++++++++++++++++++++++ 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/containers/Governance/NewProposal.jsx diff --git a/package.json b/package.json index 41dd590..0b48556 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.5.3", + "version": "0.5.4", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.jsx b/src/App.jsx index f6b3f03..d7a455b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -33,6 +33,7 @@ const Bridge = lazy(() => import("./containers/Bridge/Bridge")); const NotFound = lazy(() => import("./containers/NotFound/NotFound")); const Governance = lazy(() => import("./containers/Governance/Governance")); const ProposalDetails = lazy(() => import("./containers/Governance/ProposalDetails")); +const NewProposal = lazy(() => import("./containers/Governance/NewProposal")); const PREFIX = "App"; @@ -217,6 +218,7 @@ function App() { } /> } /> } /> + } /> } { { const isSemiSmallScreen = useMediaQuery("(max-width: 745px)"); const isSmallScreen = useMediaQuery("(max-width: 650px)"); const isVerySmallScreen = useMediaQuery("(max-width: 379px)"); + const navigate = useNavigate(); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const handleModal = () => { - alert("proposal modal here"); + const navigate = useNavigate(); } useEffect(() => { @@ -72,7 +74,7 @@ const Governance = ({ connect, config, address, chainId }) => { handleModal(true)} + onClick={() => navigate(`/governance/create`)} sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} > Create Proposal diff --git a/src/containers/Governance/NewProposal.jsx b/src/containers/Governance/NewProposal.jsx new file mode 100644 index 0000000..b377792 --- /dev/null +++ b/src/containers/Governance/NewProposal.jsx @@ -0,0 +1,76 @@ +import { useState } from "react"; + +import { + Box, + Container, + Typography, + OutlinedInput, + InputLabel, + FormControl, + useMediaQuery, +} from "@mui/material"; + +import Paper from "../../components/Paper/Paper"; +import PageTitle from "../../components/PageTitle/PageTitle"; +import { PrimaryButton } from "../../components/Button"; + +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 [descriptionUrl, setDescriptionUrl] = useState(""); + + return ( + + + + + + + Proposal Overview + + + } + > + + + + + + ) +} + +const BasicInput = ({ id, label, value, eventHandler }) => { + return ( + + {label} + + + eventHandler(event.currentTarget.value)} + /> + + + + ) +} + +export default NewProposal; From 8d13afcd421361a66e776095c34d6352807fe714 Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Wed, 4 Feb 2026 21:37:17 +0300 Subject: [PATCH 05/20] re-apply: update genesis block (default chain id) Signed-off-by: Uncle Fatso --- package.json | 2 +- src/hooks/ghost/UnstableProvider.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0b48556..e662579 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.5.4", + "version": "0.5.5", "type": "module", "scripts": { "dev": "vite", diff --git a/src/hooks/ghost/UnstableProvider.jsx b/src/hooks/ghost/UnstableProvider.jsx index d14b7e2..5df5bb0 100644 --- a/src/hooks/ghost/UnstableProvider.jsx +++ b/src/hooks/ghost/UnstableProvider.jsx @@ -4,7 +4,7 @@ import { createClient } from "@polkadot-api/substrate-client" import { getObservableClient } from "@polkadot-api/observable-client" import useSWR from "swr" -const DEFAULT_CHAIN_ID = "0x5e1190682f1a6409cdfd691c0b23a6db792864d8994e591e9c19a31d8163989f" +const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3" const UnstableProvider = createContext(null) export const useUnstableProvider = () => useContext(UnstableProvider) From 87ebb9beff3bdeeb656d993eb576aa70cf6773ac Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Tue, 10 Feb 2026 20:33:16 +0300 Subject: [PATCH 06/20] update addresses based on new deployments Signed-off-by: Uncle Fatso --- src/constants.ts | 3 --- src/constants/addresses.js | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index a5b306b..cc24806 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -26,9 +26,6 @@ export const isNetworkAvailable = (chainId, addressChainId) => { export const isNetworkLegacy = (chainId) => { let exists = false; switch (chainId) { - case 11155111: - exists = true - break; case 560048: exists = true break; diff --git a/src/constants/addresses.js b/src/constants/addresses.js index 3fed2ae..3fe82fa 100644 --- a/src/constants/addresses.js +++ b/src/constants/addresses.js @@ -1,25 +1,25 @@ import { NetworkId } from "../constants"; export const STAKING_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xd90E63E88282596E1ea33765b41Ba3d650f4aD52", + [NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",//"0xd90E63E88282596E1ea33765b41Ba3d650f4aD52", [NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86", [NetworkId.TESTNET_MORDOR]: "0xC25C9C56a89ebd6ef291b415d00ACfa7913c55e7", }; export const BOND_DEPOSITORY_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xdcE486113280e49ca2fB200258E5Ee1B2D21D495", + [NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",//"0xdcE486113280e49ca2fB200258E5Ee1B2D21D495", [NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571", [NetworkId.TESTNET_MORDOR]: "0x7C85cDEddBAd0f50453d373F7332BEa11ECa7BAf", }; export const DAO_TREASURY_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x93dd30f819403710de7933B79A74C4A42438458D", + [NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",//"0x93dd30f819403710de7933B79A74C4A42438458D", [NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8", [NetworkId.TESTNET_MORDOR]: "0x5883C8e2259556B534036c7fDF4555E09dE9f243", }; export const FTSO_DAI_LP_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x1394dC3f7bABaa2F0CA80353648087DAB1BF3fd6", + [NetworkId.TESTNET_SEPOLIA]: "0x1394dC3f7bABaa2F0CA80353648087DAB1BF3fd6", // TBD [NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50", [NetworkId.TESTNET_MORDOR]: "0xE6546D12665dB5B22Cb92FB9e0221aE51A57aeaa", }; @@ -31,49 +31,49 @@ export const FTSO_STNK_LP_ADDRESSES = { } export const RESERVE_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x5f63a27a9214a0352F2EF8dAF1eD4974d713192B", + [NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",//"0x5f63a27a9214a0352F2EF8dAF1eD4974d713192B", [NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58", [NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC", }; export const WETH_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14", + [NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14", [NetworkId.TESTNET_HOODI]: "0xE69a5c6dd88cA798b93c3C92fc50c51Fd5305eB4", [NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC", }; export const GHST_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xdf2e5306A3dCcfA4e21bbF4226C17Ff5B008dDC4", + [NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",//"0xdf2e5306A3dCcfA4e21bbF4226C17Ff5B008dDC4", [NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46", [NetworkId.TESTNET_MORDOR]: "0x14b5787F8a1E62786F50A7998A9b14aa24298423", }; export const STNK_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x02C296A27eA779d5a16F934337c12062C5E3c0D9", + [NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",//"0x02C296A27eA779d5a16F934337c12062C5E3c0D9", [NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F", [NetworkId.TESTNET_MORDOR]: "0x137bA9403885D8ECEa95AaFBb8734F5a16121bAC", }; export const FTSO_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xcFedFFEB3FdeCd2196820Ba3b71f3F84A1255f93", + [NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",//"0xcFedFFEB3FdeCd2196820Ba3b71f3F84A1255f93", [NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033", [NetworkId.TESTNET_MORDOR]: "0xeA170CC0faceC531a6a9e93a28C4330Ac50343a1", }; export const DISTRIBUTOR_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x8fbF8eB4Fcd451EF62Aee33508D46FE120963194", + [NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",//"0x8fbF8eB4Fcd451EF62Aee33508D46FE120963194", [NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842", [NetworkId.TESTNET_MORDOR]: "0xaf5e76706520db7fb01096E322940206bf3fce57", }; export const GHOST_GOVERNANCE_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xDab0c51918E6990d8763FAC8a04AE159e44e0c4f", + [NetworkId.TESTNET_SEPOLIA]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4", [NetworkId.TESTNET_HOODI]: "0x1B96B792840d4d19d5097ee007392Ed4d851e64F", [NetworkId.TESTNET_MORDOR]: "0x3dD438416D9593A58193fC52850E588efAa3D57E", }; export const BONDING_CALCULATOR_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x4896bFc6256A57Df826d7144E48c9633d51d6319", + [NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",//"0x4896bFc6256A57Df826d7144E48c9633d51d6319", [NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd", [NetworkId.TESTNET_MORDOR]: "0x0c4C7C49a173E2a3f9Eed93125F3F146D8e17bCb", } From 056177c34b1aad613848d785b50abb3e5a4cac52 Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Tue, 10 Feb 2026 20:35:12 +0300 Subject: [PATCH 07/20] incorrect bridge estimations fix Signed-off-by: Uncle Fatso --- package.json | 2 +- src/containers/Bridge/Bridge.jsx | 5 +++-- src/containers/Bridge/BridgeHeader.jsx | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e662579..4e976a5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.5.5", + "version": "0.5.7", "type": "module", "scripts": { "dev": "vite", diff --git a/src/containers/Bridge/Bridge.jsx b/src/containers/Bridge/Bridge.jsx index 8760792..b9bf28d 100644 --- a/src/containers/Bridge/Bridge.jsx +++ b/src/containers/Bridge/Bridge.jsx @@ -252,10 +252,10 @@ const Bridge = ({ chainId, address, config, connect }) => { if (commit.disabled || blockNumber < blocksInFourHours) { continue; } - certainty += (commit?.lastStoredBlock ?? 0n) - (blockNumber - blocksInFourHours); + certainty += (commit?.lastStoredBlock ?? 0n) + BigInt(finalityDelay) - (blockNumber - blocksInFourHours); } return Math.max(Number(certainty * 100n / (blocksInFourHours * BigInt(length))), 0); - }, [latestCommits, blockNumber]); + }, [latestCommits, blockNumber, finalityDelay]); const timeToNextEpoch = useMemo(() => { if (!currentSession || !genesisSlot || !currentSlot) { @@ -343,6 +343,7 @@ const Bridge = ({ chainId, address, config, connect }) => { transactionEta={slowestEvmBlock ? Number(slowestEvmBlock) : undefined} timeToNextEpoch={timeToNextEpoch ? Number(timeToNextEpoch) : undefined} isSmallScreen={isSmallScreen} + maxDelay={14400 + finalityDelay * Number(networkAvgBlockSpeed(chainId))} /> diff --git a/src/containers/Bridge/BridgeHeader.jsx b/src/containers/Bridge/BridgeHeader.jsx index 5a173a5..1b5fe49 100644 --- a/src/containers/Bridge/BridgeHeader.jsx +++ b/src/containers/Bridge/BridgeHeader.jsx @@ -12,6 +12,7 @@ export const BridgeHeader = ({ bridgeStability, transactionEta, timeToNextEpoch, + maxDelay, isSmallScreen }) => { const theme = useTheme(); @@ -92,7 +93,7 @@ export const BridgeHeader = ({ 14400 ? "∞" : formatTime(transactionEta)} + metric={transactionEta > maxDelay ? "∞" : formatTime(transactionEta)} label="Max Bridge ETA" tooltip="Maximum estimated time for finalizing bridge transactions based on the latest update." /> From 56c5616d962fc428654e898ef8808115ea160c92 Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Tue, 10 Feb 2026 20:36:19 +0300 Subject: [PATCH 08/20] new functionality for governance added Signed-off-by: Uncle Fatso --- package.json | 2 +- src/components/Select/Select.jsx | 72 +++++ src/components/Swap/SwapCard.jsx | 2 +- src/containers/Bond/Bonds.jsx | 2 +- src/containers/Governance/NewProposal.jsx | 174 ++++++++---- src/containers/Governance/ProposalDetails.jsx | 254 ++++++++++-------- .../Governance/components/Metric.jsx | 4 +- .../components/ProposalDiscussion.jsx | 29 -- .../components/ProposalDiscussionModal.jsx | 64 ----- .../Governance/components/ProposalModal.jsx | 100 +++++++ .../Governance/components/ProposalsList.jsx | 163 +++++------ .../components/functions/AuditReserves.jsx | 45 ++++ .../components/functions/CreateBond.jsx | 244 +++++++++++++++++ .../components/functions/SetAdjustment.jsx | 92 +++++++ .../Governance/components/functions/index.jsx | 188 +++++++++++++ src/hooks/governance/index.js | 3 +- 16 files changed, 1082 insertions(+), 356 deletions(-) create mode 100644 src/components/Select/Select.jsx delete mode 100644 src/containers/Governance/components/ProposalDiscussion.jsx delete mode 100644 src/containers/Governance/components/ProposalDiscussionModal.jsx create mode 100644 src/containers/Governance/components/ProposalModal.jsx create mode 100644 src/containers/Governance/components/functions/AuditReserves.jsx create mode 100644 src/containers/Governance/components/functions/CreateBond.jsx create mode 100644 src/containers/Governance/components/functions/SetAdjustment.jsx create mode 100644 src/containers/Governance/components/functions/index.jsx diff --git a/package.json b/package.json index 4e976a5..b7d74be 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.5.7", + "version": "0.5.8", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/Select/Select.jsx b/src/components/Select/Select.jsx new file mode 100644 index 0000000..2a940b9 --- /dev/null +++ b/src/components/Select/Select.jsx @@ -0,0 +1,72 @@ +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, + width = "100%", +}) => { + const theme = useTheme(); + return ( + + {label && ( + + {label} + + )} + + + + {options.map((opt) => ( + + {opt.label} + + ))} + + + + ); +}; + +export default Select; diff --git a/src/components/Swap/SwapCard.jsx b/src/components/Swap/SwapCard.jsx index 4f7b416..160549e 100644 --- a/src/components/Swap/SwapCard.jsx +++ b/src/components/Swap/SwapCard.jsx @@ -7,7 +7,7 @@ import Token from "../Token/Token"; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; -const StyledInputBase = styled(InputBase, { shouldForwardProp: prop => prop !== "inputWidth" })( +export const StyledInputBase = styled(InputBase, { shouldForwardProp: prop => prop !== "inputWidth" })( ({ inputWidth, inputFontSize }) => ({ "& .MuiInputBase-input": { padding: 0, diff --git a/src/containers/Bond/Bonds.jsx b/src/containers/Bond/Bonds.jsx index 20ee595..55c7ad0 100644 --- a/src/containers/Bond/Bonds.jsx +++ b/src/containers/Bond/Bonds.jsx @@ -1,4 +1,4 @@ -import { Box, Tab, Tabs, Container, useMediaQuery } from "@mui/material"; +import { Box, Tab, Tabs, Typography, Container, useMediaQuery } from "@mui/material"; import { useEffect, useState } from "react"; import ReactGA from "react-ga4"; diff --git a/src/containers/Governance/NewProposal.jsx b/src/containers/Governance/NewProposal.jsx index b377792..c6dbfd8 100644 --- a/src/containers/Governance/NewProposal.jsx +++ b/src/containers/Governance/NewProposal.jsx @@ -1,75 +1,153 @@ -import { useState } from "react"; +import { useState, useMemo } from "react"; 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 Paper from "../../components/Paper/Paper"; import PageTitle from "../../components/PageTitle/PageTitle"; -import { PrimaryButton } from "../../components/Button"; +import { PrimaryButton, TertiaryButton } from "../../components/Button"; + +import ProposalModal from "./components/ProposalModal"; +import { parseFunctionCalldata } from "./components/functions/index"; 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 [descriptionUrl, setDescriptionUrl] = useState(""); + const theme = useTheme(); + + const [isModalOpened, setIsModalOpened] = useState(false); + const [proposalFunctions, setProposalFunctions] = useState([]); + + const addCalldata = (calldata) => setProposalFunctions(prev => [...prev, calldata]); + const removeCalldata = (index) => setProposalFunctions(prev => prev.filter((_, i) => i !== index)); + + const nativeCurrency = useMemo(() => { + const client = config?.getClient(); + return client?.chain?.nativeCurrency?.symbol; + }, [config]); + + const submitProposal = () => { + alert("Proposal created"); + setProposalFunctions([]); + } return ( - - - - - - - Proposal Overview - + <> + setIsModalOpened(false)} /> + + + + + + + Proposal Functions + + + } + > + + + {proposalFunctions.length === 0 && + + Create new proposal by adding functions below + + + Learn more  + + + } + + + + setIsModalOpened(true)}>Add New + submitProposal()}>Submit Proposal + - } - > - - - - - - ) -} + -const BasicInput = ({ id, label, value, eventHandler }) => { - return ( - - {label} - - - eventHandler(event.currentTarget.value)} - /> - + {proposalFunctions.length > 0 && + + Proposal Functions + + + } + > + +
+ + + Function + Target + Value + + + {proposalFunctions.map((calldata, index) => { + return parseFunctionCalldata(calldata, index, nativeCurrency, removeCalldata); + })} +
+ + } +
+
-
+ ) } diff --git a/src/containers/Governance/ProposalDetails.jsx b/src/containers/Governance/ProposalDetails.jsx index 88a3e06..3a21b63 100644 --- a/src/containers/Governance/ProposalDetails.jsx +++ b/src/containers/Governance/ProposalDetails.jsx @@ -2,7 +2,7 @@ import { useEffect, useState, useMemo } from "react"; import ReactGA from "react-ga4"; import { useParams } from 'react-router-dom'; -import { Box, Container, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Container, Typography, Link, useMediaQuery, useTheme } from "@mui/material"; import Paper from "../../components/Paper/Paper"; import PageTitle from "../../components/PageTitle/PageTitle"; @@ -11,12 +11,13 @@ import InfoTooltip from "../../components/Tooltip/InfoTooltip"; import Chip from "../../components/Chip/Chip"; import { SecondaryButton } from "../../components/Button"; +import GhostStyledIcon from "../../components/Icon/GhostIcon"; +import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react"; + import { formatNumber } from "../../helpers"; import { prettifySecondsInDays } from "../../helpers/timeUtil"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; -import ProposalDiscussionModal from "./components/ProposalDiscussionModal"; -import ProposalDiscussion from "./components/ProposalDiscussion"; import { convertStatusToTemplate } from "./helpers"; import { useTokenSymbol, useTotalSupply, useBalance } from "../../hooks/tokens"; @@ -65,7 +66,7 @@ const ProposalDetails = ({ chainId, address }) => { const { proposer: proposalProposer } = useProposalProposer(chainId, id); const { locked: proposalLocked } = useProposalLocked(chainId, id); const { quorum: proposalQuorum } = useProposalQuorum(chainId, id); - const { forVotes, againstVotes } = useProposalVotes(chainId, id); + const { forVotes, againstVotes, totalVotes } = useProposalVotes(chainId, id); useEffect(() => { ReactGA.send({ hitType: "pageview", page: `/governance/${id}` }); @@ -75,130 +76,153 @@ const ProposalDetails = ({ chainId, address }) => { return selectedDiscussionUrl !== undefined; }, [selectedDiscussionUrl]); + 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 / totalSupply * HUNDRED; + }, [totalVotes, totalSupply]); + + const voteWeightPercentage = useMemo(() => { + if (totalSupply._value === 0n) return 0; + return balance / totalSupply * HUNDRED; + }, [balance, totalSupply]); + return ( - <> - setSelectedDiscussionUrl(undefined)} - /> - - - - - - - - Progress + + + + + + + + Progress + + + + } + topRight={ + + View Forum  + + + } + > + + + + + For: {formatNumber(forVotes.toString(), 2)} ({formatNumber(forVotes * HUNDRED / proposalQuorum, 1)}%) + + + Against: {formatNumber(againstVotes.toString(), 2)} ({formatNumber(againstVotes * HUNDRED / proposalQuorum, 1)}%) - - } - topRight={ - setSelectedDiscussionUrl("dicks")} /> - } - > - - - - - For: {formatNumber(forVotes.toString(), 2)} ({formatNumber(forVotes * HUNDRED / proposalQuorum, 1)}%) - - - Against: {formatNumber(againstVotes.toString(), 2)} ({formatNumber(againstVotes * HUNDRED / proposalQuorum, 1)}%) - + + + + + + + Quorum + - + {formatNumber(proposalQuorum.toString(), 4)} ({formatNumber(quorumPercentage, 1)}%) - - - - Quorum - - - {formatNumber(proposalQuorum.toString(), 4)} - - - - - Total - - - {formatNumber(totalSupply.toString(), 4)} - - - - - Votes - - - {formatNumber(balance.toString(), 4)} + + + Total + + {formatNumber(totalSupply.toString(), 4)} ({formatNumber(votePercentage, 1)}%) - - alert("For vote casted")}>For - alert("Against vote casted")}>Against + + + Votes + + + {formatNumber(balance.toString(), 4)} ({formatNumber(voteWeightPercentage, 1)}%) - - - - Timeline - - - } - > - - - - - - - - Executable Code - + + alert("For vote casted")}>For + alert("Against vote casted")}>Against + - } - > - Here will be a list of decoded calldatas - - - - + + + + + Timeline + + + } + > + + + + + + + + Executable Code + + + } + > + Here will be a list of decoded calldatas + + + ) } diff --git a/src/containers/Governance/components/Metric.jsx b/src/containers/Governance/components/Metric.jsx index 1b79ad7..9ee5822 100644 --- a/src/containers/Governance/components/Metric.jsx +++ b/src/containers/Governance/components/Metric.jsx @@ -37,7 +37,7 @@ export const ProposalThreshold = props => { const _props = { ...props, - label: `$${props.ghstSymbol} Threshold`, + label: "Min Collateral", tooltip: `Minimum $${props.ghstSymbol} required to be locked to create a proposal`, }; @@ -56,7 +56,7 @@ export const ProposalsCount = props => { tooltip: `Total proposals created`, }; - if (proposalsCount) _props.metric = proposalsCount.toString(); + if (proposalsCount) _props.metric = `${formatNumber(proposalsCount.toString(), 0)}`; else _props.isLoading = true; return ; diff --git a/src/containers/Governance/components/ProposalDiscussion.jsx b/src/containers/Governance/components/ProposalDiscussion.jsx deleted file mode 100644 index 56d4f54..0000000 --- a/src/containers/Governance/components/ProposalDiscussion.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Link } from "@mui/material"; -import GhostStyledIcon from "../../../components/Icon/GhostIcon"; -import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react"; - -const ProposalDiscussion = (linkProps) => { - return ( - - Learn more  - - - ) -} - -export default ProposalDiscussion; diff --git a/src/containers/Governance/components/ProposalDiscussionModal.jsx b/src/containers/Governance/components/ProposalDiscussionModal.jsx deleted file mode 100644 index ac2e902..0000000 --- a/src/containers/Governance/components/ProposalDiscussionModal.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useState } from "react"; -import { Box, Typography, Link } from "@mui/material"; - -import Modal from "../../../components/Modal/Modal"; -import { PrimaryButton } from "../../../components/Button"; - -const ProposalDiscussionModal = ({ isOpened, closeModal, url }) => { - const [isCopied, setIsCopied] = useState(false); - - const copyToClipboard = () => { - navigator.clipboard.writeText(url) - .then(() => { - isCopied(true); - setTimeout(() => setIsCopied(false), 2000); - }) - .catch(err => console.error(err)); - } - - return ( - - Discussion URL - - } - open={isOpened} - onClose={closeModal} - maxWidth="460px" - minHeight="200px" - > - - - - You are leaving the ghost dao app. Check the link on your own, we are not in charge of your destiny. - - - {url} - - - - window.open(url, '_blank', 'noopener,noreferrer')} - > - Open - - - - ) -} - -export default ProposalDiscussionModal; diff --git a/src/containers/Governance/components/ProposalModal.jsx b/src/containers/Governance/components/ProposalModal.jsx new file mode 100644 index 0000000..8d94de1 --- /dev/null +++ b/src/containers/Governance/components/ProposalModal.jsx @@ -0,0 +1,100 @@ +import { useState, useEffect, useMemo, useCallback } from "react"; +import { Box, Typography } 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, addCalldata }) => { + 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)); + setSelectedOption(null); + closeModal(); + }, [selectedOption]); + + const handleClose = () => { + setSelectedOption(null); + setRenderArguments(false); + closeModal(); + } + + const handleAddCalldata = (calldata) => { + addCalldata(calldata); + handleClose(); + } + + const ArgumentsSteps = useMemo(() => getFunctionArguments(selectedOption), [selectedOption]); + + return ( + + {headerLabel} + + } + open={isOpened} + onClose={handleClose} + maxWidth="460px" + minHeight="200px" + > + + {renderArguments + ? setRenderArguments(false)} + /> + : setRenderArguments(true)} + ready={ArgumentsSteps !== null} + /> + } + + + ) +} + +const InitialStep = ({ selectedOption, handleChange, handleCalldata, handleProceed, ready }) => { + const functionDescription = useMemo(() => getFunctionDescription(selectedOption), [selectedOption]); + return ( + + { - if (!selected || selected.length === 0) { - return ( - - Select payment token - - ); - } + +