diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1c9509c9c..9e39de450 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,4 +22,4 @@ assignees: '' ## Expected behavior - \ No newline at end of file + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 207145494..d8368d8e2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -10,4 +10,4 @@ contact_links: - name: All issues url: https://github.com/IntersectMBO/govtool/issues - about: Check whether your issue is not already covered here. \ No newline at end of file + about: Check whether your issue is not already covered here. diff --git a/.github/ISSUE_TEMPLATE/feature_idea.yml b/.github/ISSUE_TEMPLATE/feature_idea.yml index 60b5902b0..67f24bc04 100644 --- a/.github/ISSUE_TEMPLATE/feature_idea.yml +++ b/.github/ISSUE_TEMPLATE/feature_idea.yml @@ -41,4 +41,4 @@ body: Which technical solutions, libraries or systems should be used, which components need to change, steps how to implement this, ... validations: - required: true \ No newline at end of file + required: true diff --git a/.github/dbsync-schema.sql b/.github/dbsync-schema.sql index 014727435..7f43b6589 100644 --- a/.github/dbsync-schema.sql +++ b/.github/dbsync-schema.sql @@ -987,4 +987,3 @@ FROM tx_out LEFT JOIN block ON tx.block_id = block.id WHERE tx_in.tx_in_id IS NULL AND block.epoch_no IS NOT NULL; - diff --git a/.github/workflows/code_check_backend.yml b/.github/workflows/code_check_backend.yml new file mode 100644 index 000000000..c7a9d4981 --- /dev/null +++ b/.github/workflows/code_check_backend.yml @@ -0,0 +1,65 @@ +name: Backend Lint & Format Check +description: 'Check Haskell code in backend module' + +on: + workflow_dispatch: + push: + paths: + - govtool/backend/** + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Use Haskell + uses: haskell-actions/setup@v2 + with: + ghc-version: '9.2.7' + cabal-version: '3.6.0.0' + + - name: Use Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install Pre-commit + run: pip install pre-commit + + - name: Install HLint + run: | + cabal update + cabal install hlint + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run hlint + run: pre-commit run --all-files hlint + + check-format: + runs-on: ubuntu-latest + steps: + - name: Use Haskell + uses: haskell-actions/setup@v2 + with: + ghc-version: '9.2.7' + cabal-version: '3.6.0.0' + + - name: Use Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install Pre-commit + run: pip install pre-commit + + - name: Install HLint + run: | + cabal update + cabal install stylish-haskell + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run hlint + run: pre-commit run --all-files stylish-haskell diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index 19c0047e4..bf1d30c45 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -9,7 +9,7 @@ on: jobs: lighthouse: runs-on: ubuntu-latest - env: + env: NODE_OPTIONS: --max_old_space_size=4096 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/resync-cardano-node-and-db-sync.yml b/.github/workflows/resync-cardano-node-and-db-sync.yml index 06d6e2b56..c193a07a9 100644 --- a/.github/workflows/resync-cardano-node-and-db-sync.yml +++ b/.github/workflows/resync-cardano-node-and-db-sync.yml @@ -63,6 +63,16 @@ jobs: with: ssh-private-key: ${{ secrets.GHA_SSH_PRIVATE_KEY }} + - name: Set domain + run: | + if [[ "${{ inputs.environment }}" == "staging" ]]; then + echo "DOMAIN=staging.govtool.byron.network" >> $GITHUB_ENV + elif [[ "${{ inputs.environment }}" == "beta" ]]; then + echo "DOMAIN=sanchogov.tools" >> $GITHUB_ENV + else + echo "DOMAIN=${DOMAIN:-$ENVIRONMENT-$CARDANO_NETWORK.govtool.byron.network}" >> $GITHUB_ENV + fi + - name: Destroy Cardano Node, DB sync and Postgres database run: | make --debug=b destroy-cardano-node-and-dbsync diff --git a/.github/workflows/test_backend.yml b/.github/workflows/test_backend.yml index 125666f2a..eb17b9e46 100644 --- a/.github/workflows/test_backend.yml +++ b/.github/workflows/test_backend.yml @@ -38,7 +38,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pytest -v --github-report + pytest -v --github-report env: BASE_URL: https://${{inputs.deployment || 'staging.govtool.byron.network/api' }} METRICS_URL: https://metrics.cardanoapi.io diff --git a/.github/workflows/test_integration_cypress.yml b/.github/workflows/test_integration_cypress.yml index a0fbf9011..f6f5d554b 100644 --- a/.github/workflows/test_integration_cypress.yml +++ b/.github/workflows/test_integration_cypress.yml @@ -30,7 +30,7 @@ jobs: run: working-directory: ./tests/govtool-frontend runs-on: ubuntu-latest - env: + env: NODE_OPTIONS: --max_old_space_size=4096 steps: diff --git a/.gitignore b/.gitignore index 3d712a194..2ce4f59f3 100644 --- a/.gitignore +++ b/.gitignore @@ -101,7 +101,6 @@ book/src/08_event-db/db-diagrams/*.dot # Used by nix result* .direnv/ -/.pre-commit-config.yaml # Development Environments .vscode diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..e5d0bd8ac --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + +- repo: local + hooks: + - id: hlint + name: hlint + description: HLint gives suggestions on how to improve your source code. + entry: hlint + language: system + files: '\.l?hs$' + + - id: stylish-haskell + name: stylish-haskell + description: Haskell code format checker. + entry: stylish-haskell --config govtool/backend/.stylish-haskell.yaml + language: system + files: '\.l?hs$' diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4ce807a..aeab28c58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ changes. ### Fixed +- proposal/list now takes optional `search` query param [Issue 566](https://github.com/IntersectMBO/govtool/issues/566) - Fix possible sql error when there would be no predefined drep voting pwoer [Issue 501](https://github.com/IntersectMBO/govtool/issues/501) - Fix drep type detection when changing metadata [Issue 333](https://github.com/IntersectMBO/govtool/issues/333) - Fix make button disble when wallet tries connect [Issue 265](https://github.com/IntersectMBO/govtool/issues/265) diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md index 279206db0..8ceedd39a 100644 --- a/CODE-OF-CONDUCT.md +++ b/CODE-OF-CONDUCT.md @@ -129,4 +129,4 @@ at [https://www.contributor-covenant.org/translations][translations]. [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file +[translations]: https://www.contributor-covenant.org/translations diff --git a/CODEOWNERS b/CODEOWNERS index a1a507b56..b53151a56 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -19,4 +19,4 @@ infra/terraform/* @pla # Testing gov-action-loader/* @IntersectMBO/govtool-test @kickloop -tests/* @IntersectMBO/govtool-test @kickloop \ No newline at end of file +tests/* @IntersectMBO/govtool-test @kickloop diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9fdd28480..a6e724ba7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,7 +133,7 @@ Examples: ### Commit Messages -Please make informative commit messages! +Please make informative commit messages! It makes it much easier to work out why things are the way they are when you’re debugging things later. A commit message is communication, so as usual, put yourself in the position of the reader: what does a reviewer, or someone reading the commit message later need to do their job? @@ -175,7 +175,7 @@ If a branch is outdated, use the rebase button in PRs to rebase feature branches Keeping branches ahead of main not only make the git history a lot nicer to process, it also makes conflict resolutions easier. Merging main into a branch repeatedly is a good recipe to introduce invalid conflict resolutions and loose track of the actual changes brought by a the branch. -### Versioning +### Versioning Not all releases are declared stable. Releases that aren't stable will be released as pre-releases and will append a -pre tag indicating it is not ready for running on production networks. @@ -202,7 +202,7 @@ Please see [CSS / SASS Style Guide](./docs/style-guides/css-sass/). #### Haskell -TODO +Please see [stylish-haskell configuration](./govtool/backend/.stylish-haskell.yaml). ## Bumping Node, DB-Sync, SanchoNet Versions @@ -246,7 +246,7 @@ TODO - If QA agrees that the code is good, they can make a PR from `test` branch to `staging` branch where end-to-end and performance tests are run. - If tests pass, then QA or tech lead can merge and deploy to staging environment (automatically). - Moving ticket to `staging` status this ready for PO check. - + ### PO Workflow - Choose ticket from `staging` status. diff --git a/LICENSE b/LICENSE index c38410929..b9e734694 100644 --- a/LICENSE +++ b/LICENSE @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/SECURITY.md b/SECURITY.md index d7856a7fb..b7cf16805 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,4 +12,4 @@ Please provide a clear and concise description of the vulnerability, including: * steps that can be followed to exercise the vulnerability, * any workarounds or mitigations -If you have developed any code or utilities that can help demonstrate the suspected vulnerability, please mention them in your email but ***DO NOT*** attempt to include them as attachments as this may cause your Email to be blocked by spam filters. \ No newline at end of file +If you have developed any code or utilities that can help demonstrate the suspected vulnerability, please mention them in your email but ***DO NOT*** attempt to include them as attachments as this may cause your Email to be blocked by spam filters. diff --git a/SUPPORT.md b/SUPPORT.md index 29a3ade79..39f3878e2 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -10,4 +10,4 @@ See [`SECURITY.md`](SECURITY.md) on how to report a security vulnerability. # Contributions -See [`CONTRIBUTING.md`](CONTRIBUTING.md) on how to contribute. \ No newline at end of file +See [`CONTRIBUTING.md`](CONTRIBUTING.md) on how to contribute. diff --git a/docs/architecture/.gitignore b/docs/architecture/.gitignore index bce10f1e2..3434d617e 100644 --- a/docs/architecture/.gitignore +++ b/docs/architecture/.gitignore @@ -7,4 +7,4 @@ arch-structurizr/workspace.json #oura install oura/ -./oura/ \ No newline at end of file +./oura/ diff --git a/docs/architecture/arch-structurizr/README.md b/docs/architecture/arch-structurizr/README.md index 924bd9ce2..da08652d9 100644 --- a/docs/architecture/arch-structurizr/README.md +++ b/docs/architecture/arch-structurizr/README.md @@ -25,4 +25,4 @@ http://localhost:8080/workspace/documentation/* ## Decision Log ```bash http://localhost:8080/workspace/decisions/* -``` \ No newline at end of file +``` diff --git a/docs/architecture/arch-structurizr/dapp.dsl b/docs/architecture/arch-structurizr/dapp.dsl index dabfa3e1f..a1b1795bd 100644 --- a/docs/architecture/arch-structurizr/dapp.dsl +++ b/docs/architecture/arch-structurizr/dapp.dsl @@ -77,4 +77,3 @@ hwWallet -> cardanoWallet "integrates" // User's browser attaches to GVC FE browser -> dAppFrontEnd "connects" - diff --git a/docs/architecture/arch-structurizr/decisions/0001-decision b/docs/architecture/arch-structurizr/decisions/0001-decision index d01970187..5673ab07c 100644 --- a/docs/architecture/arch-structurizr/decisions/0001-decision +++ b/docs/architecture/arch-structurizr/decisions/0001-decision @@ -15,4 +15,3 @@ Accepted ## Consequences - diff --git a/docs/architecture/arch-structurizr/docker-compose.yml b/docs/architecture/arch-structurizr/docker-compose.yml index a55ac8367..ad387bf08 100644 --- a/docs/architecture/arch-structurizr/docker-compose.yml +++ b/docs/architecture/arch-structurizr/docker-compose.yml @@ -2,9 +2,9 @@ version: "3.6" services: voltaire-structurizr: - container_name: voltaire-dapp-architecture + container_name: voltaire-dapp-architecture image: structurizr/lite:latest ports: - '8080:8080' volumes: - - .:/usr/local/structurizr:rw \ No newline at end of file + - .:/usr/local/structurizr:rw diff --git a/docs/architecture/arch-structurizr/workspace.dsl b/docs/architecture/arch-structurizr/workspace.dsl index 096ccc31e..ee4cffc41 100644 --- a/docs/architecture/arch-structurizr/workspace.dsl +++ b/docs/architecture/arch-structurizr/workspace.dsl @@ -2,9 +2,9 @@ workspace "Voltaire Implementation Draft" { !docs ./documentation !adrs ./decisions !identifiers hierarchical - + model { - !include dapp.dsl + !include dapp.dsl } views { @@ -16,14 +16,14 @@ workspace "Voltaire Implementation Draft" { systemContext dAppFrontEnd { include * - autoLayout lr + autoLayout lr } systemContext dAppBackEnd { include * - autoLayout lr + autoLayout lr } - + container dAppBackEnd { include * autolayout lr @@ -73,12 +73,12 @@ workspace "Voltaire Implementation Draft" { element "Database" { shape Cylinder - } + } } branding { logo data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfUAAABkCAMAAACo21lxAAAArlBMVEX///8AM60AMKwAKasAL6wAK6sALasAJqoZPK8AI6kAKKuruODc4PCls90AIakcS7dXc8Vmd8NGZcCkrtudptZogcoAHqj09vz5+/4AFacAGqg9Xb3Ez+wAOrHt8fq2weTY3vFzhcrP1+4ADabu8fnj6PV/kc9SbcIgQ7KNn9Y6VrjAyujM1O2+xuVScMQyULaUn9N3jM4AAKVue8M/Yb6Fl9ItTLUmRrNndsJjfsljQZZ0AAAWOElEQVR4nO1diXbiuLbFkgdswMy2MZMhmCkkhNchufz/jz1PR5ZsSYZUp26lrvfq7rUaT5L20dGZpDQaX8Ds9XwJv/JgjZ+LQMPI6P63W1Hjm9Dm/3w1FUXxn7nXwpn3jQ2q8e2YfSoDl3dha0SsO0vepbWuN2vd/4MRdjXkj3hX1v+nI2zw5rQ7wIrRq2f7z0UwRwr+h3upNZ3vAt6F8FNXcM36T8bKx3aHf8kNBcxeTcPnqv4aPwTutv84ga1t6xuaUqNGjRo1atSoUaNGjfsRHMYv1R74YnTbBt/fmBq/B625bdkrbmSWQvvsYLM5+y0tqvHtWNiqoigTbmSWwi4OzevKb2lSjW9Hx4noVLQB82NwuA2ZfNxij+LbJoIkXY0fhqFdZr1j2dhRTtQvi2bC+lPN+t+BhR3z6dAa/sWJf9LnNMWrWDjwx+9uXY1vwkg1LPOVtuZecTyvFftA/RYObNM8r39342p8F9bXC5t526sJ67hH/+i13lqL39quGr8V71rCunH9bzekxm9Ex49JR06tz/8OrF/uYdK7+Jqu8Yurire2Dvyiyhp/DK6Kjd7uuM8b7s67ewouNn3fNL+0EHgJvvJkDcB9Q/hiIkW1gn/xu61oLUDWgytBezm6Xsa9wfv77jJqrWvqH8fiZfi2XfV377v+avvWmcni6Ye4zN0e/otfX1niwnkuvGDURdgwMNZ0VdewgdX563NVEiDCet8EKBfJfUO1KcTn6+HEr+ruKLz79+/j0al9R+NibPNXfAaS++bpPfOjoL58hNIbVGFALGwNFM0wLKxF67CGLcPQ57sXYbl6Kw6+Oo9lUOQzsTWJ5jq+f64ve4qtJlG+HJEQvHcqJ/zIRADVkNw3dJAQqmb4+z6vPrRjcB/QsenMB9t7+ucqOnnMlllEWE1vMnr86yM7ve4IWD/1945WGEIFafbHKhC0bOfjyU0su6fr2cHHQ97JWV97evJvL8InvMuTPblbeQRTv9TeBLozr7AivCb1oExdDU3eB2gZ889l3dSxxA/o1uTYqpTKNLCdPSJLUmmkF3zZGGUdMLmsB7sJ5g6homJnzI+nuMObuP3uVTNUJZpJKJsN3lVPvoDxRSgp3mkYCDtY7M9c47Y3bfNYqklffOpmbScmoYr1+HG1NN4S1mPoxlQs+SkGOnW/L9GnZAyQeuJdl7HuDedY0kyj+3jJc9/JhAgZ6ahcDZAqZ/zw28q42HwhhSZPZZG/G9NbPRDeeAfrCnIOhacqWI8kxSw+wuKk0J3DK/GdueRr5w3nuoz1sXwII+m8w9sObsrnG3z5LVdRyI9Ng9Mkf53ZIY8Y2vtXojfeyqbbF62ZarQQ0j9hkX0Tf7bJ3GqJR/Ue1qP+FEankvVIIe94HAFWzAvQXNwVSt9ZN851MetenxlCJR5CnbWSkC8XzggvioVUZ5pqVrer5g8nsdgxNb30aapT13uMkKZXqTsOrtTbVEtTmtPp8bOJDOpnayB8GoYCtNFcqOIJ6wgXoFEdRBarWAjr9FMarbQjqRQ3r9GwEN0+meVBr3IOx7IUs76iBDpqp6ocp9PpWVENaqVHWgXtrpEMg71N/m9NzWxFPy4a4Z6WojSx7vUSknTlYR/7JX+9bncPy2wurDt9O+fdFsZ7suFUoU2WcP8NWFXovBqPx/G/gNezaZIuGaz3B6yjLnliddtNdd+gmLdvwm6npSkKakIfz8KRoFlH87LaFLJ+yGe6as9XrexRd90Zq0bO1ZPcj24Z2Uhu6I9lrQkbgUP9oNjJMIfZ6GuPTvbwg0w048i6aeGKNBlpAm9llg2qMsteowk3WMJcx0VTxPPc8NAEEtGe0cHAuka7U567WV/f1VxFCFdNr5dyaY1uGamq0J5jLFrto9QPEevrXGTxx4FdbcLtnrxW7Uo3mg+NbCgTZXcwaNZRxDptNWest7Pf8KNb3a5ETu1+yVYfEQ2FeQtdhEuqD3B/A6ayKeoazPUS6wnWH0A7uzufy3oCbzYn8wEpgq8GmWSgE/ga1lbQPpZ1xSjdJ2Dd2xGdaO/KzQg/SCtNWRCrsUzv09OFvUVPbfUcafg5reEniT5ZnJNBQziQvbiMNnkXV4nnq2qT+2JoSiRsI0M0WhmEcz17FQg37vNaUGY9wpUMjciKvKRv1Y+NMDOPkCay/Qreq1+cQALWW2TeYC6r3hhuQKrU2m4mDcgMj5A2QmPPw91R7UPNdIa+JaJs9mWv5WBExpq/NK5Ajg2uEsnmb7LumCCXglGVz/X8UypTDCZlvXElY8NfgiCEFIsivJ9nqCUosK7uA/a6gPUjKClDIHk5X2I9EyP4dLAxAcmhPeJJvCotac8NLMOVaZpPY5kPw4EH/gGa833ytp7pAsyVp6zH2q5B6nwULAhIVMz1xks2Nmz8TM56bj0b3HnWyp62g1xn6gOB5VGMVOF39jqf9TXoG30vsmhC4uGo0oCXN7pcgvyh3OBKxWlHRBwPCM/h4fDwVog1mAhFN5mgB1zwXLLnrDd2TPRbNiiJCHBQxfoaPH9EC2AF6wswRtUPzsoOtpweH/LjOSBV3MhbmXVwogB81g/AhcRrhqFRnAfMrk7qQyFtkqnhxaufTDLV5vX1AYClWDCcKSxBlCccMd1a1NNrCO34fLVRxXr4mT3PxPcqWM89HMzxi8BWMBKFCIpWZHkA6zpMMqQyRHJZd8GKhcAJt2vg14r6zkWw03zHV3p5IzpH23H886/Wz8FAiAOVxNzzy5bIJjPxUyvGI6PKb1UV620YGp2WwCrWG5A1sjgqfpux7iRvfAYbZsJ/E9A3eCdeJOOQDHmshyB10jz5FmyW411lre4mdL34MJrTcs0cSLMJl61wkdyxPm2+WP8QnjOxFh2CkxxylOYXOWfedDL7LLsE5prOj9tXsX7K5As9ouHzQCUn/LLJzA49tQ8Xx6y3Aj2bcY23M+Ia4x6l4aADNs060YWWTOu+wOrCCf6UENz2zsSZSnLc3vDs+L6/+1qJHMlMTIQiuBhkxQZ6aahgcqMsq+6Ch2Jw7YsqG74Dq80j1lw0oPBVv3wJs9MQpr4gkgSsrxq5B05ng7hz/ZK1T2gjpgA5squjaAclUaG68S4y/cKulbRVE3rJUhAR1MX3eIDSlVO2kBN/BKYd356rmOse2GU6YzpXsh7AwjAJipdAKj8zmtowL7XSrTGA9X+olBJCOU0j3lwfZMuaJV9sQakalTmYtwl82+AfNNnYnIlQ+l8pvwIi9OMXHiZTB4FNvCT2EE/bVbA+AkrYzGkl6wvwl0snbi4gXglOpwcLNt/yyC5q0e0HEv5RP8nQc1mHrxvy8QfvWyt2PhiOGKX/TEViHf5U7uW3II32R7zOeCXwT2hcoC2iIZUiG9Rct7lEonl+oFTDuyTeghDjGFWyTixSszjuh4ylvHqQLCJN3izSctYbPTKd8vM6eTb8AhwPUZQC2mLAYEXPLPKvDxXbRrSYv1LBGfTJmz0BHZqlwyhe38AWJdCnA/9gYZBAfiixAhCJpOqNrtC3KWdUyVy/eS6LTTg65kE2dtGtZr2fdaKoPDfZzEZN8sIQNLfG44hmPWySGAmG/vFYJz6ZLresOiThGIneUzfIfl7HXiLS8mc9nY62c5vZYvIy83ykE0LyOqDWxJ5wef0V1iEColAJ8Ta4MT5H0QDraP8+ZdG17Dxt+sQ+Ws06RFqL4c5T9j1anb+Cp8qLNNKsN1p5Zgd2GvFYz91NeYQMbE702eiqCqnHTF9JxRVDJlRk85wNug6QSTul+ol4kJEORBqvLb1Kd12MIKODVthEEngpuryqQtXVpNgE/sOUOBX0fzXrsEwVHfZMV6I5JUYnGDGHozsZ1qmlXT+nN/8K62S5njcUandq+koqdfQrrK9Y1gdYUZu8tvwK62DLMQsaWTg5K9KQrTLiA58LPuT9rBc6scg8DH1K/wjGF6/YjmXdOxMCjFQSf4l1soI1Lr5tw5g9xxUMyMyH0GPqrrjVEoyGV/f5hWX8Ml0Jsv+dzZ8sbhiGaPjHay7drLuIyTm4YIuUTKu76uaQWarQ+/JcH2UPsik2YlUdy3kqlvVGO1/aUwNdvq7fOdej2fd8yLXP1rSwsaKGcEDnU7lx8jZtzTFJp6tu2/O8u5sFPxcHS6KoZkIC8NIKua6+OFJWzbqGykXY1axDlAAz67oLthxbHtmGdCUuT6MC69HcIcOfpAa4rIMNr8mtOWpdZ/G8GjMl8S9UNYXNN7f6+UCieUBfOY3uqYYHd0mUJpMA6C1spXuGFpVjJpWs4yPHBqxm/T/c8D8kQIuLVxdiMa+lFxVZb4wJ7VocJ+Ox7n7c57m1chu+AqM8HMzzhCJspsS7mwgj6RKAPyEpIRQgzNqmvbIuIUwxjqlA1nWks4CR5WbwK1knSS92UYHFyyjMQbIqPpWC0CXWvSNRt+ZYkHO7M0pDfNrqyMjFSL6qGl1RmDzcJxFZhPGvRWTRo0/C+pjEY7z4Hy/deQfqo5zvzj23wTuFwTQbNxI4ZVDJegizjYnNgeZFc7p5Xlywkt1uljaOU7G5DNQOCmfGz76QRKN8Hzqxm+/wkZcD33f85lCWfZk6/kTpf+1oyRPYK0/iLwTLDDSLkM1S1PcBiym8suR1kChNUQtAEQ63HKaS9RPMxwmToIUpvS+0b0BKpN+Ltk5prkevIVVL+ruXFbEWsi8kvCnNvoD4cH2xIrzN6TmJ3y2CViukm7mJfpiFyS3rjfvFTGsbBN8R2yJbR0vg00vIC3FE1IK6JnZvadUQxuGJ1cRzoitZh7grG5E45u3grycReUUrojzXc/Ml1g2wODBznSzY3CAvYAHSo90RJyfYdiMnz5nmgrKcOrbt73+xqoKsiZJUECkypBUoHS0WoXgIg5B1F3Q8rzCiknWo/GMs0mBSbk8RJceFx/riM6/TH4MBQbNO8ni8cGTeC5CN/f2HQZ72ZtI1fdJLp7vXm6SF0Hbz184lAv1UVncAUudO19IsnqoHtRQhFefclplpiPRyb6pYn5FUHa2LLndIZamGnqPhI1/AyneCwJdo5shaJ8tgkSrZijTX7HAgbaLyqUYqnxeiYPGRKJb15VJ9xEABeSWISFIDWJF8SoO9VQdc4q0erCSJWfdgBwQnPF7Butcn1ZyUxJAaISmsgtnNZb3xZhSfY6sloThKsGMgwQzeId1/u+g9mflZQlvqu5M4uDCjphopp+hNDOxPHzxN1gVJFnrssGyq1A6gzXu+OkpQqLmS5NehgLno/TcqWV+CO8gct9u6J/hbqm/ksx7Hs1mwrJNaHn7teAJSMu/LyqzeY56Rk8rigt6AnZiK/9A5WCP16tJkwcPHyZJdDoKtUiEUvtL6+pRv5uABmls47VrCuncUTnY56ydS9MKYHWSUpe0rFoAKWA8/C4qjsAuCBEiFZc9k1zCWKfh1tuMp1d4neltbHJpl97Sm3YW6cO1BF24JDVKbPDVBkmhMqvdGlFqXB1LXzpYGymppOrDS2MXJLmV9vQd69TM1bwPSKW77ujB0BR9SwHrjuWDFFFgnO1pRk79M5klbSxbAA2fTSPQBs6dVscNGmzl9IY02wPYKfpZFAhLt1zlt9nrQI3pUYXObaL/MguySYlYxGevEjNeKW9ElrC9G+T52gx5PiBSJignheiEzKGKdWWLLrG9yzWLxHOA3LFB+BWQObDaqnWI+tcB6UuqR/W2AqnBwGcTQUPR5q+ByBlPuAT3Dquj9gFhY9MIprZvrgGirhayIiHUvPJzzveHMuQUbcObMgN8+oj3ZzXtC1qldqxzWqe2nSL0UJ0K7n28H16XO+ia90Uh3ujCnFsR7WkmSOEG6pzU9Jf4rpxZcqKKRc4cS/tNKI6Tr51wgPJiX/D8I3qBMLMaek1dLgiRrhcJNYF0dnGaAl85oNbDMfLnVGTcYHEFNUGOaL8VsJEnIeqPNnNhSOrUgL19Exny7zL/abt1wLjClM3cKWGINISuLS7tniuRkKbpSGkftpjy7R1uNvvl4Xbz3mWd0dUM5j6/D4ei6ms71vL3MIZWwN04tFkAQkO0GzADKWSch1ILln++lzsNrmmFgWvBVzEwiUDXihMiImxkUs95Y0vuKS6wv8ixNnBJRPvqj4XB42O2RRSkJ6TkqCU5TTb/By0f0aUTx8Ad+LnpE9YaHffP2oOOWfmtOjSBSsRE5jdGoMtJNj9+Km89mAPEERJuIctaJAivM0DtOI9IQo6hJMbsh3NqR30IHNyWsN27URCufS7NWaCGMxtCMx1BjNIQlPcmLA3LCEZw8tiThZPvRDesczBTxaXPJV5lae1JHMBGHBUlA1ORsHREU7gyJGc9wWMk6Mj7YhkCEQVY0ALEd5tAQGetubuLwTh4L9hXNROaAR/p6KF7q3VWyOiBMKqc7n1ZcUoutrTDm77bGwzsPWl0fZW3W5oxfkNeUS94IixJ9U9XeF5h/apf5XMVwasqqEEyG95iSzUUkJmlT64mM9UY7P3yGd95cu2ciRQxVX/EMrjffftqKLbGXnv/0hG65UC+2Xf/JWomXcbc3wZPenbQvxk+C0zAV3SlUuED0VHokK6mboYoaqnY3kmMzGMtaynp8CGlR4zwTs0MSB/PAeKRrKaWsU1Fo/omib4Yh4l13+PbWKZYU+QnPDx7Znhy+4999ItV6hTiNRpozLZhE4FHIws78DdtVuxs3IE/6BzV7O6UwePZ9pFv2J2eDDyntktaZgEFMHxWT7YkWRVbJsRg233pabPc25zBehM2joEAiiU19bYeiAIkJaN/zhwYyBJemHhlxKpggCBvafPdSzMVt/fSgP1teatdz0tsMTGbccJL+5IhKM9+yV+MnSo10fKt4KGF6LGFzeuEdLh4oRnKL9STNRs6s7DY/t+fs9CfnP/xHFp/ZI8I/mBgu3xXL0LKTORFSI9NYU24zkRl3ik+KvX9m3oFYe6BS4YAcs852cN4rlj+Z+Npnf/hSXiC8ZStFJ5C+KujAfWHpJ1HQ2GsBqMW23eLgZRaI1HcIX6koWlmSb5F5CL+IVC5piuTwn7B17R27Td2emKjZnY5HnDHMcTFMX342cwr3MD2O7+LyoNpK5bbZEjzX3aT4an3O/zzIELpu1Rh6L9fqk87j4hdf1y18V4rldNffD6rx5+OWxl2bv3YGUY2fhcw0FkbAa/wFCFtvjN6HHGfhdLjh5Rr81nbV+EbM9qZp72i7PysdZ/6OxWJnW6byla0vNf5EJIdMO3SC4C2JkWBmQ2ZSO6Xad4bhavzhSMNg6gfFp3eYGNhhY41pzLte6v8SBBnrTGhnfb0V/uz2sWb9r0KST7Wr9pYnWr/W8H8Nnucm9oW7UgDuysH2/NG/AVHjj8X60u9UV2B4y/Fb8P2NqVGjRo0aNWrUqFGjhgSt7eNRl2XvWldC/GR0HFO0wX0TCqh9s7H/b1be1fjNcAeaovP/qufoOO9xL3jRMyr3QPEaPwPuACsW9yir5ZOOrDn3oauD8H1/QqjGn4l2U59z62+TTWacP7LViPdRK+S0+Ro/Eu6Mr6tX8VYQ0Q6zr+xtrPEDcHI0ZL5X31fjr8Jy9yHe0Vjjz8f/A58flJOaxNzMAAAAAElFTkSuQmCC - # font - } + # font + } } -} \ No newline at end of file +} diff --git a/docs/architecture/sequence-diagrams/raw/delegation.txt b/docs/architecture/sequence-diagrams/raw/delegation.txt index 30501025e..689907360 100644 --- a/docs/architecture/sequence-diagrams/raw/delegation.txt +++ b/docs/architecture/sequence-diagrams/raw/delegation.txt @@ -10,4 +10,4 @@ dApp Frontend->Wallet:""API.submitDelegation(dRepID, PubStakeKey)"" Wallet->User: Ask permission popup (Wallet UI) User->Wallet: Access granted (Wallet UI) Wallet->Cardano Node: Submit transaction: \n""POST /delegation/{delegation-cert} -Wallet->dApp Frontend: ""SignedDelegationCertificate"" \ No newline at end of file +Wallet->dApp Frontend: ""SignedDelegationCertificate"" diff --git a/docs/architecture/sequence-diagrams/raw/drep-registration.txt b/docs/architecture/sequence-diagrams/raw/drep-registration.txt index eac9db47a..5e701117f 100644 --- a/docs/architecture/sequence-diagrams/raw/drep-registration.txt +++ b/docs/architecture/sequence-diagrams/raw/drep-registration.txt @@ -12,4 +12,4 @@ dApp Frontend->Wallet: Pass certificate to wallet:\n""API.submitDRepRegistration Wallet->User: Ask permission popup (Wallet UI) User->Wallet: Access granted (Wallet UI) Wallet->Cardano Node: Submit transaction: \n""POST /registration/{registration-cert} -Wallet->dApp Frontend: ""SignedDRepRegistrationCertificate"" \ No newline at end of file +Wallet->dApp Frontend: ""SignedDRepRegistrationCertificate"" diff --git a/docs/architecture/sequence-diagrams/raw/drep-retirement.txt b/docs/architecture/sequence-diagrams/raw/drep-retirement.txt index cbd99a2a4..99a43a9ae 100644 --- a/docs/architecture/sequence-diagrams/raw/drep-retirement.txt +++ b/docs/architecture/sequence-diagrams/raw/drep-retirement.txt @@ -11,4 +11,4 @@ dApp Frontend->Wallet: Pass certificate to wallet:\n""API.submitDRepRetirementCe Wallet->User (dRep): Ask permission popup (Wallet UI) User (dRep)->Wallet: Access granted (Wallet UI) Wallet->Cardano Node: Submit transaction: \n""POST /retirement/{retirement-cert} -Wallet->dApp Frontend: ""SignedDRepRetirementCertificate"" \ No newline at end of file +Wallet->dApp Frontend: ""SignedDRepRetirementCertificate"" diff --git a/docs/architecture/sequence-diagrams/raw/drep-status.txt b/docs/architecture/sequence-diagrams/raw/drep-status.txt index 398a99bdb..e668837c8 100644 --- a/docs/architecture/sequence-diagrams/raw/drep-status.txt +++ b/docs/architecture/sequence-diagrams/raw/drep-status.txt @@ -8,4 +8,4 @@ dApp Frontend->Wallet: ""API.getDRepKey()"" Wallet->dApp Frontend: ""pubDRepKey"" dApp Frontend->dApp Backend: ""GET drep/{pubDRepKey} -dApp Backend->dApp Frontend: ""bool"" \ No newline at end of file +dApp Backend->dApp Frontend: ""bool"" diff --git a/docs/architecture/sequence-diagrams/raw/login.txt b/docs/architecture/sequence-diagrams/raw/login.txt index 8340cc37a..4a772d98a 100644 --- a/docs/architecture/sequence-diagrams/raw/login.txt +++ b/docs/architecture/sequence-diagrams/raw/login.txt @@ -33,4 +33,4 @@ Wallet->dApp Frontend: ""pubDRepKey"" dApp Frontend->dApp Backend: ""GET drep/{pubDRepKey} dApp Backend->dApp Frontend: ""DRepCert"" -dApp Frontend->User: Serve correct UI \ No newline at end of file +dApp Frontend->User: Serve correct UI diff --git a/docs/architecture/sequence-diagrams/raw/voting.txt b/docs/architecture/sequence-diagrams/raw/voting.txt index 2c311ed14..1767720c7 100644 --- a/docs/architecture/sequence-diagrams/raw/voting.txt +++ b/docs/architecture/sequence-diagrams/raw/voting.txt @@ -13,4 +13,4 @@ dApp Frontend->Wallet: Pass object to wallet:\n""API.submitVote(Vote)"" Wallet->User (dRep): Ask permission popup (Wallet UI) User (dRep)->Wallet: Access granted (Wallet UI) Wallet->Cardano Node: Submit transaction: \n""POST /vote/{vote} -Wallet->dApp Frontend: ""SignedVote"" \ No newline at end of file +Wallet->dApp Frontend: ""SignedVote"" diff --git a/docs/architecture/sequence-diagrams/raw/wallet-connect.txt b/docs/architecture/sequence-diagrams/raw/wallet-connect.txt index 3119dc48c..6348fbce9 100644 --- a/docs/architecture/sequence-diagrams/raw/wallet-connect.txt +++ b/docs/architecture/sequence-diagrams/raw/wallet-connect.txt @@ -10,4 +10,4 @@ User->dApp Frontend: Wallet Selected ""walletName dApp Frontend->Wallet: ""cardano.{walletName}.enable({"cip": ?}) Wallet->User: Ask permission popup (Wallet UI) User->Wallet: Access granted (Wallet UI) -Wallet->dApp Frontend: ""API"" object \ No newline at end of file +Wallet->dApp Frontend: ""API"" object diff --git a/docs/operations/README.md b/docs/operations/README.md index bfe06e2dc..4b6ab139f 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -64,4 +64,3 @@ Deploying new versions of the application is done using Github actions 4. From the droping options - "Run workflow", select the branch, Cardano network and type of environment for your deployment 5. Press "Run workflow" 6. Wait for the final effect. It's done. - diff --git a/flake.lock b/flake.lock index 176d430c2..fb4213fd1 100644 --- a/flake.lock +++ b/flake.lock @@ -1,6 +1,6 @@ { "nodes": { - "nixpkgs": { + "default_nixpkgs": { "locked": { "lastModified": 1679410443, "narHash": "sha256-xDHO/jixWD+y5pmW5+2q4Z4O/I/nA4MAa30svnZKK+M=", @@ -16,6 +16,24 @@ "type": "github" } }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "node_nixpkgs": { "locked": { "lastModified": 1696748673, @@ -34,9 +52,25 @@ }, "root": { "inputs": { - "nixpkgs": "nixpkgs", + "default_nixpkgs": "default_nixpkgs", + "flake-utils": "flake-utils", "node_nixpkgs": "node_nixpkgs" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 922d4d454..e732205ec 100644 --- a/flake.nix +++ b/flake.nix @@ -1,27 +1,22 @@ { description = "GovTool and utilities monorepo."; - inputs.nixpkgs.url = - "github:nixos/nixpkgs/c9ece0059f42e0ab53ac870104ca4049df41b133"; - inputs.node_nixpkgs.url = - "github:nixos/nixpkgs/9957cd48326fe8dbd52fdc50dd2502307f188b0d"; + inputs.default_nixpkgs.url = "github:nixos/nixpkgs/c9ece0059f42e0ab53ac870104ca4049df41b133"; + inputs.node_nixpkgs.url = "github:nixos/nixpkgs/9957cd48326fe8dbd52fdc50dd2502307f188b0d"; + inputs.flake-utils.url = "github:numtide/flake-utils"; - outputs = { self, nixpkgs, node_nixpkgs }: { - packages = let - supportedSystems = - [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; - in builtins.listToAttrs (map (system: + outputs = { self, default_nixpkgs, node_nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: let - pkgs = import nixpkgs { inherit system; config.allowBroken = true; }; - npkgs = import node_nixpkgs { inherit system; }; - in { - name = system; - value = { - scripts = pkgs.callPackage ./scripts/govtool { inherit pkgs; }; - infra = pkgs.callPackage ./infra/terraform { inherit pkgs; }; - backend = pkgs.callPackage ./govtool/backend { inherit pkgs; }; - frontend = pkgs.callPackage ./govtool/frontend { pkgs = npkgs; }; - }; - }) supportedSystems); - }; + defaultPkgs = import default_nixpkgs { inherit system; config.allowBroken = true; }; + nodePkgs = import node_nixpkgs { inherit system; }; + in + { + packages.scripts = defaultPkgs.callPackage ./scripts/govtool { pkgs = defaultPkgs; }; + packages.infra = defaultPkgs.callPackage ./infra/terraform { pkgs = defaultPkgs; }; + packages.backend = defaultPkgs.callPackage ./govtool/backend { pkgs = defaultPkgs; }; + packages.frontend = nodePkgs.callPackage ./govtool/frontend { pkgs = nodePkgs; }; + + devShell = defaultPkgs.mkShell { buildInputs = [ defaultPkgs.pre-commit ]; }; + }); } diff --git a/gov-action-loader/backend/.env.example b/gov-action-loader/backend/.env.example index a9b58410d..654ec757e 100644 --- a/gov-action-loader/backend/.env.example +++ b/gov-action-loader/backend/.env.example @@ -4,4 +4,3 @@ KUBER_API_KEY=xxxxxxxxxxxxx ## Not required anymore BLOCKFROST_API_URL= BLOCKFROST_PROJECT_ID= - diff --git a/gov-action-loader/backend/.gitignore b/gov-action-loader/backend/.gitignore index 69188450f..4c79d15bd 100644 --- a/gov-action-loader/backend/.gitignore +++ b/gov-action-loader/backend/.gitignore @@ -2,4 +2,4 @@ .venv venv .vscode -__pycache__ \ No newline at end of file +__pycache__ diff --git a/gov-action-loader/backend/Dockerfile b/gov-action-loader/backend/Dockerfile index 3115980da..d0ced0e73 100644 --- a/gov-action-loader/backend/Dockerfile +++ b/gov-action-loader/backend/Dockerfile @@ -13,4 +13,4 @@ COPY ./requirements.txt ./requirements.txt RUN pip install --no-cache-dir --upgrade -r ./requirements.txt COPY . /app EXPOSE 8000 -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/gov-action-loader/backend/README.md b/gov-action-loader/backend/README.md index ef636cd86..52cf07d6a 100644 --- a/gov-action-loader/backend/README.md +++ b/gov-action-loader/backend/README.md @@ -9,10 +9,10 @@ This repository helps in creation of Conway era transactions containing proposa - Sanchonet Faucet API ### Limitations -Gov action loader backend instance uses fixed set of wallet to perform transactions. This means that gov action loader can be used by only 1 user at a time. +Gov action loader backend instance uses fixed set of wallet to perform transactions. This means that gov action loader can be used by only 1 user at a time. -## 1. Setup +## 1. Setup ``` python3 -m venv ./venv @@ -22,7 +22,7 @@ pip install -r requirements.txt ## 1. Initialize Wallets -### Prerequisite +### Prerequisite - You should have sancho-node, kuber server runnning and cardano-cli available. For bulk proposal creation , multiple wallets must be generated. Generate them with following command. @@ -34,4 +34,4 @@ python3 wallets.py ## 2. Start the server - Add .env file with all the keys from .env.example -- Run `uvicorn app.main:app --reload --env-file .env` in the repository to run the program \ No newline at end of file +- Run `uvicorn app.main:app --reload --env-file .env` in the repository to run the program diff --git a/gov-action-loader/backend/requirements.txt b/gov-action-loader/backend/requirements.txt index 7f65dd75c..65e8a2cea 100644 --- a/gov-action-loader/backend/requirements.txt +++ b/gov-action-loader/backend/requirements.txt @@ -4,4 +4,4 @@ uvicorn[standard] pydantic-settings httpx python-dotenv -pycardano \ No newline at end of file +pycardano diff --git a/gov-action-loader/backend/wallets.json b/gov-action-loader/backend/wallets.json index 5ec44eff8..22c4a3f9b 100644 --- a/gov-action-loader/backend/wallets.json +++ b/gov-action-loader/backend/wallets.json @@ -1 +1 @@ -[{"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200a2e6f6f040636a56b61276487459b9e9f309d09d1d57319b41ed32f3053af8d"}, "address": "addr_test1qzc97zml9xzhm7xcsmqretkk7ztzyehj3dpd7ph7h0s40wp0ea9f3e353pmmr7yxv7m2dj09rn44m7pvd0m4cylusn8szlm75t"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205a06b43ec9c2b0569e7ab12d34d54238b08dad06c46b37acc4cb560a41dd1b0a"}, "address": "addr_test1qpar26cedvu2adsseslkvedd8p0spdyah9xugr956mrczj2lhcns9veuwczx5zymn3mwfzz43zetmx6rxc9lyxuquvpq5c283g"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204e0d6d81b89cca3fc8d7a34c0e6e034154b7bfeaa63c20d8fb87a104ebe7cb7a"}, "address": "addr_test1qz5urft08hklx5enx8apfsl2uwrfyk82h6yfhhhkzusv6chgqq0lvpajj4j6239dv0eg8fkyphfzdh4e2ewq0px4u4ssmky45z"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208af08d653745b206a093cc3f8dfc560277b36ca9e629a2fd310eb80fb0cbf3bc"}, "address": "addr_test1qqxzhl9ry49mcjn6az9aafwyvnfsnyhz4wf5furvd85yxkww66wl944qdle7sdt6m0ueq43u3txz9c30tt53q0ca6zfqqsdkxp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582013a95f98b763a2913bc7e3d8a553b41cd63d37065c5b18e8b9560051aa2e0f5a"}, "address": "addr_test1qr4nrsdwl2n02tmgg7ervdjmsqteed4h9qk3klfxulx699z7hhekyju28ph48l3qszz6el3dy2euu8h9r5erzt52dkjq382zgd"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820958c3ae65c29af651bb779efcef724134b0889bc967d8a7409008cb16d0013c3"}, "address": "addr_test1qzras6eud27wnf376hhshldx0h3zwy2667ph98w3qzpmk4p88936qrse6wfdxmkdrj9ta2a2zh9pa5qd42za5830mw6q7jzx0h"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820b9bde7b6f67f5271787d7a6f0b65f3385b3c8a9ae75736374006372d60048517"}, "address": "addr_test1qqn0vcanqqz7779qyk54kcffc6zgdume5r3342693cyzzek94w4xjn6vpw4eddugs8hmw6vuw9x226a07fv3ryhefvmqffjmpv"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203bf861d3b48380e6b1b3b6c6b5ca127fcba0b805f8755d67ca9ccb396e6aae05"}, "address": "addr_test1qr04s52teaxy5vyxdatvvyadmf3va4n2f0g4996qqwd0j0nsmc7dqrz7ajt7ug6f0t8rs374utz7w7x38q0dqqzqvpwqz92j3h"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582093ccc3b4bca81828f0f6dd7d38f02b7bdbb7c4b81426ec448b02b6d0d17fb007"}, "address": "addr_test1qzec537cq9grnv2jqgaclhdgeaw0p9qkez6j4g4ay3qpgvxxeucwgy4g26dqg6pmvwqmmmg7utrv403xgl2dqfvw9x7qd5r8g4"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820584a10e1b5736b2298611c4ecadd382b1113f4220162761a82f493d9c63966c7"}, "address": "addr_test1qrw28zy637s5nfznsz2fs5ftrh89dy8zk3see7tnnrcs5ukcuy85nuhlkml63rps7w52wjpm2fjz5msywwjwc6kffdmsucmdey"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208062f1160a44b4e992addd08797ccc043525298a8e04834b68fb8e5d509eb9f1"}, "address": "addr_test1qplakcg2ucd92xtkn07apd96v9d8cqjj3w94k9nr9vcj0runvpfgqp2neczllccl7ymwvglh9905ryykkq6s58fuchtqayd6td"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c4fc3936e9b1f8b076a554a99c61376a43a1105e4e946441e1e2a0fd431b5da3"}, "address": "addr_test1qpcqkm9e2n0uwdqm9jf8d5faqyrf3n7hcj8y67zkxgzzhu5f4vttf8wkx4jx7uszc75mfsralzf8wrqp2xkqq4yxg88qtfvr7l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209023f71c570758e01a83332a4ddef1ac3a396cbe5dc96293924178e5aa73a85e"}, "address": "addr_test1qpvrjdc7frapmlgyztlxq3mlk0mtlx4adc87eqf85fhxf2ehcvf8npp5sd596jr8myt80pz2cq28h3cam4m9w7x9m8essj5hjn"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ad567690944c74b711f2bd6c403c0266aeb33ef569fde5e929a66ac4578940bb"}, "address": "addr_test1qr5grdfutlxekmg2jdy320u5zky8dvlrpggcup4mnjarwqann9j4q8ylmr3q7t8vppzsp6x4jspgaapwla47ehgl249srupqh8"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203b536bb6939143c3643e55c45b19b46dfd93ccbe7d999b490b87910c67f8f5ce"}, "address": "addr_test1qq6wxgslzpzc9emduyyvwk4ptz8cqjac8tgr7n5dh9a0mng99rg3d5sznefewv9dng9ry3qyf9fqp26qnq3pma6zygns44j798"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582055a933fe1696fe8b80b80eeb9841e8e5cbc72705b898bef725f7a690f9cf2902"}, "address": "addr_test1qp0uzvrarx2m7gx7v3nt0u2afsre3zemmc3heycuawznj8z3dgxjm2ps8nswpsnktgezrfqlz4e8tz3cfgfvl0adfy6qzyvf5x"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820eb5486a25e7acb113a6b49eb311d81b8b27fd1c9295139b0fb4e1ea886e9a333"}, "address": "addr_test1qrfjufl2vuxhqyvkxxtpfw0za25c999p8zmdvqtyhv66nnn7trnwvgeqm047vaplnmzjhtejd3792rs8jc0ru5sayjcq5wnn8p"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820eb2a0425a2cbc76ab42aa99749e078ee0aedaef0f0519ff14e5fe35dbf14283e"}, "address": "addr_test1qpyj98rkr87wg9zhxyp756fecqvk4dkkwna0hdk5fwx5gknplkjcxhjqr9kc57m3p8vcdw742xrkuvudwylhg8hc5lhqegn70p"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582099e860b4e1cda64a81fe0d61efe359428a0625287067d9d6f3baaf18897a6d95"}, "address": "addr_test1qpzy9yv4kpja5szr0ar8saxg4pttckm4n250grvgpdey6xrjmupujmcefgtxgtuk4ffwj6e43lnde7dhepn7p9pv0zgq4lck4l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820fcd30b8e9387bc932e1361767cb9338ee62583fbccdd4b968820dab092d4b50e"}, "address": "addr_test1qpelegk5t7t2j0du5lpkgkm5grpe298cqjv7wcufh2nf852etawk7dzeeuhncw9pweuamfun9cwt6ju9kruuwn0yelqqmkstrr"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208eb7443ae318c03dc925c1c7a317ed4e308f14f5d0d78579896a344b06d8911d"}, "address": "addr_test1qq8h2294gstt78hwx3tnm68fp88q4l9v9tnwemrl6r9sygzpqdd4cfegjg0zg97z0ux9yykm9wq5wrsm878jddtp0dks3u3xfs"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820aa68ea9835b1f731fb66f86eb7129e1a78b837b172f6d93b81911f4e7dc2ec15"}, "address": "addr_test1qpq8tq56aly64xr3sptjrnm435hsaawztfxxpmpze00a3r9y6zu07qac8y33ad5hlf37s2tv4emzrykf54hpe8vsfpnsfaulzp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582079cfa42cddca931a91aa3f33e019bf6c114064d6032d9612253e2406f836b390"}, "address": "addr_test1qzz8gpvw73ja6k8e8etlzpyu4lxzhwpk3598vg3cy7spyxuxqxnaf5p4kxzkhh67y8msaqhrg3qm2smz7nt2740stf2q44lxcy"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ab077b26077859b02161e00747cca6fe1c305cb399fb55a7afb3b76db8ece0e3"}, "address": "addr_test1qr3u4j5uqzxxz2882r4chehkxxdlezwdestgtcgh05tjz2lyr84rk32j46cjmzn7ptqju72l33nxz7hcmyz7dwj9c44qmea74d"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820bbe285ba6305c11e8be4570ec7af912bbb7ac84d81ea013eeca7ae4422d2448d"}, "address": "addr_test1qpay2kzw0pcxlvue03c6lv52f956lwgn8fj5a3dxj7n3ncvp6kyv6pnlq289r28ks9rveewwndsyvm3lvf47k9qwhazqjjvx27"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820227fbb23046db689b1212575824f0b60ff0192594993db19356755362d1f05cd"}, "address": "addr_test1qqeyqxwde7ua0aeurtdu36zh5rys60ctwqnu2cq36ss2d9uqfd5kz3twjh6pepq8dv9r9hd2yve9j54v9ragngl93xlsjgkkpx"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820fb718b4d6d3943fdc780cc6243bc2d7458470e41e10a3dbd4b33f8856c268fa7"}, "address": "addr_test1qrt5nf8y3d7q4wz0hk5ak5n4n9zvryg2qhd6znreq8g5vppct0hx6ce86ufy7xkja5mnvjyy5e90g2pawtnsvmhdemkq7082lt"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582083b107c5f516a79f87874f1227a7f605ed118427a8718d7e8eef9f5b9e2175f1"}, "address": "addr_test1qqvz895af8s5n3glpmtppdpgh6p023upeyw5ty6cn6al3sd67tmrkhjh824s2cyer2ul66v9cpf3j7gd5g5d8ksrx48q3qvt4k"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e72d4bc4ba8b3f996874b9233353a180f1ab96ae5ee3c163415456eb7dc9c309"}, "address": "addr_test1qrezun5rxdqn57z6ez4srgwy40m95mvd366aglyzcks9v650xndqkjavz5q3ee7l89fpfdmz6830hzjv9jhxg8kc7qeqnqh74s"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582008efb0c23cee254223e7670a26182f6fae2b85bb61be7ac2b73b4a649d83ec29"}, "address": "addr_test1qrgsjqg5nl5he4qmrm40cgapw26jey5xp36z86s4dp6y0ql8atma6d2dnqt9m3zxm6crgjj2epetcw8t54wdzlrmq8mq76ldtk"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200064710c686465feffeb57eaadf9ed04b72b569aa6addb7a2ce0d524bda756d4"}, "address": "addr_test1qpjjlqjeqhgqh92tu4ydskkkz0qyayg747p4lhxlxpqsrnz77jz3yw4s4erdkchwf23cy7l2hyeqwflnemfazfacwt7s2q6map"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582060f70d9122bbffea0d72a6a4158b9101f69c7bbab3cfc4335588a291613ca667"}, "address": "addr_test1qzszut7ncw6trjj0aufd6f5ev5cthalf6ft6anwgtqnpach96lq6nn63lvypemq567q68n3je6quj2da44naugprkufseqlydt"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820db0bdab4fc037516b8e1892e8c22ba577943ecfccb31f3a2c24b749d95a09f69"}, "address": "addr_test1qq5288yeeyyu03c9dyyze0mwvp33acjt0dhe0uklyehhuxzlt3jq9fzd5f67jr4n7gvt8s2sul6cgwprsp5zut0srn5qnkugxe"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d9d68af1a51c9c738a0d637f238ba14687e25e276aadbff0cd42a811659213cd"}, "address": "addr_test1qq57krgrudegs37tc9g785vqhtg7nclnafvkq7gthzx40rwxs2fgfdry0vg4jv54met9kq2nyds4s49fa04yzunqjyuspe4h0n"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582014de642b103132b51bfe8e0a63b53d10acae7eda49ffc2f2256455e3ed5852e3"}, "address": "addr_test1qphzfecrcklg7xty0daecgjsrqdvh0ul4d7az5p4l8vctcmme8avdyepm68xjz7wk9g0xn5drpyskqm7vngzf3lfugxsw4m9ta"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a9e0dcdd0b0bd8ca64a8b192a9e3b09154a9d6f5387f5a8d6de988ac76e0ba48"}, "address": "addr_test1qq2wepd76vnslm2m7ql0lmy3secg6cxv4j64epp7adgsq55hepl7l0fzjcq0qkwe9zd4h3khggs90m4rj2ehyxdvdesqe0tkpa"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ae2adcbd3cee1ad398a391cb1240ea7f6086dfaa2219b0cb77552814918ec134"}, "address": "addr_test1qq2sxtrzyv8grup9k3aaqsdmrmpzspywdfcpqxzrmy4pe6yd8y6q3tv3f3dly5zpneh2r7f3vq0z0r8xzqj5ldpjpj9s4mhakg"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203f5f055a14a03098a90359a1dc3de8e9806aec7f385cac762afe2639f69c6215"}, "address": "addr_test1qzgt2pkj2w5lqw5xsdln0ysvyqksfmkdvm26pm4kte4hme2m0cd2mewx3d3q6rjjl2yrvehzxaxqur32fm77vkkxzeqqts5rzz"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582051ba4bdab4e70750bb88f3fc3175dca03e8ec84c3d5f0e082db2c66cd5f92e46"}, "address": "addr_test1qpwg8pncfyrpka3n2g094rnsesghy6x6t89jrmk5q3mvekfa93vhxrjmnxrl3hzluqw8jrpec4rr873lg0g35c0ac9rszrkqa9"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203dc963b39006273a5de79deb47daeda8b1991684825be02e43a198c58daf1add"}, "address": "addr_test1qzgrqf8sxu2gyfggew5csn3j5yl9h8au7n5aa7pcn9ntgwnwjl4m4khdt88nnrneeguj8zvmg06r0l9775gntusa5k6su5mnf4"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205d97c9e2bbaf10f105f102811ba3558fcde0fe830de0217ce2253cc00656ceba"}, "address": "addr_test1qrvcm5jhz6ehduydre0jnsjpx7dppjr38vdwy4f2ncywkhacpphlkrqxmtg560ercrm4uqv35ev62sg4rtw26ms7cyes2t8s4y"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a62cd87a7c9463c30d499cc0c6e29171ec00cd0d46e695de74dfaca5b5b5050d"}, "address": "addr_test1qp5dju8pfcmqd67a4qf4lsmvqh85hhpdutxyltpg8n5syjrkx6g2q3ap5c8rf0xxh8g974tne7qqfyjee2qu69dzlw3q0kzjys"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f77e255c72c51c28a2a4f7145a6010109201cb3083cf2d2bbaf7f80ef66faae2"}, "address": "addr_test1qqgj39e6md45dl8m9s7yaqwwcz7sr5ufpz54pzpjl3wsgwcvrkvg96r3wdtc674xexgtxqt2xdg43qq5vu4r32gznv8s245z6r"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820918c8dc3d23f0579982b389418b4d412a86dc9077461e50a9b7b1f581472461e"}, "address": "addr_test1qqed9hjwtrhn2v3h6dzr59jgddxlv3s0pn4uva9pz9kza5tvm5sjzgle30q4dkwxsy0n6u72h5x86pt5sv82d9lwwe7s9455at"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d8b39df9090e185663517490e204aabff5aa745118c77fb772a0df71b555732f"}, "address": "addr_test1qzddjjt4f5j8qm3vqggx9qw5suryv796jxcc5nhklh90dsgq538xxj49neh2lk8kr2x3skyj9vvgj24hx6l5awu2zrdqkg38zw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582040d166554488456a7b8a9bdd879eb52c116918954a86313636aaa02d09a768b0"}, "address": "addr_test1qzpucx5wmq07jrfj0d8cce9fwpxykvhxw8uydkmuek9zr0ctvq2j2hcvf8zrq2qws3jphtyelac49xy6c7eysrvdsmmqzpwrun"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a3999baf0647f8a8132b21a17cb92bd7f61394c9417a0a06ab17fb4f37baa9ac"}, "address": "addr_test1qpglea3wj58kfr7t7jejrjhe6ha9q5y0kryq6xk8ucghs3xfjl8jh342ua529rkrr42r2gyattfhtv24eu5gz2cvmrqsarhh8d"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f3016ec0282d157f3f47595dbc91d0998ec4ef714b128f5cc4a6d49a44df6af0"}, "address": "addr_test1qzx6m5wpzfu4fwjeaxtvvc3sphj4xsvhvnf5zkydfj8kcdrnzshl6ew27jxzy0t3f8d8cqfmh64c6pmguspp7lpktfpsplk4sv"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582009ad5b0e4bcfbf2f6ddcd6b41d13a6a1d073766c2b8134448fd77065684287c8"}, "address": "addr_test1qq4sa8xxg4kts09uug8adm44vgue5h6ul45n0uka5hg63lar2pfdxfddamm6pq9sqz2qy40lwf49hzme3xe8klj4y46svefmpd"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582021154504b195ce79f26c1716d487ec96638c4b824c71e3c418249f4b18a5e592"}, "address": "addr_test1qztre66cmy4tfva38t9j580qxvf3v058qcdw79a3sq0pcnx3xg5ksg2068llk42rda9ds8n0j06fvnl6hgm9ae8y99zsrwve3z"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c2cb7bf7a900cb4b20eec08f50a4eae760ee951d8a5e9c68ff0451694624db9d"}, "address": "addr_test1qpuc6jdqpp5puyzjt7qz2e3s9ln6ecfgsvwz30rqprq0sjvv9zvs6rp7sdy6qcudskwp6q6h5pvk5n9nrn6t7l35h5lss8ylwe"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204b16b9439028ff25e235f7b2d8ad0481d08172bb0c0b23964a2acb772ff7927b"}, "address": "addr_test1qp3hywfjjyxhfs7ja7eaf52wsyptsvutq4gwmgm9xgvc0r27f2futth8ef8sz8w3m088cugu3cgraa2vvl3alrefksdqalyfgz"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582064aca5869e08afc130bdac07723f60dbe937aaf07f2651eeba886c2efc29e4aa"}, "address": "addr_test1qpu0daxkk4f4lqwx2v0vzvcx3gtavz4wzrghx62zpl5zp5ht39d56ryy0h4x847u9678fq8xx6u4yq4kx00nejkfm6wseq56uh"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208b8851a4e0a807637ff02ab22e19de925d049e43840c36b75b3a4cb45b4491f4"}, "address": "addr_test1qqk0280rtx729tksvlah3lswmx9s4sssgctfsq2lf3fsggjm4l7xf0hks0yj0sjfw5ynchec20efktejmtqh3x0zsxwq3w356l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582070f96c0f1f66e835097551e61c4064834dfe75480762aeca2d9bd1b86fc1ba7d"}, "address": "addr_test1qrw5edpp3dvgs99vmqta7rvxed9ej2hlayqkd8rmvvlp96dy5m97ypz6em8fwxtk9cdgxy8whvxv0v3709xvnuf0fj4qwq2vts"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f4e0e9beb3aaa75344f31135bcc260445edada24108b9dca0b54b0de336e1f92"}, "address": "addr_test1qzc540h2xvvhe4347gasx9k3ykta2gqn277qcxy7plssz8kgm52z8rpe9s4uhhwz8kemnuawhhvw8w5axtmu0wp5c3lsshl4dn"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820658f01aafdd8c2855a9eec25a2a7b6beb3eaaa78bba61733e5229979fe4e9ac0"}, "address": "addr_test1qr6px93r5jlam5lr7huq3um55asy7dc9grautya3znx6fj6fhst6gmnc4mkhmc2gu9ffdrax7fnu2d69zf049w3d5dcsdp3c0j"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c15b71f08481eee1419f6b4ceb7d778d3e7d1bfe8748ca08be8d9b39c8b89776"}, "address": "addr_test1qz8xlya86v60lpchn5wl806wl52jjpupywycg2dyuttjke4gdqkyfpcq38dq5ccx2pe9jg56lqw74gy597k7upfthz3q0yv6s6"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200a8903b013478226c0323723ca68340e8b04c56b38870462c0d9ed46788a828b"}, "address": "addr_test1qp2xxse9u7yc0fzg4q08cqdaumsqklx3tf99yxpec5wlsejmptgu9uygexx0zlz6gk4e3p20zdg7tk3szk5rtk7auk9qx2el88"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d2f4a534f7a5b6f8e53e169a5a6c515a66a7ca9da0f33c748bd31147cca877df"}, "address": "addr_test1qqnem7ma2hd5zemysmd56mrqs5wfv7ygpjzwe02prgwwntn7mcdz7tmy0eyv2swaavmps5gwytsekmzl890favp6th3qgstzjy"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820748eba6fb1929fdd437eddd43871c4525a78996c7dd68a2093a2d4a1dcd42d5e"}, "address": "addr_test1qzj9qnd8x03ngsv29v6hcdxwmkprfkx929zkhh5j92gf6qza2x7nfsyk2p5ul3vjnsmcvlr86vqse07jp6kgn6n64a4qgrz78n"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d5c398a03ac485050d1f5efef92cb04294b90950fa8fa0de2a63d6730be3a43e"}, "address": "addr_test1qqsrjsvga9ctmw06y3z4hn4hrzp2zqaa2xdnl5tpr9dmjamn8yylvtknlr9pt398x5hkff3pqekkhkvgmfatfpmypdgqfz8zxm"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207045c6e00bc31243e086c67731debfbbb7d749a41a64345b953d8283649a16fc"}, "address": "addr_test1qqfj0armf9nvh2sl0zr8ujx534h3ukngpx3ewkldg4ea6mp95xrwkvsm64j0tsr63hresvu6n0e2a67s92edxntah2uq4uz5kp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820df37410977298dc6c7d25920d9ce0f03e872c36cda3bc3ca4d4e50aaa17e72bf"}, "address": "addr_test1qq9cka5g8nh6qv5tt3ngd0k0em89ve8h36vlyuakkyw8e73ugrvcqzv93v5xszyh7xdeujnk5hv9gvhleazku7mqxq3smr5c68"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820b2a71fb7f7c8767d3fc38318fb0cccfac2c7b6577c26dd427a2b2d9bfdf8e604"}, "address": "addr_test1qzt2hsddymfe7svypf3tnepgrhtmw5c9dqshx0kdty4pc90ngr5tuyz5dl79qwnn98kjnsm02s47886e0zyfpchruyyqstfl8f"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582001db82f567f769eebef32ee9bd81b3c8c16630a46ae4b106f4c7447f7441786f"}, "address": "addr_test1qqv23gu9s4fpjaxzxvjnqqcdzy3ugf4cn29d8tg570shfxckpl9vtp3s9kyh8a7m2m3gwuw445t62ya36p0suaypavlqtw38uf"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582035a8819c5fd816504c7679c7d4e0b3c5b12e765eacc6eae0b535b4aed33dd465"}, "address": "addr_test1qrd5dkpv9u54sm994raskcq4p3vy43h0e3hv58mvjeskszt3fa3ce2wsaq57dyhl02nr836u7l65sg4u22na4zkwtq9smfx9ks"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582055f57e773105c3910c309fae354f7cf49399fc099dff8280ca768ea265f321e6"}, "address": "addr_test1qqh97jsyx6e3h2ryj8cp5ftuy2pc2udfury08fhzwduvlgd6gxl5u4y4a5kxm7455axv2sm050e5mxezdnjx72sntxdq29nkme"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204c365ba833942147100e2987c12fdf1cb4da566452245cf9e4f922c4e8d1ad26"}, "address": "addr_test1qpyyrsqfpfvs3j4uhnlgptmsz39raakw5j7s7npzp9nrxhq08d2jy8d5cpzhq8ung9zsy529elpc4am09auuc404z6hsupgld7"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e9376890bb088e1e935d53d65f18f8faa75bfb79803e27a1a650135988230097"}, "address": "addr_test1qz2r9mu9y357uk6mhkswk54m3cz5le6chrytvv5jhxau4v5zffpncl3pamnzmk2a28ndcep7ad2x88xeudw0z4ua5xzsa6t5ay"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207c233befb95bc5067a6c43626d07182d80cd1aad8127aed991fa00f79f0b6cec"}, "address": "addr_test1qqch4fw78p2wkce8kppmhehakvt75vxjlky65wlkx8seyvwu3zkkh646cvapf683vt0q4uhhncxwl0egswfypsfjfzlq5jmpjr"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e432d6c88e281bf9d3bc178685bb799b1ff57527ba452620343463a321219e8a"}, "address": "addr_test1qpln286ymus9qu677me28vmhs2qezszk92jcnrhu9tp6arkyuzrdpy690jh9kxkx2wn0teqce07npjysajpzpns05xqsn5rer2"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d09b3df5dfe773cbca11cfb4c5c46abcd29b95b6a4cf5ae6fee35a5ffb4bd1b2"}, "address": "addr_test1qqhvg099hufuzqmlf0h3mk08u4cf9v4yxmgs33lq0d7htu4wp24xh2e5z6t00sq5f6n2xk284v2qjj3uyuuk9mremxpsqc2tc5"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e2085aa055bb690ddd69f4bca8016e83542671196e87d3c2ec72991b50141fc8"}, "address": "addr_test1qrz3ny4cedtrqgav3kteh2kzl5zegqyzpqw8whxu3ae5dw3z47r0xr7de68ruf5ml4kgz3a4r6ud5f75z5re4hlvzlpsge64tm"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820325706b6e17b3d2d2f829c0aa7ffd0bfd36516ef4f3cf594e5f8731d9846e1a4"}, "address": "addr_test1qp062p78ygykumn0z0ahadfgrudwgphzwhx5c43r8dppa6axv6t079zsgur9jca5569ju2mlvvhyzsscjfgnpg9829pskjs8nw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582032c843a12240d13196b5d55ac9060dc8ec5c45b0f10af8f93f462106dab772dd"}, "address": "addr_test1qr8e7ur4fhyk82z2uy3m29wpdk0ce99q39rk594h4c8z3suvpu3q7hekae3uvpvteqwwswqz6vqz52str3g55r0l8acsz7sgf5"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c5ec97c726432f4fc7d03d063c1c0c1c5c46145ab6ee176dd17a4c57723a0263"}, "address": "addr_test1qpysucqwdh0vruz4mmk7r46jhc74yf9zgempapmq7t0k99ugy3pd786mmksclcqwtjp6khnqmsprx67ula3n9ll429xq3n837v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c79cb3dfde7fda9747f067c56dbaa175f04788478f74b324c6537a638c158535"}, "address": "addr_test1qzqntczre92n7ls7slw6hqdsqcsznrvm8f0v6wk9e5urvqpasld8cv8n8r0m5z7uwd2zxml83fszf0e0q8py2zjw4ppsmvjv6a"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820203e9ca1f5c01bf639d0d213a88da67df87be9aa51fad637fd2ac9466489df03"}, "address": "addr_test1qzstlc6223c833evt0u859pl6y3y427aprqgzthlvjh36za9242rttc7st3rtvny784pge9test9703fs8chf34y4jksp6u825"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820cfccbd81c30d126b41fab9a0fa93ea11d8693bda0b4fae1fd21c61792a7ad44e"}, "address": "addr_test1qqwj54jdqn5a9xcxrp989qfpsuyrpvkh03mh8363tlwwqzm89znufs474n6lyhn5nfnlp4mtvff3ua2mvg87f52gvpeqazmwh7"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582032194ee0832c1a8ada6e1c1f6c9ab50bd99aeb0797269994761bd6564fe47587"}, "address": "addr_test1qzt5lp69ecndampnuvvtvcnayh9j8a3cqphe7fu986gfnjvn2ryncytd7dtyxhug8eu2hp0mmj39h6zr5syz09268p6sev935r"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820aa4fbdd2fac33ccb890254275ae1ca1926e2569c9fa6f64a782182a8c8f8984d"}, "address": "addr_test1qqmh0xzy2cgy8swvg7yynt3ghxhhgqjf45agms3mhusxjrxmrxzuexd6wl0c38jyka3weg0pwfe0593a68ad0h3l6xpswzm78v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209086ca689ee57c3adb4d7bf6fcacd7c60237bb7b925c0d7d33a8e32b5fce858f"}, "address": "addr_test1qr4sx93tm3t930lzp9jyu6yq836rl89fp5dp0zpq707t9ftw0ftqrp3aa60c04l3t3raneagavk8g63j3y7c6cgqn0rqjsdft2"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207687a37cb998a6ef6660560f6c2a08e2e362952cfdb92ff909252fe64fb6818f"}, "address": "addr_test1qqt08k40mf935y8gsa2qww8fp6rqq4xw39ahdpydlvlra9x4fks6fwxg7fyqgf8638vk7fjy2dlknvfj99yc2ed424mqy225qw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58201f49e375c440373c43ff6d97e78886508561c36780cbf69aeb5548726b24656a"}, "address": "addr_test1qqpd46mpzgg2r5pd7vummtja4tasnce4vls5874n0wz5uvntgw7e47798xg5s6fqk3tx2wfszrm02jrtytwek92d44rsryv578"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208e06736a19a49be41d0567628bb6918c48801e6aafaf90dc3880ecef7f4bbc21"}, "address": "addr_test1qz30ckh2m5fyxhjrxc40dtjr5w0sk2fehtfvfkwrkh8gzjlps772z664fan2kjhmtvtepryeendhnz2wh549ng9e8g4qh3vmfl"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820663c75b93138abef03aec209a887a635e1b324cdc253e00ef61fbacc3a1c6bf8"}, "address": "addr_test1qp3rpg4klp4t95tpzzf495yg7lxnehrxljw3qrd5thzehwu5kdqttj400uwkthyhjy62fqhksuvy7dcenhkf3x83825qwnkmde"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582069f95ed1cd495d87dced3deda7b534833fa1bf4095a4d10cb7b73f67a5c1d281"}, "address": "addr_test1qq3s9mejjepwdnguu0r0gflvjvvcqrlr4lfkuplg8ad2j2cjhl4ang6khtt798wyzy5htsxqmt68fyrj2ffy7w6yt3xq6wkqzq"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e41830b1774b12952ff1ca7a210ed0ced58b44fc4f727ed907c50df522d7c725"}, "address": "addr_test1qqx34xfamcj8cp2xv8t889nwksd6htk7ly6ufgdughm52mknmv4ur49c6f2edt070qzmgjm0f9k89wza39c9jd7hk2hq448pyl"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582066fa28ed6153583c292b4466b546d499cbb672ee2ad20623d354c70a7b88ff51"}, "address": "addr_test1qq58ju8a370q7n4ulxujrssk6c98eszuumuvxyy8vc76nl7zywanfjzcp2mad75jm55esz8waulq7thz90ps3x30txks3duz5v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582018dd4bb9f015554f4c46df02dfa4683fd49f20497151faaa19a93fd4b9f570b8"}, "address": "addr_test1qzjtr80cvne0yx8vuzvrw2l4cha0kzqhhfzxujxhkfrrwd8pad76hjgvxwjswxnaee6kwvu80f4ug6n7ly69lzl7e74sdasyqc"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58201539b8bb6a1a4fd7390cf4eb576ff7338dea6a7595ed3d9121f4ea82a1696275"}, "address": "addr_test1qremwlucnak007zx57gmqm6yxcm3yy7gf6dqlw560djvk9nfln0uprtncqwkry7epvyw63mn7zm5neeulq5wfgreyj7s8xh7x8"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582050469db716b7d5a38373f6b300a676d7fc55f2afb0e9948271c937a00c355905"}, "address": "addr_test1qrs0vfk8fwzfsdzksx4ywjnre45r54g9peafk5p792ehs9mgtsj95je6vfqnw4a4sdjdff8t4d4c4dk7fh3tjg9ldmqs4k7h44"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205af4b5a5b29878c0a91f7a66b9d420dfd450ff3cec6142b975de99c2265eb71c"}, "address": "addr_test1qz43u6sh4d3hjh236q6as4cxs6jer8r38kyp62376c79uy9nlnhgv6jgl98huhpk43zekc4unsmd494sefl4pka0hleqnl4l2a"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820526f9ce860319985a47b696a57943cf98d6c4cf187f0f66f5cf3b8c671507a07"}, "address": "addr_test1qrm6h58kx02tdr73vjl0eg0dqqvwtgecxwkeefgrt6qznr473yv3x2kutgfk9ywrezspgzz4ed4dzg52f78qz2z0zu4s7tmdsh"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e6648a1c07f763f85e0c871794da73dd4f29df60e6716038e9fe0ca24a8b767e"}, "address": "addr_test1qryv7sl3rdrfsu0ls0r5dmzmy9c7v76hcuha72wexjg5pxkdf49eglluc3ft9v6fnxk7pnheplxm9k299eqycj6wmzgq39yp4u"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209ddcc8ba672d9f655bb1ef6f09ef1ee5ad1771fe818821fb6bee45fb1a73d55f"}, "address": "addr_test1qptydp0la6hfpr7f3dye47nsvulh3p26at7rnw04tacwnqpuhlrdly8cr8jsc93h3l43caql9jhp709wdzq65pgj4ths8wku4q"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200b0d49a4bb4abf9aa811eb09b034c107d6df0e4b220aad55a9ed52c174586377"}, "address": "addr_test1qpyjhtp5y3hlfg5dl87ks809w5gtthzv79x066ynfax233w95xlrrmtq943mma9nmmkh00j6z9jxlera0haz7c27864q33uj55"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d5165e766d21c90f29407b15b15318e21dc0894800f8e54cc115f8433555e0ce"}, "address": "addr_test1qqskuewyq2hslehw60jam988au60ddljvwv2wgkf77h4ufvkcfqfz9wv3spgjjz6wcynsfs79jzxdufkmy23v9qvluvqvzmukq"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208635957b0163e296f6c09f0261c464af9de08b8597b5d6f7d84917103044e7af"}, "address": "addr_test1qpzredcujhqnm2en3yn70cykpqs97sqecwzysrz3nxedkaxur3nak5j59tl9ammjk7c50zmquecu8ea8kt7x29s3ndkqsm85h4"}] \ No newline at end of file +[{"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200a2e6f6f040636a56b61276487459b9e9f309d09d1d57319b41ed32f3053af8d"}, "address": "addr_test1qzc97zml9xzhm7xcsmqretkk7ztzyehj3dpd7ph7h0s40wp0ea9f3e353pmmr7yxv7m2dj09rn44m7pvd0m4cylusn8szlm75t"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205a06b43ec9c2b0569e7ab12d34d54238b08dad06c46b37acc4cb560a41dd1b0a"}, "address": "addr_test1qpar26cedvu2adsseslkvedd8p0spdyah9xugr956mrczj2lhcns9veuwczx5zymn3mwfzz43zetmx6rxc9lyxuquvpq5c283g"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204e0d6d81b89cca3fc8d7a34c0e6e034154b7bfeaa63c20d8fb87a104ebe7cb7a"}, "address": "addr_test1qz5urft08hklx5enx8apfsl2uwrfyk82h6yfhhhkzusv6chgqq0lvpajj4j6239dv0eg8fkyphfzdh4e2ewq0px4u4ssmky45z"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208af08d653745b206a093cc3f8dfc560277b36ca9e629a2fd310eb80fb0cbf3bc"}, "address": "addr_test1qqxzhl9ry49mcjn6az9aafwyvnfsnyhz4wf5furvd85yxkww66wl944qdle7sdt6m0ueq43u3txz9c30tt53q0ca6zfqqsdkxp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582013a95f98b763a2913bc7e3d8a553b41cd63d37065c5b18e8b9560051aa2e0f5a"}, "address": "addr_test1qr4nrsdwl2n02tmgg7ervdjmsqteed4h9qk3klfxulx699z7hhekyju28ph48l3qszz6el3dy2euu8h9r5erzt52dkjq382zgd"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820958c3ae65c29af651bb779efcef724134b0889bc967d8a7409008cb16d0013c3"}, "address": "addr_test1qzras6eud27wnf376hhshldx0h3zwy2667ph98w3qzpmk4p88936qrse6wfdxmkdrj9ta2a2zh9pa5qd42za5830mw6q7jzx0h"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820b9bde7b6f67f5271787d7a6f0b65f3385b3c8a9ae75736374006372d60048517"}, "address": "addr_test1qqn0vcanqqz7779qyk54kcffc6zgdume5r3342693cyzzek94w4xjn6vpw4eddugs8hmw6vuw9x226a07fv3ryhefvmqffjmpv"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203bf861d3b48380e6b1b3b6c6b5ca127fcba0b805f8755d67ca9ccb396e6aae05"}, "address": "addr_test1qr04s52teaxy5vyxdatvvyadmf3va4n2f0g4996qqwd0j0nsmc7dqrz7ajt7ug6f0t8rs374utz7w7x38q0dqqzqvpwqz92j3h"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582093ccc3b4bca81828f0f6dd7d38f02b7bdbb7c4b81426ec448b02b6d0d17fb007"}, "address": "addr_test1qzec537cq9grnv2jqgaclhdgeaw0p9qkez6j4g4ay3qpgvxxeucwgy4g26dqg6pmvwqmmmg7utrv403xgl2dqfvw9x7qd5r8g4"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820584a10e1b5736b2298611c4ecadd382b1113f4220162761a82f493d9c63966c7"}, "address": "addr_test1qrw28zy637s5nfznsz2fs5ftrh89dy8zk3see7tnnrcs5ukcuy85nuhlkml63rps7w52wjpm2fjz5msywwjwc6kffdmsucmdey"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208062f1160a44b4e992addd08797ccc043525298a8e04834b68fb8e5d509eb9f1"}, "address": "addr_test1qplakcg2ucd92xtkn07apd96v9d8cqjj3w94k9nr9vcj0runvpfgqp2neczllccl7ymwvglh9905ryykkq6s58fuchtqayd6td"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c4fc3936e9b1f8b076a554a99c61376a43a1105e4e946441e1e2a0fd431b5da3"}, "address": "addr_test1qpcqkm9e2n0uwdqm9jf8d5faqyrf3n7hcj8y67zkxgzzhu5f4vttf8wkx4jx7uszc75mfsralzf8wrqp2xkqq4yxg88qtfvr7l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209023f71c570758e01a83332a4ddef1ac3a396cbe5dc96293924178e5aa73a85e"}, "address": "addr_test1qpvrjdc7frapmlgyztlxq3mlk0mtlx4adc87eqf85fhxf2ehcvf8npp5sd596jr8myt80pz2cq28h3cam4m9w7x9m8essj5hjn"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ad567690944c74b711f2bd6c403c0266aeb33ef569fde5e929a66ac4578940bb"}, "address": "addr_test1qr5grdfutlxekmg2jdy320u5zky8dvlrpggcup4mnjarwqann9j4q8ylmr3q7t8vppzsp6x4jspgaapwla47ehgl249srupqh8"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203b536bb6939143c3643e55c45b19b46dfd93ccbe7d999b490b87910c67f8f5ce"}, "address": "addr_test1qq6wxgslzpzc9emduyyvwk4ptz8cqjac8tgr7n5dh9a0mng99rg3d5sznefewv9dng9ry3qyf9fqp26qnq3pma6zygns44j798"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582055a933fe1696fe8b80b80eeb9841e8e5cbc72705b898bef725f7a690f9cf2902"}, "address": "addr_test1qp0uzvrarx2m7gx7v3nt0u2afsre3zemmc3heycuawznj8z3dgxjm2ps8nswpsnktgezrfqlz4e8tz3cfgfvl0adfy6qzyvf5x"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820eb5486a25e7acb113a6b49eb311d81b8b27fd1c9295139b0fb4e1ea886e9a333"}, "address": "addr_test1qrfjufl2vuxhqyvkxxtpfw0za25c999p8zmdvqtyhv66nnn7trnwvgeqm047vaplnmzjhtejd3792rs8jc0ru5sayjcq5wnn8p"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820eb2a0425a2cbc76ab42aa99749e078ee0aedaef0f0519ff14e5fe35dbf14283e"}, "address": "addr_test1qpyj98rkr87wg9zhxyp756fecqvk4dkkwna0hdk5fwx5gknplkjcxhjqr9kc57m3p8vcdw742xrkuvudwylhg8hc5lhqegn70p"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582099e860b4e1cda64a81fe0d61efe359428a0625287067d9d6f3baaf18897a6d95"}, "address": "addr_test1qpzy9yv4kpja5szr0ar8saxg4pttckm4n250grvgpdey6xrjmupujmcefgtxgtuk4ffwj6e43lnde7dhepn7p9pv0zgq4lck4l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820fcd30b8e9387bc932e1361767cb9338ee62583fbccdd4b968820dab092d4b50e"}, "address": "addr_test1qpelegk5t7t2j0du5lpkgkm5grpe298cqjv7wcufh2nf852etawk7dzeeuhncw9pweuamfun9cwt6ju9kruuwn0yelqqmkstrr"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208eb7443ae318c03dc925c1c7a317ed4e308f14f5d0d78579896a344b06d8911d"}, "address": "addr_test1qq8h2294gstt78hwx3tnm68fp88q4l9v9tnwemrl6r9sygzpqdd4cfegjg0zg97z0ux9yykm9wq5wrsm878jddtp0dks3u3xfs"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820aa68ea9835b1f731fb66f86eb7129e1a78b837b172f6d93b81911f4e7dc2ec15"}, "address": "addr_test1qpq8tq56aly64xr3sptjrnm435hsaawztfxxpmpze00a3r9y6zu07qac8y33ad5hlf37s2tv4emzrykf54hpe8vsfpnsfaulzp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582079cfa42cddca931a91aa3f33e019bf6c114064d6032d9612253e2406f836b390"}, "address": "addr_test1qzz8gpvw73ja6k8e8etlzpyu4lxzhwpk3598vg3cy7spyxuxqxnaf5p4kxzkhh67y8msaqhrg3qm2smz7nt2740stf2q44lxcy"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ab077b26077859b02161e00747cca6fe1c305cb399fb55a7afb3b76db8ece0e3"}, "address": "addr_test1qr3u4j5uqzxxz2882r4chehkxxdlezwdestgtcgh05tjz2lyr84rk32j46cjmzn7ptqju72l33nxz7hcmyz7dwj9c44qmea74d"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820bbe285ba6305c11e8be4570ec7af912bbb7ac84d81ea013eeca7ae4422d2448d"}, "address": "addr_test1qpay2kzw0pcxlvue03c6lv52f956lwgn8fj5a3dxj7n3ncvp6kyv6pnlq289r28ks9rveewwndsyvm3lvf47k9qwhazqjjvx27"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820227fbb23046db689b1212575824f0b60ff0192594993db19356755362d1f05cd"}, "address": "addr_test1qqeyqxwde7ua0aeurtdu36zh5rys60ctwqnu2cq36ss2d9uqfd5kz3twjh6pepq8dv9r9hd2yve9j54v9ragngl93xlsjgkkpx"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820fb718b4d6d3943fdc780cc6243bc2d7458470e41e10a3dbd4b33f8856c268fa7"}, "address": "addr_test1qrt5nf8y3d7q4wz0hk5ak5n4n9zvryg2qhd6znreq8g5vppct0hx6ce86ufy7xkja5mnvjyy5e90g2pawtnsvmhdemkq7082lt"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582083b107c5f516a79f87874f1227a7f605ed118427a8718d7e8eef9f5b9e2175f1"}, "address": "addr_test1qqvz895af8s5n3glpmtppdpgh6p023upeyw5ty6cn6al3sd67tmrkhjh824s2cyer2ul66v9cpf3j7gd5g5d8ksrx48q3qvt4k"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e72d4bc4ba8b3f996874b9233353a180f1ab96ae5ee3c163415456eb7dc9c309"}, "address": "addr_test1qrezun5rxdqn57z6ez4srgwy40m95mvd366aglyzcks9v650xndqkjavz5q3ee7l89fpfdmz6830hzjv9jhxg8kc7qeqnqh74s"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582008efb0c23cee254223e7670a26182f6fae2b85bb61be7ac2b73b4a649d83ec29"}, "address": "addr_test1qrgsjqg5nl5he4qmrm40cgapw26jey5xp36z86s4dp6y0ql8atma6d2dnqt9m3zxm6crgjj2epetcw8t54wdzlrmq8mq76ldtk"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200064710c686465feffeb57eaadf9ed04b72b569aa6addb7a2ce0d524bda756d4"}, "address": "addr_test1qpjjlqjeqhgqh92tu4ydskkkz0qyayg747p4lhxlxpqsrnz77jz3yw4s4erdkchwf23cy7l2hyeqwflnemfazfacwt7s2q6map"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582060f70d9122bbffea0d72a6a4158b9101f69c7bbab3cfc4335588a291613ca667"}, "address": "addr_test1qzszut7ncw6trjj0aufd6f5ev5cthalf6ft6anwgtqnpach96lq6nn63lvypemq567q68n3je6quj2da44naugprkufseqlydt"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820db0bdab4fc037516b8e1892e8c22ba577943ecfccb31f3a2c24b749d95a09f69"}, "address": "addr_test1qq5288yeeyyu03c9dyyze0mwvp33acjt0dhe0uklyehhuxzlt3jq9fzd5f67jr4n7gvt8s2sul6cgwprsp5zut0srn5qnkugxe"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d9d68af1a51c9c738a0d637f238ba14687e25e276aadbff0cd42a811659213cd"}, "address": "addr_test1qq57krgrudegs37tc9g785vqhtg7nclnafvkq7gthzx40rwxs2fgfdry0vg4jv54met9kq2nyds4s49fa04yzunqjyuspe4h0n"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582014de642b103132b51bfe8e0a63b53d10acae7eda49ffc2f2256455e3ed5852e3"}, "address": "addr_test1qphzfecrcklg7xty0daecgjsrqdvh0ul4d7az5p4l8vctcmme8avdyepm68xjz7wk9g0xn5drpyskqm7vngzf3lfugxsw4m9ta"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a9e0dcdd0b0bd8ca64a8b192a9e3b09154a9d6f5387f5a8d6de988ac76e0ba48"}, "address": "addr_test1qq2wepd76vnslm2m7ql0lmy3secg6cxv4j64epp7adgsq55hepl7l0fzjcq0qkwe9zd4h3khggs90m4rj2ehyxdvdesqe0tkpa"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ae2adcbd3cee1ad398a391cb1240ea7f6086dfaa2219b0cb77552814918ec134"}, "address": "addr_test1qq2sxtrzyv8grup9k3aaqsdmrmpzspywdfcpqxzrmy4pe6yd8y6q3tv3f3dly5zpneh2r7f3vq0z0r8xzqj5ldpjpj9s4mhakg"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203f5f055a14a03098a90359a1dc3de8e9806aec7f385cac762afe2639f69c6215"}, "address": "addr_test1qzgt2pkj2w5lqw5xsdln0ysvyqksfmkdvm26pm4kte4hme2m0cd2mewx3d3q6rjjl2yrvehzxaxqur32fm77vkkxzeqqts5rzz"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582051ba4bdab4e70750bb88f3fc3175dca03e8ec84c3d5f0e082db2c66cd5f92e46"}, "address": "addr_test1qpwg8pncfyrpka3n2g094rnsesghy6x6t89jrmk5q3mvekfa93vhxrjmnxrl3hzluqw8jrpec4rr873lg0g35c0ac9rszrkqa9"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203dc963b39006273a5de79deb47daeda8b1991684825be02e43a198c58daf1add"}, "address": "addr_test1qzgrqf8sxu2gyfggew5csn3j5yl9h8au7n5aa7pcn9ntgwnwjl4m4khdt88nnrneeguj8zvmg06r0l9775gntusa5k6su5mnf4"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205d97c9e2bbaf10f105f102811ba3558fcde0fe830de0217ce2253cc00656ceba"}, "address": "addr_test1qrvcm5jhz6ehduydre0jnsjpx7dppjr38vdwy4f2ncywkhacpphlkrqxmtg560ercrm4uqv35ev62sg4rtw26ms7cyes2t8s4y"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a62cd87a7c9463c30d499cc0c6e29171ec00cd0d46e695de74dfaca5b5b5050d"}, "address": "addr_test1qp5dju8pfcmqd67a4qf4lsmvqh85hhpdutxyltpg8n5syjrkx6g2q3ap5c8rf0xxh8g974tne7qqfyjee2qu69dzlw3q0kzjys"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f77e255c72c51c28a2a4f7145a6010109201cb3083cf2d2bbaf7f80ef66faae2"}, "address": "addr_test1qqgj39e6md45dl8m9s7yaqwwcz7sr5ufpz54pzpjl3wsgwcvrkvg96r3wdtc674xexgtxqt2xdg43qq5vu4r32gznv8s245z6r"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820918c8dc3d23f0579982b389418b4d412a86dc9077461e50a9b7b1f581472461e"}, "address": "addr_test1qqed9hjwtrhn2v3h6dzr59jgddxlv3s0pn4uva9pz9kza5tvm5sjzgle30q4dkwxsy0n6u72h5x86pt5sv82d9lwwe7s9455at"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d8b39df9090e185663517490e204aabff5aa745118c77fb772a0df71b555732f"}, "address": "addr_test1qzddjjt4f5j8qm3vqggx9qw5suryv796jxcc5nhklh90dsgq538xxj49neh2lk8kr2x3skyj9vvgj24hx6l5awu2zrdqkg38zw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582040d166554488456a7b8a9bdd879eb52c116918954a86313636aaa02d09a768b0"}, "address": "addr_test1qzpucx5wmq07jrfj0d8cce9fwpxykvhxw8uydkmuek9zr0ctvq2j2hcvf8zrq2qws3jphtyelac49xy6c7eysrvdsmmqzpwrun"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a3999baf0647f8a8132b21a17cb92bd7f61394c9417a0a06ab17fb4f37baa9ac"}, "address": "addr_test1qpglea3wj58kfr7t7jejrjhe6ha9q5y0kryq6xk8ucghs3xfjl8jh342ua529rkrr42r2gyattfhtv24eu5gz2cvmrqsarhh8d"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f3016ec0282d157f3f47595dbc91d0998ec4ef714b128f5cc4a6d49a44df6af0"}, "address": "addr_test1qzx6m5wpzfu4fwjeaxtvvc3sphj4xsvhvnf5zkydfj8kcdrnzshl6ew27jxzy0t3f8d8cqfmh64c6pmguspp7lpktfpsplk4sv"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582009ad5b0e4bcfbf2f6ddcd6b41d13a6a1d073766c2b8134448fd77065684287c8"}, "address": "addr_test1qq4sa8xxg4kts09uug8adm44vgue5h6ul45n0uka5hg63lar2pfdxfddamm6pq9sqz2qy40lwf49hzme3xe8klj4y46svefmpd"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582021154504b195ce79f26c1716d487ec96638c4b824c71e3c418249f4b18a5e592"}, "address": "addr_test1qztre66cmy4tfva38t9j580qxvf3v058qcdw79a3sq0pcnx3xg5ksg2068llk42rda9ds8n0j06fvnl6hgm9ae8y99zsrwve3z"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c2cb7bf7a900cb4b20eec08f50a4eae760ee951d8a5e9c68ff0451694624db9d"}, "address": "addr_test1qpuc6jdqpp5puyzjt7qz2e3s9ln6ecfgsvwz30rqprq0sjvv9zvs6rp7sdy6qcudskwp6q6h5pvk5n9nrn6t7l35h5lss8ylwe"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204b16b9439028ff25e235f7b2d8ad0481d08172bb0c0b23964a2acb772ff7927b"}, "address": "addr_test1qp3hywfjjyxhfs7ja7eaf52wsyptsvutq4gwmgm9xgvc0r27f2futth8ef8sz8w3m088cugu3cgraa2vvl3alrefksdqalyfgz"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582064aca5869e08afc130bdac07723f60dbe937aaf07f2651eeba886c2efc29e4aa"}, "address": "addr_test1qpu0daxkk4f4lqwx2v0vzvcx3gtavz4wzrghx62zpl5zp5ht39d56ryy0h4x847u9678fq8xx6u4yq4kx00nejkfm6wseq56uh"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208b8851a4e0a807637ff02ab22e19de925d049e43840c36b75b3a4cb45b4491f4"}, "address": "addr_test1qqk0280rtx729tksvlah3lswmx9s4sssgctfsq2lf3fsggjm4l7xf0hks0yj0sjfw5ynchec20efktejmtqh3x0zsxwq3w356l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582070f96c0f1f66e835097551e61c4064834dfe75480762aeca2d9bd1b86fc1ba7d"}, "address": "addr_test1qrw5edpp3dvgs99vmqta7rvxed9ej2hlayqkd8rmvvlp96dy5m97ypz6em8fwxtk9cdgxy8whvxv0v3709xvnuf0fj4qwq2vts"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f4e0e9beb3aaa75344f31135bcc260445edada24108b9dca0b54b0de336e1f92"}, "address": "addr_test1qzc540h2xvvhe4347gasx9k3ykta2gqn277qcxy7plssz8kgm52z8rpe9s4uhhwz8kemnuawhhvw8w5axtmu0wp5c3lsshl4dn"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820658f01aafdd8c2855a9eec25a2a7b6beb3eaaa78bba61733e5229979fe4e9ac0"}, "address": "addr_test1qr6px93r5jlam5lr7huq3um55asy7dc9grautya3znx6fj6fhst6gmnc4mkhmc2gu9ffdrax7fnu2d69zf049w3d5dcsdp3c0j"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c15b71f08481eee1419f6b4ceb7d778d3e7d1bfe8748ca08be8d9b39c8b89776"}, "address": "addr_test1qz8xlya86v60lpchn5wl806wl52jjpupywycg2dyuttjke4gdqkyfpcq38dq5ccx2pe9jg56lqw74gy597k7upfthz3q0yv6s6"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200a8903b013478226c0323723ca68340e8b04c56b38870462c0d9ed46788a828b"}, "address": "addr_test1qp2xxse9u7yc0fzg4q08cqdaumsqklx3tf99yxpec5wlsejmptgu9uygexx0zlz6gk4e3p20zdg7tk3szk5rtk7auk9qx2el88"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d2f4a534f7a5b6f8e53e169a5a6c515a66a7ca9da0f33c748bd31147cca877df"}, "address": "addr_test1qqnem7ma2hd5zemysmd56mrqs5wfv7ygpjzwe02prgwwntn7mcdz7tmy0eyv2swaavmps5gwytsekmzl890favp6th3qgstzjy"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820748eba6fb1929fdd437eddd43871c4525a78996c7dd68a2093a2d4a1dcd42d5e"}, "address": "addr_test1qzj9qnd8x03ngsv29v6hcdxwmkprfkx929zkhh5j92gf6qza2x7nfsyk2p5ul3vjnsmcvlr86vqse07jp6kgn6n64a4qgrz78n"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d5c398a03ac485050d1f5efef92cb04294b90950fa8fa0de2a63d6730be3a43e"}, "address": "addr_test1qqsrjsvga9ctmw06y3z4hn4hrzp2zqaa2xdnl5tpr9dmjamn8yylvtknlr9pt398x5hkff3pqekkhkvgmfatfpmypdgqfz8zxm"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207045c6e00bc31243e086c67731debfbbb7d749a41a64345b953d8283649a16fc"}, "address": "addr_test1qqfj0armf9nvh2sl0zr8ujx534h3ukngpx3ewkldg4ea6mp95xrwkvsm64j0tsr63hresvu6n0e2a67s92edxntah2uq4uz5kp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820df37410977298dc6c7d25920d9ce0f03e872c36cda3bc3ca4d4e50aaa17e72bf"}, "address": "addr_test1qq9cka5g8nh6qv5tt3ngd0k0em89ve8h36vlyuakkyw8e73ugrvcqzv93v5xszyh7xdeujnk5hv9gvhleazku7mqxq3smr5c68"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820b2a71fb7f7c8767d3fc38318fb0cccfac2c7b6577c26dd427a2b2d9bfdf8e604"}, "address": "addr_test1qzt2hsddymfe7svypf3tnepgrhtmw5c9dqshx0kdty4pc90ngr5tuyz5dl79qwnn98kjnsm02s47886e0zyfpchruyyqstfl8f"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582001db82f567f769eebef32ee9bd81b3c8c16630a46ae4b106f4c7447f7441786f"}, "address": "addr_test1qqv23gu9s4fpjaxzxvjnqqcdzy3ugf4cn29d8tg570shfxckpl9vtp3s9kyh8a7m2m3gwuw445t62ya36p0suaypavlqtw38uf"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582035a8819c5fd816504c7679c7d4e0b3c5b12e765eacc6eae0b535b4aed33dd465"}, "address": "addr_test1qrd5dkpv9u54sm994raskcq4p3vy43h0e3hv58mvjeskszt3fa3ce2wsaq57dyhl02nr836u7l65sg4u22na4zkwtq9smfx9ks"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582055f57e773105c3910c309fae354f7cf49399fc099dff8280ca768ea265f321e6"}, "address": "addr_test1qqh97jsyx6e3h2ryj8cp5ftuy2pc2udfury08fhzwduvlgd6gxl5u4y4a5kxm7455axv2sm050e5mxezdnjx72sntxdq29nkme"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204c365ba833942147100e2987c12fdf1cb4da566452245cf9e4f922c4e8d1ad26"}, "address": "addr_test1qpyyrsqfpfvs3j4uhnlgptmsz39raakw5j7s7npzp9nrxhq08d2jy8d5cpzhq8ung9zsy529elpc4am09auuc404z6hsupgld7"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e9376890bb088e1e935d53d65f18f8faa75bfb79803e27a1a650135988230097"}, "address": "addr_test1qz2r9mu9y357uk6mhkswk54m3cz5le6chrytvv5jhxau4v5zffpncl3pamnzmk2a28ndcep7ad2x88xeudw0z4ua5xzsa6t5ay"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207c233befb95bc5067a6c43626d07182d80cd1aad8127aed991fa00f79f0b6cec"}, "address": "addr_test1qqch4fw78p2wkce8kppmhehakvt75vxjlky65wlkx8seyvwu3zkkh646cvapf683vt0q4uhhncxwl0egswfypsfjfzlq5jmpjr"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e432d6c88e281bf9d3bc178685bb799b1ff57527ba452620343463a321219e8a"}, "address": "addr_test1qpln286ymus9qu677me28vmhs2qezszk92jcnrhu9tp6arkyuzrdpy690jh9kxkx2wn0teqce07npjysajpzpns05xqsn5rer2"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d09b3df5dfe773cbca11cfb4c5c46abcd29b95b6a4cf5ae6fee35a5ffb4bd1b2"}, "address": "addr_test1qqhvg099hufuzqmlf0h3mk08u4cf9v4yxmgs33lq0d7htu4wp24xh2e5z6t00sq5f6n2xk284v2qjj3uyuuk9mremxpsqc2tc5"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e2085aa055bb690ddd69f4bca8016e83542671196e87d3c2ec72991b50141fc8"}, "address": "addr_test1qrz3ny4cedtrqgav3kteh2kzl5zegqyzpqw8whxu3ae5dw3z47r0xr7de68ruf5ml4kgz3a4r6ud5f75z5re4hlvzlpsge64tm"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820325706b6e17b3d2d2f829c0aa7ffd0bfd36516ef4f3cf594e5f8731d9846e1a4"}, "address": "addr_test1qp062p78ygykumn0z0ahadfgrudwgphzwhx5c43r8dppa6axv6t079zsgur9jca5569ju2mlvvhyzsscjfgnpg9829pskjs8nw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582032c843a12240d13196b5d55ac9060dc8ec5c45b0f10af8f93f462106dab772dd"}, "address": "addr_test1qr8e7ur4fhyk82z2uy3m29wpdk0ce99q39rk594h4c8z3suvpu3q7hekae3uvpvteqwwswqz6vqz52str3g55r0l8acsz7sgf5"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c5ec97c726432f4fc7d03d063c1c0c1c5c46145ab6ee176dd17a4c57723a0263"}, "address": "addr_test1qpysucqwdh0vruz4mmk7r46jhc74yf9zgempapmq7t0k99ugy3pd786mmksclcqwtjp6khnqmsprx67ula3n9ll429xq3n837v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c79cb3dfde7fda9747f067c56dbaa175f04788478f74b324c6537a638c158535"}, "address": "addr_test1qzqntczre92n7ls7slw6hqdsqcsznrvm8f0v6wk9e5urvqpasld8cv8n8r0m5z7uwd2zxml83fszf0e0q8py2zjw4ppsmvjv6a"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820203e9ca1f5c01bf639d0d213a88da67df87be9aa51fad637fd2ac9466489df03"}, "address": "addr_test1qzstlc6223c833evt0u859pl6y3y427aprqgzthlvjh36za9242rttc7st3rtvny784pge9test9703fs8chf34y4jksp6u825"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820cfccbd81c30d126b41fab9a0fa93ea11d8693bda0b4fae1fd21c61792a7ad44e"}, "address": "addr_test1qqwj54jdqn5a9xcxrp989qfpsuyrpvkh03mh8363tlwwqzm89znufs474n6lyhn5nfnlp4mtvff3ua2mvg87f52gvpeqazmwh7"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582032194ee0832c1a8ada6e1c1f6c9ab50bd99aeb0797269994761bd6564fe47587"}, "address": "addr_test1qzt5lp69ecndampnuvvtvcnayh9j8a3cqphe7fu986gfnjvn2ryncytd7dtyxhug8eu2hp0mmj39h6zr5syz09268p6sev935r"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820aa4fbdd2fac33ccb890254275ae1ca1926e2569c9fa6f64a782182a8c8f8984d"}, "address": "addr_test1qqmh0xzy2cgy8swvg7yynt3ghxhhgqjf45agms3mhusxjrxmrxzuexd6wl0c38jyka3weg0pwfe0593a68ad0h3l6xpswzm78v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209086ca689ee57c3adb4d7bf6fcacd7c60237bb7b925c0d7d33a8e32b5fce858f"}, "address": "addr_test1qr4sx93tm3t930lzp9jyu6yq836rl89fp5dp0zpq707t9ftw0ftqrp3aa60c04l3t3raneagavk8g63j3y7c6cgqn0rqjsdft2"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207687a37cb998a6ef6660560f6c2a08e2e362952cfdb92ff909252fe64fb6818f"}, "address": "addr_test1qqt08k40mf935y8gsa2qww8fp6rqq4xw39ahdpydlvlra9x4fks6fwxg7fyqgf8638vk7fjy2dlknvfj99yc2ed424mqy225qw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58201f49e375c440373c43ff6d97e78886508561c36780cbf69aeb5548726b24656a"}, "address": "addr_test1qqpd46mpzgg2r5pd7vummtja4tasnce4vls5874n0wz5uvntgw7e47798xg5s6fqk3tx2wfszrm02jrtytwek92d44rsryv578"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208e06736a19a49be41d0567628bb6918c48801e6aafaf90dc3880ecef7f4bbc21"}, "address": "addr_test1qz30ckh2m5fyxhjrxc40dtjr5w0sk2fehtfvfkwrkh8gzjlps772z664fan2kjhmtvtepryeendhnz2wh549ng9e8g4qh3vmfl"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820663c75b93138abef03aec209a887a635e1b324cdc253e00ef61fbacc3a1c6bf8"}, "address": "addr_test1qp3rpg4klp4t95tpzzf495yg7lxnehrxljw3qrd5thzehwu5kdqttj400uwkthyhjy62fqhksuvy7dcenhkf3x83825qwnkmde"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582069f95ed1cd495d87dced3deda7b534833fa1bf4095a4d10cb7b73f67a5c1d281"}, "address": "addr_test1qq3s9mejjepwdnguu0r0gflvjvvcqrlr4lfkuplg8ad2j2cjhl4ang6khtt798wyzy5htsxqmt68fyrj2ffy7w6yt3xq6wkqzq"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e41830b1774b12952ff1ca7a210ed0ced58b44fc4f727ed907c50df522d7c725"}, "address": "addr_test1qqx34xfamcj8cp2xv8t889nwksd6htk7ly6ufgdughm52mknmv4ur49c6f2edt070qzmgjm0f9k89wza39c9jd7hk2hq448pyl"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582066fa28ed6153583c292b4466b546d499cbb672ee2ad20623d354c70a7b88ff51"}, "address": "addr_test1qq58ju8a370q7n4ulxujrssk6c98eszuumuvxyy8vc76nl7zywanfjzcp2mad75jm55esz8waulq7thz90ps3x30txks3duz5v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582018dd4bb9f015554f4c46df02dfa4683fd49f20497151faaa19a93fd4b9f570b8"}, "address": "addr_test1qzjtr80cvne0yx8vuzvrw2l4cha0kzqhhfzxujxhkfrrwd8pad76hjgvxwjswxnaee6kwvu80f4ug6n7ly69lzl7e74sdasyqc"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58201539b8bb6a1a4fd7390cf4eb576ff7338dea6a7595ed3d9121f4ea82a1696275"}, "address": "addr_test1qremwlucnak007zx57gmqm6yxcm3yy7gf6dqlw560djvk9nfln0uprtncqwkry7epvyw63mn7zm5neeulq5wfgreyj7s8xh7x8"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582050469db716b7d5a38373f6b300a676d7fc55f2afb0e9948271c937a00c355905"}, "address": "addr_test1qrs0vfk8fwzfsdzksx4ywjnre45r54g9peafk5p792ehs9mgtsj95je6vfqnw4a4sdjdff8t4d4c4dk7fh3tjg9ldmqs4k7h44"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205af4b5a5b29878c0a91f7a66b9d420dfd450ff3cec6142b975de99c2265eb71c"}, "address": "addr_test1qz43u6sh4d3hjh236q6as4cxs6jer8r38kyp62376c79uy9nlnhgv6jgl98huhpk43zekc4unsmd494sefl4pka0hleqnl4l2a"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820526f9ce860319985a47b696a57943cf98d6c4cf187f0f66f5cf3b8c671507a07"}, "address": "addr_test1qrm6h58kx02tdr73vjl0eg0dqqvwtgecxwkeefgrt6qznr473yv3x2kutgfk9ywrezspgzz4ed4dzg52f78qz2z0zu4s7tmdsh"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e6648a1c07f763f85e0c871794da73dd4f29df60e6716038e9fe0ca24a8b767e"}, "address": "addr_test1qryv7sl3rdrfsu0ls0r5dmzmy9c7v76hcuha72wexjg5pxkdf49eglluc3ft9v6fnxk7pnheplxm9k299eqycj6wmzgq39yp4u"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209ddcc8ba672d9f655bb1ef6f09ef1ee5ad1771fe818821fb6bee45fb1a73d55f"}, "address": "addr_test1qptydp0la6hfpr7f3dye47nsvulh3p26at7rnw04tacwnqpuhlrdly8cr8jsc93h3l43caql9jhp709wdzq65pgj4ths8wku4q"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200b0d49a4bb4abf9aa811eb09b034c107d6df0e4b220aad55a9ed52c174586377"}, "address": "addr_test1qpyjhtp5y3hlfg5dl87ks809w5gtthzv79x066ynfax233w95xlrrmtq943mma9nmmkh00j6z9jxlera0haz7c27864q33uj55"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d5165e766d21c90f29407b15b15318e21dc0894800f8e54cc115f8433555e0ce"}, "address": "addr_test1qqskuewyq2hslehw60jam988au60ddljvwv2wgkf77h4ufvkcfqfz9wv3spgjjz6wcynsfs79jzxdufkmy23v9qvluvqvzmukq"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208635957b0163e296f6c09f0261c464af9de08b8597b5d6f7d84917103044e7af"}, "address": "addr_test1qpzredcujhqnm2en3yn70cykpqs97sqecwzysrz3nxedkaxur3nak5j59tl9ammjk7c50zmquecu8ea8kt7x29s3ndkqsm85h4"}] diff --git a/gov-action-loader/frontend/.env.example b/gov-action-loader/frontend/.env.example index dff54b8f7..ca3203c85 100644 --- a/gov-action-loader/frontend/.env.example +++ b/gov-action-loader/frontend/.env.example @@ -1,3 +1,3 @@ VITE_DATA_LOADER_API=https://vva-governance.cardanoapi.io -VITE_VVA_WEBAPP_URL=https://vva-be.cardanoapi.io \ No newline at end of file +VITE_VVA_WEBAPP_URL=https://vva-be.cardanoapi.io diff --git a/gov-action-loader/frontend/.gitignore b/gov-action-loader/frontend/.gitignore index 1cac5597e..7ceb59f89 100644 --- a/gov-action-loader/frontend/.gitignore +++ b/gov-action-loader/frontend/.gitignore @@ -22,4 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? -.env \ No newline at end of file +.env diff --git a/gov-action-loader/frontend/README.md b/gov-action-loader/frontend/README.md index 876636f1a..321528145 100644 --- a/gov-action-loader/frontend/README.md +++ b/gov-action-loader/frontend/README.md @@ -5,9 +5,9 @@ It contains simple forms for loading multiple/specific proposals in the sancho n ### Depends On - [gov-action-loader-be](../gov-action-loader-be/) - + ### Limitations -Gov action loader backend instance uses fixed set of wallet to perform transactions. This means that gov action loader can be used by only 1 user at a time. +Gov action loader backend instance uses fixed set of wallet to perform transactions. This means that gov action loader can be used by only 1 user at a time. ## Running the frontend diff --git a/gov-action-loader/frontend/public/vite.svg b/gov-action-loader/frontend/public/vite.svg index e7b8dfb1b..ee9fadaf9 100644 --- a/gov-action-loader/frontend/public/vite.svg +++ b/gov-action-loader/frontend/public/vite.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/gov-action-loader/frontend/src/assets/vite.svg b/gov-action-loader/frontend/src/assets/vite.svg index e7b8dfb1b..ee9fadaf9 100644 --- a/gov-action-loader/frontend/src/assets/vite.svg +++ b/gov-action-loader/frontend/src/assets/vite.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/govtool/backend/.envrc b/govtool/backend/.envrc index 4b15e214d..28444c771 100644 --- a/govtool/backend/.envrc +++ b/govtool/backend/.envrc @@ -1,3 +1,3 @@ source_up_if_exists -watch_file vva-be.cabal +watch_file vva-be.cabal default.nix use flake --extra-experimental-features nix-command --extra-experimental-features flakes ../..#backend diff --git a/govtool/backend/.gitignore b/govtool/backend/.gitignore index 5dc5c47e4..148b93507 100644 --- a/govtool/backend/.gitignore +++ b/govtool/backend/.gitignore @@ -1,3 +1,3 @@ # other .vscode -dev-config.json \ No newline at end of file +dev-config.json diff --git a/govtool/backend/.stylish-haskell.yaml b/govtool/backend/.stylish-haskell.yaml new file mode 100644 index 000000000..6c806b76c --- /dev/null +++ b/govtool/backend/.stylish-haskell.yaml @@ -0,0 +1,490 @@ +# stylish-haskell configuration file +# ================================== + +# The stylish-haskell tool is mainly configured by specifying steps. These steps +# are a list, so they have an order, and one specific step may appear more than +# once (if needed). Each file is processed by these steps in the given order. +steps: + # Convert some ASCII sequences to their Unicode equivalents. This is disabled + # by default. + # - unicode_syntax: + # # In order to make this work, we also need to insert the UnicodeSyntax + # # language pragma. If this flag is set to true, we insert it when it's + # # not already present. You may want to disable it if you configure + # # language extensions using some other method than pragmas. Default: + # # true. + # add_language_pragma: true + + # Format module header + # + # Currently, this option is not configurable and will format all exports and + # module declarations to minimize diffs + # + - module_header: + # # How many spaces use for indentation in the module header. + indent: 4 + # + # # Should export lists be sorted? Sorting is only performed within the + # # export section, as delineated by Haddock comments. + sort: true + # + # # See `separate_lists` for the `imports` step. + separate_lists: true + # + # # When to break the "where". + # # Possible values: + # # - exports: only break when there is an explicit export list. + # # - single: only break when the export list counts more than one export. + # # - inline: only break when the export list is too long. This is + # # determined by the `columns` setting. Not applicable when the export + # # list contains comments as newlines will be required. + # # - always: always break before the "where". + # break_where: exports + # + # # Where to put open bracket + # # Possible values: + # # - same_line: put open bracket on the same line as the module name, before the + # # comment of the module + # # - next_line: put open bracket on the next line, after module comment + # open_bracket: next_line + + # Format record definitions. This is disabled by default. + # + # You can control the layout of record fields. The only rules that can't be configured + # are these: + # + # - "|" is always aligned with "=" + # - "," in fields is always aligned with "{" + # - "}" is likewise always aligned with "{" + # + - records: + # How to format equals sign between type constructor and data constructor. + # Possible values: + # - "same_line" -- leave "=" AND data constructor on the same line as the type constructor. + # - "indent N" -- insert a new line and N spaces from the beginning of the next line. + equals: "indent 2" + + # How to format first field of each record constructor. + # Possible values: + # - "same_line" -- "{" and first field goes on the same line as the data constructor. + # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor + first_field: "indent 2" + + # How many spaces to insert between the column with "," and the beginning of the comment in the next line. + field_comment: 2 + + # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines. + deriving: 2 + + # How many spaces to insert before "via" clause counted from indentation of deriving clause + # Possible values: + # - "same_line" -- "via" part goes on the same line as "deriving" keyword. + # - "indent N" -- insert a new line and N spaces from the beginning of "deriving" keyword. + via: "indent 2" + # + # # Sort typeclass names in the "deriving" list alphabetically. + # sort_deriving: true + # + # # Whether or not to break enums onto several lines + # # + # # Default: false + # break_enums: false + # + # # Whether or not to break single constructor data types before `=` sign + # # + # # Default: true + # break_single_constructors: true + # + # # Whether or not to curry constraints on function. + # # + # # E.g: @allValues :: Enum a => Bounded a => Proxy a -> [a]@ + # # + # # Instead of @allValues :: (Enum a, Bounded a) => Proxy a -> [a]@ + # # + # # Default: false + curried_context: false + + # Align the right hand side of some elements. This is quite conservative + # and only applies to statements where each element occupies a single + # line. + # Possible values: + # - always - Always align statements. + # - adjacent - Align statements that are on adjacent lines in groups. + # - never - Never align statements. + # All default to always. + - simple_align: + cases: always + top_level_patterns: always + records: always + multi_way_if: always + + # Import cleanup + - imports: + # There are different ways we can align names and lists. + # + # - global: Align the import names and import list throughout the entire + # file. + # + # - file: Like global, but don't add padding when there are no qualified + # imports in the file. + # + # - group: Only align the imports per group (a group is formed by adjacent + # import lines). + # + # - none: Do not perform any alignment. + # + # Default: global. + align: file + + # The following options affect only import list alignment. + # + # List align has following options: + # + # - after_alias: Import list is aligned with end of import including + # 'as' and 'hiding' keywords. + # + # > import qualified Data.List as List (concat, foldl, foldr, head, + # > init, last, length) + # + # - with_alias: Import list is aligned with start of alias or hiding. + # + # > import qualified Data.List as List (concat, foldl, foldr, head, + # > init, last, length) + # + # - with_module_name: Import list is aligned `list_padding` spaces after + # the module name. + # + # > import qualified Data.List as List (concat, foldl, foldr, head, + # init, last, length) + # + # This is mainly intended for use with `pad_module_names: false`. + # + # > import qualified Data.List as List (concat, foldl, foldr, head, + # init, last, length, scanl, scanr, take, drop, + # sort, nub) + # + # - new_line: Import list starts always on new line. + # + # > import qualified Data.List as List + # > (concat, foldl, foldr, head, init, last, length) + # + # - repeat: Repeat the module name to align the import list. + # + # > import qualified Data.List as List (concat, foldl, foldr, head) + # > import qualified Data.List as List (init, last, length) + # + # Default: after_alias + list_align: after_alias + + # Right-pad the module names to align imports in a group: + # + # - true: a little more readable + # + # > import qualified Data.List as List (concat, foldl, foldr, + # > init, last, length) + # > import qualified Data.List.Extra as List (concat, foldl, foldr, + # > init, last, length) + # + # - false: diff-safe + # + # > import qualified Data.List as List (concat, foldl, foldr, init, + # > last, length) + # > import qualified Data.List.Extra as List (concat, foldl, foldr, + # > init, last, length) + # + # Default: true + pad_module_names: true + + # Long list align style takes effect when import is too long. This is + # determined by 'columns' setting. + # + # - inline: This option will put as much specs on same line as possible. + # + # - new_line: Import list will start on new line. + # + # - new_line_multiline: Import list will start on new line when it's + # short enough to fit to single line. Otherwise it'll be multiline. + # + # - multiline: One line per import list entry. + # Type with constructor list acts like single import. + # + # > import qualified Data.Map as M + # > ( empty + # > , singleton + # > , ... + # > , delete + # > ) + # + # Default: inline + long_list_align: inline + + # Align empty list (importing instances) + # + # Empty list align has following options + # + # - inherit: inherit list_align setting + # + # - right_after: () is right after the module name: + # + # > import Vector.Instances () + # + # Default: inherit + empty_list_align: inherit + + # List padding determines indentation of import list on lines after import. + # This option affects 'long_list_align'. + # + # - : constant value + # + # - module_name: align under start of module name. + # Useful for 'file' and 'group' align settings. + # + # Default: 4 + list_padding: 4 + + # Separate lists option affects formatting of import list for type + # or class. The only difference is single space between type and list + # of constructors, selectors and class functions. + # + # - true: There is single space between Foldable type and list of it's + # functions. + # + # > import Data.Foldable (Foldable (fold, foldl, foldMap)) + # + # - false: There is no space between Foldable type and list of it's + # functions. + # + # > import Data.Foldable (Foldable(fold, foldl, foldMap)) + # + # Default: true + separate_lists: true + + # Space surround option affects formatting of import lists on a single + # line. The only difference is single space after the initial + # parenthesis and a single space before the terminal parenthesis. + # + # - true: There is single space associated with the enclosing + # parenthesis. + # + # > import Data.Foo ( foo ) + # + # - false: There is no space associated with the enclosing parenthesis + # + # > import Data.Foo (foo) + # + # Default: false + space_surround: false + + # Post qualify option moves any qualifies found in import declarations + # to the end of the declaration. This also adjust padding for any + # unqualified import declarations. + # + # - true: Qualified as is moved to the end of the + # declaration. + # + # > import Data.Bar + # > import Data.Foo qualified as F + # + # - false: Qualified remains in the default location and unqualified + # imports are padded to align with qualified imports. + # + # > import Data.Bar + # > import qualified Data.Foo as F + # + # Default: false + post_qualify: false + + # Automatically group imports based on their module names, with + # a blank line separating each group. Groups are ordered in + # alphabetical order. + # + # By default, this groups by the first part of each module's + # name (Control.* will be grouped together, Data.*... etc), but + # this can be configured with the group_patterns setting. + # + # When enabled, this rewrites existing blank lines and groups. + # + # - true: Group imports by the first part of the module name. + # + # > import Control.Applicative + # > import Control.Monad + # > import Control.Monad.MonadError + # > + # > import Data.Functor + # + # - false: Keep import groups as-is (still sorting and + # formatting the imports within each group) + # + # > import Control.Monad + # > import Data.Functor + # > + # > import Control.Applicative + # > import Control.Monad.MonadError + # + # Default: false + group_imports: true + + # A list of rules specifying how to group modules and how to + # order the groups. + # + # Each rule has a match field; the rule only applies to module + # names matched by this pattern. Patterns are POSIX extended + # regular expressions; see the documentation of Text.Regex.TDFA + # for details: + # https://hackage.haskell.org/package/regex-tdfa-1.3.1.2/docs/Text-Regex-TDFA.html + # + # Rules are processed in order, so only the *first* rule that + # matches a specific module will apply. Any module names that do + # not match a single rule will be put into a single group at the + # end of the import block. + # + # Example: group MyApp modules first, with everything else in + # one group at the end. + # + # group_rules: + # - match: "^MyApp\\>" + # + # > import MyApp + # > import MyApp.Foo + # > + # > import Control.Monad + # > import MyApps + # > import Test.MyApp + # + # A rule can also optionally have a sub_group pattern. Imports + # that match the rule will be broken up into further groups by + # the part of the module name matched by the sub_group pattern. + # + # Example: group MyApp modules first, then everything else + # sub-grouped by the first part of the module name. + # + # group_rules: + # - match: "^MyApp\\>" + # - match: "." + # sub_group: "^[^.]+" + # + # > import MyApp + # > import MyApp.Foo + # > + # > import Control.Applicative + # > import Control.Monad + # > + # > import Data.Map + # + # A pattern only needs to match part of the module name, which + # could be in the middle. You can use ^pattern to anchor to the + # beginning of the module name, pattern$ to anchor to the end + # and ^pattern$ to force a full match. Example: + # + # - "Test\\." would match "Test.Foo" and "Foo.Test.Lib" + # - "^Test\\." would match "Test.Foo" but not "Foo.Test.Lib" + # - "\\.Test$" would match "Foo.Test" but not "Foo.Test.Lib" + # - "^Test$" would *only* match "Test" + # + # You can use \\< and \\> to anchor against the beginning and + # end of words, respectively. For example: + # + # - "^Test\\." would match "Test.Foo" but not "Test" or "Tests" + # - "^Test\\>" would match "Test.Foo" and "Test", but not + # "Tests" + # + # The default is a single rule that matches everything and + # sub-groups based on the first component of the module name. + # + # Default: [{ "match" : ".*", "sub_group": "^[^.]+" }] + group_rules: + - match: ".*" + sub_group: "^[^.]+" + + # Language pragmas + - language_pragmas: + # We can generate different styles of language pragma lists. + # + # - vertical: Vertical-spaced language pragmas, one per line. + # + # - compact: A more compact style. + # + # - compact_line: Similar to compact, but wrap each line with + # `{-# LANGUAGE #-}'. + # + # - vertical_compact: Similar to vertical, but use only one language pragma. + # + # Default: vertical. + style: vertical + + # Align affects alignment of closing pragma brackets. + # + # - true: Brackets are aligned in same column. + # + # - false: Brackets are not aligned together. There is only one space + # between actual import and closing bracket. + # + # Default: true + align: true + + # stylish-haskell can detect redundancy of some language pragmas. If this + # is set to true, it will remove those redundant pragmas. Default: true. + remove_redundant: true + + # Language prefix to be used for pragma declaration, this allows you to + # use other options non case-sensitive like "language" or "Language". + # If a non correct String is provided, it will default to: LANGUAGE. + language_prefix: LANGUAGE + + # Replace tabs by spaces. This is disabled by default. + - tabs: + # Number of spaces to use for each tab. Default: 8, as specified by the + # Haskell report. + spaces: 4 + + # Remove trailing whitespace + - trailing_whitespace: {} + + # Squash multiple spaces between the left and right hand sides of some + # elements into single spaces. Basically, this undoes the effect of + # simple_align but is a bit less conservative. + # - squash: {} + +# A common setting is the number of columns (parts of) code will be wrapped +# to. Different steps take this into account. +# +# Set this to null to disable all line wrapping. +# +# Default: 80. +columns: 110 + +# By default, line endings are converted according to the OS. You can override +# preferred format here. +# +# - native: Native newline format. CRLF on Windows, LF on other OSes. +# +# - lf: Convert to LF ("\n"). +# +# - crlf: Convert to CRLF ("\r\n"). +# +# Default: native. +newline: native + +# Sometimes, language extensions are specified in a cabal file or from the +# command line instead of using language pragmas in the file. stylish-haskell +# needs to be aware of these, so it can parse the file correctly. +# +# No language extensions are enabled by default. +language_extensions: + - FlexibleContexts + - GADTs + - TemplateHaskell + - TypeApplications + - DataKinds + - RankNTypes + - LambdaCase + - ScopedTypeVariables + - PolyKinds + - TypeOperators + - TypeFamilies + +# Attempt to find the cabal file in ancestors of the current directory, and +# parse options (currently only language extensions) from that. +# +# Default: true +cabal: true diff --git a/govtool/backend/Dockerfile b/govtool/backend/Dockerfile index 75600043c..b8882627d 100644 --- a/govtool/backend/Dockerfile +++ b/govtool/backend/Dockerfile @@ -3,4 +3,4 @@ FROM 733019650473.dkr.ecr.eu-west-1.amazonaws.com/backend-base:$BASE_IMAGE_TAG WORKDIR /src COPY . . RUN cabal build -RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.8/vva-be-0.1.0.0/x/vva-be/build/vva-be/vva-be /usr/local/bin +RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-0.1.0.0/x/vva-be/build/vva-be/vva-be /usr/local/bin diff --git a/govtool/backend/README.md b/govtool/backend/README.md index bf9225f57..8d539b304 100644 --- a/govtool/backend/README.md +++ b/govtool/backend/README.md @@ -3,6 +3,7 @@ This is a backend application of GovTool project. ## Prerequisites + In order to run `backend` your host machine will need access to the `cardano-db-sync` postgres database. To have this database running locally you'll need: * `cardano-node` * `cardano-db-sync` @@ -18,6 +19,8 @@ You will need your `cardano-node` and `cardano-db-sync` to be compatible with Sa [`sancho` testnet config files](https://sancho.network/tutorials/start-node/) +You can utilize the [docker-compose.node+dbsync.yml](../../scripts/govtool/docker-compose.node+dbsync.yml) file to setup the required services. + ### Using Nix and Direnv Due to problems with openapi3 package it's hard to build this project with plain `ghc` and `cabal-install`. Until the prolem is solved we reccomend using `nix` - this problem is fixed when you build your project from inside of the nix shell. @@ -51,3 +54,21 @@ Due to problems with openapi3 package it's hard to build this project with plain ``` > [!WARNING] > In the context of our ongoing project enhancements, it is assumed that the executable previously known as 'vva-be' should be now officially renamed to 'govtool-backend'. This change is necessary for aligning with the updated branding and functional scope of the application and it has to be implemented in the near future as a chore and refactoring ticket. Make sure that the documentation matches the actual name of the executable. + +## Development + +### Linter + +In the development environment in Nix, the [`hlint`](https://github.com/ndmitchell/hlint) tool is readily available to verify Haskell files. + +By using `hlint`, developers can ensure that their code adheres to best practices and follows appropriate guidelines. By incorporating `hlint` into the development process, developers can catch potential errors and make necessary improvements early on, ultimately leading to more efficient and robust software development. + +### Formatter + +To easily format Haskell code, ensuring consistency and readability across the codebase the [`stylish-haskell`](https://github.com/haskell/stylish-haskell) formatter has been introduced into the nix configuration. + +Developers can streamline the process of formatting their code, reducing the time and effort required for manual formatting. + +### HLS + +Developers can use the IDE integrations for Language Server Protocol (LSP) by utilising the [Haskell-Language-Server](https://github.com/haskell/haskell-language-server) that include support for GHC 9.2.7. Using such integration, developers can ensure a seamless and efficient experience with Haskell code in their IDE. diff --git a/govtool/backend/app/Main.hs b/govtool/backend/app/Main.hs index 8de817ea3..f4df1c03c 100644 --- a/govtool/backend/app/Main.hs +++ b/govtool/backend/app/Main.hs @@ -1,90 +1,77 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedLabels #-} -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE OverloadedLabels #-} -{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} module Main where -import Control.Exception - ( Exception, - SomeException, - fromException, - throw, - ) -import Control.Lens.Operators ((.~)) -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.Trans.Except -import Control.Monad.Trans.Reader -import Data.Aeson hiding (Error) -import Data.ByteString.Char8 (unpack) -import qualified Data.ByteString as BS -import Data.Function ((&)) -import Data.Monoid (mempty) -import Data.OpenApi (OpenApi, Server (Server), servers, _openApiServers, _serverDescription, _serverUrl, _serverVariables) -import Data.Proxy -import Data.String (fromString) -import Data.String.Conversions - ( cs, - ) -import qualified Data.Text as Text -import qualified Data.Text.IO as Text -import qualified Data.Text.Lazy as LazyText -import qualified Data.Text.Lazy.Encoding as LazyText -import Network.Wai -import Network.Wai - ( Request, - rawPathInfo, - requestHeaderHost, - ) -import Network.Wai.Handler.Warp -import Network.Wai.Handler.Warp - ( defaultOnException, - defaultSettings, - runSettings, - setOnException, - setPort, - ) -import Network.Wai.Middleware.Cors -import Options.Applicative (execParser) -import Servant -import Servant.API.ContentTypes -import Servant.OpenApi (toOpenApi) -import qualified Servant.Server as Servant -import Servant.Swagger.UI - ( SwaggerSchemaUI, - swaggerSchemaUIServer, - ) -import System.IO (stderr) -import System.Log.Raven - ( initRaven, - register, - silentFallback, - ) -import System.Log.Raven.Transport.HttpConduit (sendRecord) -import System.Log.Raven.Types - ( SentryLevel (Error), - SentryRecord (..), - ) -import VVA.API -import VVA.CommandLine -import VVA.Config -import Data.Function ((&)) -import Control.Lens.Operators ((.~)) -import Data.Monoid (mempty) -import qualified Data.Cache as Cache -import VVA.API.Types -import System.Clock (TimeSpec(TimeSpec)) -import Data.Pool (createPool) -import Database.PostgreSQL.Simple (connectPostgreSQL, close) -import Data.Text.Encoding (encodeUtf8) -import Data.Has (getter) -import VVA.Types (AppError(ValidationError, NotFoundError, CriticalError), CacheEnv(..), AppEnv(..)) +import Control.Exception (Exception, + SomeException, + fromException, throw) +import Control.Lens.Operators ((.~)) +import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Trans.Except +import Control.Monad.Trans.Reader + +import Data.Aeson hiding (Error) +import qualified Data.ByteString as BS +import Data.ByteString.Char8 (unpack) +import qualified Data.Cache as Cache +import Data.Function ((&)) +import Data.Has (getter) +import Data.Monoid (mempty) +import Data.OpenApi (OpenApi, + Server (Server), + _openApiServers, + _serverDescription, + _serverUrl, + _serverVariables, + servers) +import Data.Pool (createPool) +import Data.Proxy +import Data.String (fromString) +import Data.String.Conversions (cs) +import qualified Data.Text as Text +import Data.Text.Encoding (encodeUtf8) +import qualified Data.Text.IO as Text +import qualified Data.Text.Lazy as LazyText +import qualified Data.Text.Lazy.Encoding as LazyText + +import Database.PostgreSQL.Simple (close, + connectPostgreSQL) + +import Network.Wai +import Network.Wai.Handler.Warp +import Network.Wai.Middleware.Cors + +import Options.Applicative (execParser) + +import Servant +import Servant.API.ContentTypes +import Servant.OpenApi (toOpenApi) +import qualified Servant.Server as Servant +import Servant.Swagger.UI (SwaggerSchemaUI, + swaggerSchemaUIServer) + +import System.Clock (TimeSpec (TimeSpec)) +import System.IO (stderr) +import System.Log.Raven (initRaven, register, + silentFallback) +import System.Log.Raven.Transport.HttpConduit (sendRecord) +import System.Log.Raven.Types (SentryLevel (Error), + SentryRecord (..)) + +import VVA.API +import VVA.API.Types +import VVA.CommandLine +import VVA.Config +import VVA.Types (AppEnv (..), + AppError (CriticalError, NotFoundError, ValidationError), + CacheEnv (..)) proxyAPI :: Proxy (VVAApi :<|> SwaggerAPI) proxyAPI = Proxy @@ -94,7 +81,7 @@ main = do commandLineConfig <- execParser cmdParser vvaConfig <- loadVVAConfig (clcConfigPath commandLineConfig) case clcCommand commandLineConfig of - StartApp -> startApp vvaConfig + StartApp -> startApp vvaConfig ShowConfig -> Text.putStrLn $ vvaConfigToText vvaConfig startApp :: VVAConfig -> IO () @@ -172,7 +159,7 @@ recordUpdate Nothing exception record = record recordUpdate (Just request) exception record = record { srCulprit = Just $ unpack $ rawPathInfo request, - srServerName = fmap unpack $ requestHeaderHost request + srServerName = unpack <$> requestHeaderHost request } shouldDisplayException :: SomeException -> Bool @@ -243,7 +230,8 @@ mkVVAServer appEnv = do (liftServer appEnv :<|> swagger) ) -newtype TextException = TextException Text.Text +newtype TextException + = TextException Text.Text instance Show TextException where show (TextException e) = show e diff --git a/govtool/backend/default.nix b/govtool/backend/default.nix index cb9eb4aae..575bf4dcc 100644 --- a/govtool/backend/default.nix +++ b/govtool/backend/default.nix @@ -1,24 +1,30 @@ { pkgs ? import { } }: let - # This is the version of the Haskell compiler we reccommend using. - ghcPackages = pkgs.haskell.packages.ghc927; + inherit (pkgs.lib.trivial) pipe; + inherit (pkgs) haskell; + inherit (haskell) lib; - additionalTools = drv: - pkgs.haskell.lib.addBuildTools drv (with ghcPackages; [ + # This is the version of the Haskell compiler we recommend using. + ghcPackages = haskell.packages.ghc927; + + appendLibraries = drv: lib.addExtraLibraries drv (with pkgs; [ lzma zlib ]); + + appendTools = drv: + lib.addBuildTools drv (with ghcPackages; [ cabal-install haskell-language-server - lzma - ormolu - pkgs.postgresql - zlib + hlint + stylish-haskell ]); + useBroken = drv: pipe drv [ lib.markBroken lib.dontCheck ]; + + modifier = drv: pipe drv [ appendLibraries appendTools ]; + project = ghcPackages.developPackage { root = ./.; - modifier = additionalTools; - overrides = self: super: { - openapi3 = pkgs.haskell.lib.dontCheck super.openapi3; - }; + modifier = modifier; + overrides = self: super: { openapi3 = useBroken super.openapi3; }; }; in project.overrideAttrs (oldAttrs: { shellHook = '' @@ -27,6 +33,6 @@ in project.overrideAttrs (oldAttrs: { tput bold warn "Welcome to GovTool!" 4 warn "This is a backend development shell." 4 - warn "Read the govtool/backend/README.md to get more info about this module." 8 + warn "Read the ${./README.md} to get more info about this module." 8 '' + (oldAttrs.shellHook or ""); }) diff --git a/govtool/backend/misc/migration6.sql b/govtool/backend/misc/migration6.sql index 56e8e0173..53982ee4a 100644 --- a/govtool/backend/misc/migration6.sql +++ b/govtool/backend/misc/migration6.sql @@ -4,4 +4,4 @@ UPDATE governance_action SET temp_column = json_build_object('message', details: ALTER TABLE governance_action DROP COLUMN details; -ALTER TABLE governance_action RENAME COLUMN temp_column TO details; \ No newline at end of file +ALTER TABLE governance_action RENAME COLUMN temp_column TO details; diff --git a/govtool/backend/misc/schema.sql b/govtool/backend/misc/schema.sql index 6221a01e0..6d0d23af6 100644 --- a/govtool/backend/misc/schema.sql +++ b/govtool/backend/misc/schema.sql @@ -396,4 +396,3 @@ GRANT ALL ON SCHEMA public TO PUBLIC; -- -- PostgreSQL database dump complete -- - diff --git a/govtool/backend/misc/schema4.sql b/govtool/backend/misc/schema4.sql index 0a38cee71..1904fb40f 100644 --- a/govtool/backend/misc/schema4.sql +++ b/govtool/backend/misc/schema4.sql @@ -397,4 +397,3 @@ GRANT ALL ON SCHEMA public TO PUBLIC; -- -- PostgreSQL database dump complete -- - diff --git a/govtool/backend/misc/schema5.sql b/govtool/backend/misc/schema5.sql index f100dfd7f..179a9063d 100644 --- a/govtool/backend/misc/schema5.sql +++ b/govtool/backend/misc/schema5.sql @@ -397,4 +397,3 @@ GRANT ALL ON SCHEMA public TO PUBLIC; -- -- PostgreSQL database dump complete -- - diff --git a/govtool/backend/sql/get-all-proposal-stake-keys.sql b/govtool/backend/sql/get-all-proposal-stake-keys.sql index 136c558b3..458af120a 100644 --- a/govtool/backend/sql/get-all-proposal-stake-keys.sql +++ b/govtool/backend/sql/get-all-proposal-stake-keys.sql @@ -27,4 +27,4 @@ join drep_hash on drep_hash.id = delegation_vote.id join stake_address on stake_address.id = delegation_vote.addr_id -where drep_hash.view = 'AlwaysAbstain' \ No newline at end of file +where drep_hash.view = 'AlwaysAbstain' diff --git a/govtool/backend/sql/get-current-delegation.sql b/govtool/backend/sql/get-current-delegation.sql index d445aa280..68e9dce31 100644 --- a/govtool/backend/sql/get-current-delegation.sql +++ b/govtool/backend/sql/get-current-delegation.sql @@ -10,4 +10,4 @@ join stake_address on stake_address.id = delegation_vote.addr_id where stake_address.hash_raw = decode(?, 'hex') and not exists (select * from delegation_vote as dv2 where dv2.addr_id = delegation_vote.addr_id and dv2.tx_id > delegation_vote.tx_id) -limit 1; \ No newline at end of file +limit 1; diff --git a/govtool/backend/sql/get-current-epoch-params.sql b/govtool/backend/sql/get-current-epoch-params.sql index 80a7eb053..9fd642903 100644 --- a/govtool/backend/sql/get-current-epoch-params.sql +++ b/govtool/backend/sql/get-current-epoch-params.sql @@ -1 +1 @@ -select ROW_TO_JSON(epoch_param) from epoch_param order by epoch_no desc limit 1; \ No newline at end of file +select ROW_TO_JSON(epoch_param) from epoch_param order by epoch_no desc limit 1; diff --git a/govtool/backend/sql/get-delegates.sql b/govtool/backend/sql/get-delegates.sql index bf0a63d5c..f315ef3c4 100644 --- a/govtool/backend/sql/get-delegates.sql +++ b/govtool/backend/sql/get-delegates.sql @@ -2,4 +2,4 @@ SELECT delegation_vote.stake_addr from drep join delegation_vote on delegation_vote.drep_id = drep.id -where drep.drep_raw = ? \ No newline at end of file +where drep.drep_raw = ? diff --git a/govtool/backend/sql/get-stake-key-voting-power.sql b/govtool/backend/sql/get-stake-key-voting-power.sql index 7a128045b..59aa5049b 100644 --- a/govtool/backend/sql/get-stake-key-voting-power.sql +++ b/govtool/backend/sql/get-stake-key-voting-power.sql @@ -3,4 +3,4 @@ from stake_address join utxo_view on utxo_view.stake_address_id = stake_address.id where stake_address.hash_raw = decode(?, 'hex') -group by stake_address.hash_raw \ No newline at end of file +group by stake_address.hash_raw diff --git a/govtool/backend/sql/get-transaction-status.sql b/govtool/backend/sql/get-transaction-status.sql index d368945ec..ff21785e7 100644 --- a/govtool/backend/sql/get-transaction-status.sql +++ b/govtool/backend/sql/get-transaction-status.sql @@ -1 +1 @@ -select exists (select * from tx where tx.hash = decode(?, 'hex')) \ No newline at end of file +select exists (select * from tx where tx.hash = decode(?, 'hex')) diff --git a/govtool/backend/sql/get-votes.sql b/govtool/backend/sql/get-votes.sql index 7fa5dc935..30dc5fc2b 100644 --- a/govtool/backend/sql/get-votes.sql +++ b/govtool/backend/sql/get-votes.sql @@ -10,5 +10,3 @@ join tx on tx.id = gov_action_proposal.tx_id where drep_hash.raw = decode(?, 'hex') order by voting_procedure.gov_action_proposal_id, voting_procedure.drep_voter, voting_procedure.id desc - - diff --git a/govtool/backend/sql/get-voting-power.sql b/govtool/backend/sql/get-voting-power.sql index b81b5eb84..3a7cbbfb7 100644 --- a/govtool/backend/sql/get-voting-power.sql +++ b/govtool/backend/sql/get-voting-power.sql @@ -4,4 +4,4 @@ left join drep_distr on drep_hash.id = drep_distr.hash_id where drep_hash.raw = decode(?,'hex') order by epoch_no desc -limit 1 \ No newline at end of file +limit 1 diff --git a/govtool/backend/sql/list-proposals.sql b/govtool/backend/sql/list-proposals.sql index 94cd4849e..f0bc3cc13 100644 --- a/govtool/backend/sql/list-proposals.sql +++ b/govtool/backend/sql/list-proposals.sql @@ -59,6 +59,7 @@ SELECT off_chain_vote_data.abstract, off_chain_vote_data.motivation, off_chain_vote_data.rationale, + off_chain_vote_data.json, coalesce(Sum(ldd.amount) FILTER (WHERE voting_procedure.vote::text = 'Yes'), 0) +( CASE WHEN gov_action_proposal.type = 'NoConfidence' THEN always_no_confidence_voting_power.amount @@ -109,6 +110,7 @@ GROUP BY off_chain_vote_data.abstract, off_chain_vote_data.motivation, off_chain_vote_data.rationale, + off_chain_vote_data.json, gov_action_proposal.index, creator_tx.hash, creator_block.time, diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index aba121ba6..3796b52d3 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -1,38 +1,46 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE ViewPatterns #-} module VVA.API where -import Control.Monad.Reader -import Control.Monad.Except (throwError) -import Data.List (sortOn) -import Data.Maybe (fromMaybe, Maybe (Nothing)) -import Data.Ord (Down (..)) -import Data.Text hiding (elem, filter, map, null, take, drop, length) -import Servant.API -import Servant.Server -import Text.Read (readMaybe) -import VVA.API.Types -import qualified VVA.AdaHolder as AdaHolder -import VVA.Config -import qualified VVA.DRep as DRep -import qualified VVA.Proposal as Proposal -import qualified VVA.Epoch as Epoch -import qualified VVA.Transaction as Transaction -import Data.Bool (Bool) -import qualified Data.Map as Map -import VVA.Cache (cacheRequest) -import Control.Exception (throw) -import VVA.Types (CacheEnv(..), AppError(ValidationError, CriticalError), App, AppEnv(..)) -import qualified VVA.Types as Types -import qualified Data.Text as Text -import VVA.Network as Network -import Numeric.Natural (Natural) +import Control.Exception (throw) +import Control.Monad.Except (throwError) +import Control.Monad.Reader + +import Data.Bool (Bool) +import Data.List (sortOn) +import qualified Data.Map as Map +import Data.Maybe (Maybe (Nothing), fromMaybe) +import Data.Ord (Down (..)) +import Data.Text hiding (drop, elem, filter, length, map, + null, take) +import qualified Data.Text as Text + +import Numeric.Natural (Natural) + +import Servant.API +import Servant.Server + +import Text.Read (readMaybe) + +import qualified VVA.AdaHolder as AdaHolder +import VVA.API.Types +import VVA.Cache (cacheRequest) +import VVA.Config +import qualified VVA.DRep as DRep +import qualified VVA.Epoch as Epoch +import VVA.Network as Network +import qualified VVA.Proposal as Proposal +import qualified VVA.Transaction as Transaction +import qualified VVA.Types as Types +import VVA.Types (App, AppEnv (..), + AppError (CriticalError, ValidationError), + CacheEnv (..)) type VVAApi = "drep" :> "list" :> QueryParam "drepView" Text :> Get '[JSON] [DRep] @@ -47,6 +55,7 @@ type VVAApi = :> QueryParam "page" Natural :> QueryParam "pageSize" Natural :> QueryParam "drepId" HexText + :> QueryParam "search" Text :> Get '[JSON] ListProposalsResponse :<|> "proposal" :> "get" :> Capture "proposalId" GovActionId :> QueryParam "drepId" HexText :> Get '[JSON] GetProposalResponse :<|> "epoch" :> "params" :> Get '[JSON] GetCurrentEpochParamsResponse @@ -70,12 +79,12 @@ server = drepList mapDRepType :: Types.DRepType -> DRepType -mapDRepType Types.DRep = NormalDRep +mapDRepType Types.DRep = NormalDRep mapDRepType Types.SoleVoter = SoleVoter mapDRepStatus :: Types.DRepStatus -> DRepStatus -mapDRepStatus Types.Retired = Retired -mapDRepStatus Types.Active = Active +mapDRepStatus Types.Retired = Retired +mapDRepStatus Types.Active = Active mapDRepStatus Types.Inactive = Inactive drepRegistrationToDrep :: Types.DRepRegistration -> DRep @@ -98,9 +107,9 @@ drepList mDRepView = do let filtered = flip filter dreps $ \Types.DRepRegistration {..} -> case (dRepRegistrationType, mDRepView) of (Types.SoleVoter, Just x) -> x == dRepRegistrationView - (Types.DRep, Just x) -> isInfixOf x dRepRegistrationView - (Types.DRep, Nothing) -> True - _ -> False + (Types.DRep, Just x) -> x `isInfixOf` dRepRegistrationView + (Types.DRep, Nothing) -> True + _ -> False return $ map drepRegistrationToDrep filtered getVotingPower :: App m => HexText -> m Integer @@ -127,6 +136,7 @@ proposalToResponse Types.Proposal {..} = proposalResponseAbout = proposalAbout, proposalResponseMotivation = proposalMotivaiton, proposalResponseRationale = proposalRationale, + proposalResponseMetadata = GovernanceActionMetadata <$> proposalMetadata, proposalResponseYesVotes = proposalYesVotes, proposalResponseNoVotes = proposalNoVotes, proposalResponseAbstainVotes = proposalAbstainVotes @@ -163,10 +173,10 @@ mapSortAndFilterProposals selectedTypes sortMode proposals = ) mappedProposals sortedProposals = case sortMode of - Nothing -> filteredProposals - Just NewestCreated -> sortOn (Down . proposalResponseCreatedDate) filteredProposals + Nothing -> filteredProposals + Just NewestCreated -> sortOn (Down . proposalResponseCreatedDate) filteredProposals Just SoonestToExpire -> sortOn proposalResponseExpiryDate filteredProposals - Just MostYesVotes -> sortOn (Down . proposalResponseYesVotes) filteredProposals + Just MostYesVotes -> sortOn (Down . proposalResponseYesVotes) filteredProposals in sortedProposals getVotes :: App m => HexText -> [GovernanceActionType] -> Maybe GovernanceActionSortMode -> m [VoteResponse] @@ -177,7 +187,7 @@ getVotes (unHexText -> dRepId) selectedTypes sortMode = do let processedProposals = mapSortAndFilterProposals selectedTypes sortMode proposals return $ [ VoteResponse - { voteResponseVote = voteToResponse (voteMap Map.! (read $ unpack proposalResponseId)) + { voteResponseVote = voteToResponse (voteMap Map.! read (unpack proposalResponseId)) , voteResponseProposal = proposalResponse } | proposalResponse@ProposalResponse{proposalResponseId} <- processedProposals @@ -207,7 +217,7 @@ getCurrentDelegation (unHexText -> stakeKey) = do getStakeKeyVotingPower :: App m => HexText -> m Integer getStakeKeyVotingPower (unHexText -> stakeKey) = do CacheEnv {adaHolderVotingPowerCache} <- asks vvaCache - cacheRequest adaHolderVotingPowerCache stakeKey $ AdaHolder.getStakeKeyVotingPower $ stakeKey + cacheRequest adaHolderVotingPowerCache stakeKey $ AdaHolder.getStakeKeyVotingPower stakeKey listProposals @@ -217,8 +227,9 @@ listProposals -> Maybe Natural -> Maybe Natural -> Maybe HexText + -> Maybe Text -> m ListProposalsResponse -listProposals selectedTypes sortMode mPage mPageSize mDrepRaw = do +listProposals selectedTypes sortMode mPage mPageSize mDrepRaw mSearchQuery = do let page = (fromIntegral $ fromMaybe 0 mPage) :: Int pageSize = (fromIntegral $ fromMaybe 10 mPageSize) :: Int @@ -229,15 +240,30 @@ listProposals selectedTypes sortMode mPage mPageSize mDrepRaw = do map (voteParamsProposalId . voteResponseVote) <$> getVotes drepId [] Nothing + + + let filterF ProposalResponse{..} = case mSearchQuery of + Nothing -> True + Just searchQuery -> fromMaybe False $ do + title <- proposalResponseTitle + about <- proposalResponseAbout + motivation <- proposalResponseMotivation + rationale <- proposalResponseRationale + + let result = searchQuery `isInfixOf` title + || searchQuery `isInfixOf` about + || searchQuery `isInfixOf` motivation + || searchQuery `isInfixOf` rationale + + pure result + CacheEnv {proposalListCache} <- asks vvaCache mappedAndSortedProposals <- filter - ( \ProposalResponse {proposalResponseId} -> + ( \p@ProposalResponse {proposalResponseId} -> proposalResponseId `notElem` proposalsToRemove - ) - <$> - mapSortAndFilterProposals selectedTypes sortMode - <$> cacheRequest proposalListCache () Proposal.listProposals + && filterF p + ) . mapSortAndFilterProposals selectedTypes sortMode <$> cacheRequest proposalListCache () Proposal.listProposals let total = length mappedAndSortedProposals :: Int @@ -250,7 +276,7 @@ listProposals selectedTypes sortMode mPage mPageSize mDrepRaw = do , listProposalsResponseElements = elements } -getProposal :: App m => GovActionId -> Maybe (HexText) -> m GetProposalResponse +getProposal :: App m => GovActionId -> Maybe HexText -> m GetProposalResponse getProposal g@(GovActionId govActionTxHash govActionIndex) mDrepId' = do let mDrepId = unHexText <$> mDrepId' CacheEnv {getProposalCache} <- asks vvaCache @@ -279,7 +305,7 @@ getTransactionStatus :: App m => HexText -> m GetTransactionStatusResponse getTransactionStatus (unHexText -> transactionId) = do x <- Transaction.getTransactionStatus transactionId case x of - Types.TransactionConfirmed -> return $ GetTransactionStatusResponse True + Types.TransactionConfirmed -> return $ GetTransactionStatusResponse True Types.TransactionUnconfirmed -> return $ GetTransactionStatusResponse False throw500 :: App m => m () @@ -300,4 +326,4 @@ getNetworkMetrics = do , getNetworkMetricsResponseTotalRegisteredDReps = networkMetricsTotalRegisteredDReps , getNetworkMetricsResponseAlwaysAbstainVotingPower = networkMetricsAlwaysAbstainVotingPower , getNetworkMetricsResponseAlwaysNoConfidenceVotingPower = networkMetricsAlwaysNoConfidenceVotingPower - } \ No newline at end of file + } diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index 5080cd339..83352c56e 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -1,60 +1,68 @@ -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module VVA.API.Types where -import Control.Lens ((?~), (.~)) -import Control.Monad.Except -import Control.Monad.Reader -import Data.Aeson -import qualified Data.Aeson as Aeson -import qualified Data.Text.Lazy.Encoding as Text +import Control.Exception (throw) +import Control.Lens ((.~), (?~)) +import Control.Monad (guard) +import Control.Monad.Except +import Control.Monad.Reader + +import Data.Aeson +import qualified Data.Aeson as Aeson +import qualified Data.Aeson.KeyMap as Aeson (toList) +import Data.Aeson.TH (deriveJSON) import qualified Data.ByteString.Lazy.Char8 as Char8 -import qualified Data.Aeson.KeyMap as Aeson (toList) -import Data.Aeson.TH (deriveJSON) -import Data.Function ((&)) -import Data.Maybe (fromJust, fromMaybe) -import Data.OpenApi hiding (Info) -import Data.Text hiding (map) -import qualified Data.Text as Text -import Data.Time -import GHC.Generics -import Servant.API (FromHttpApiData, parseUrlPiece, parseQueryParam) -import Text.Read (readMaybe) -import VVA.API.Utils -import VVA.Config -import GHC.Exts (toList) -import qualified Data.Cache as Cache -import qualified VVA.Proposal as Proposal -import Data.Hashable (Hashable) -import Data.Has (Has, getter, modifier) -import Data.Pool (Pool) -import Database.PostgreSQL.Simple (Connection) -import Data.Char (isHexDigit) -import VVA.Types (AppError(ValidationError)) -import Data.Proxy (Proxy(Proxy)) -import Control.Exception (throw) -import Data.Swagger.Internal (SwaggerType(SwaggerString)) -import Control.Monad (guard) - -newtype HexText = HexText { unHexText :: Text } - deriving newtype (Show, Eq) +import qualified Data.Cache as Cache +import Data.Char (isHexDigit) +import Data.Function ((&)) +import Data.Has (Has, getter, modifier) +import Data.Hashable (Hashable) +import Data.Maybe (fromJust, fromMaybe) +import Data.OpenApi hiding (Info) +import Data.Pool (Pool) +import Data.Proxy (Proxy (Proxy)) +import Data.Swagger.Internal (SwaggerType (SwaggerString)) +import Data.Text hiding (map) +import qualified Data.Text as Text +import qualified Data.Text.Lazy.Encoding as Text +import Data.Time + +import Database.PostgreSQL.Simple (Connection) + +import GHC.Exts (toList) +import GHC.Generics + +import Servant.API (FromHttpApiData, parseQueryParam, + parseUrlPiece) + +import Text.Read (readMaybe) + +import VVA.API.Utils +import VVA.Config +import qualified VVA.Proposal as Proposal +import VVA.Types (AppError (ValidationError)) + +newtype HexText + = HexText { unHexText :: Text } + deriving newtype (Eq, Show) instance FromJSON HexText where parseJSON (Aeson.String t) = do - if (Text.length t `mod` 2 == 1 || Text.any (not . isHexDigit) t) + if Text.length t `mod` 2 == 1 || Text.any (not . isHexDigit) t then mzero else pure $ HexText t @@ -64,7 +72,7 @@ instance ToJSON HexText where -- To use it in routes, we need to be able to parse it from Text: instance FromHttpApiData HexText where parseUrlPiece txt - | Text.all isHexDigit txt && (Text.length txt `mod` 2 == 0) = Right (HexText txt) + | Text.all isHexDigit txt && even (Text.length txt) = Right (HexText txt) | otherwise = Left "Not a valid hex value" @@ -82,10 +90,11 @@ instance ToSchema HexText where & schema . format ?~ "hex" & schema . example ?~ toJSON (HexText "a1b2c3") -data GovActionId = GovActionId - { govActionIdTxHash :: HexText - , govActionIdIndex :: Integer - } +data GovActionId + = GovActionId + { govActionIdTxHash :: HexText + , govActionIdIndex :: Integer + } deriving (Eq) instance Show GovActionId where @@ -135,15 +144,14 @@ instance ToSchema GovActionId where & schema . example ?~ Aeson.String exampleGovActionId -data GovernanceActionType - = ParameterChange - | HardForkInitiation - | TreasuryWithdrawals - | NoConfidence - | NewCommittee - | NewConstitution - | InfoAction - deriving (Eq, Show, Read, Enum, Bounded, Generic) +data GovernanceActionType = ParameterChange | HardForkInitiation | TreasuryWithdrawals | NoConfidence | NewCommittee | NewConstitution | InfoAction deriving + ( Bounded + , Enum + , Eq + , Generic + , Read + , Show + ) instance FromJSON GovernanceActionType where parseJSON (Aeson.String governanceActionType) = pure $ fromMaybe InfoAction $ readMaybe (Text.unpack governanceActionType) @@ -163,7 +171,7 @@ instance ToSchema GovernanceActionType where instance FromHttpApiData GovernanceActionType where parseQueryParam t = case readMaybe $ Text.unpack t of - Just x -> Right x + Just x -> Right x Nothing -> Left ("incorrect governance action type: " <> t) instance ToParamSchema GovernanceActionType where @@ -172,11 +180,14 @@ instance ToParamSchema GovernanceActionType where & type_ ?~ OpenApiString & enum_ ?~ map toJSON (enumFromTo minBound maxBound :: [GovernanceActionType]) -data GovernanceActionSortMode - = SoonestToExpire - | NewestCreated - | MostYesVotes - deriving (Eq, Show, Read, Enum, Bounded, Generic) +data GovernanceActionSortMode = SoonestToExpire | NewestCreated | MostYesVotes deriving + ( Bounded + , Enum + , Eq + , Generic + , Read + , Show + ) instance FromJSON GovernanceActionSortMode where parseJSON (Aeson.String governanceActionSortMode) = pure $ fromJust $ readMaybe (Text.unpack governanceActionSortMode) @@ -196,7 +207,7 @@ instance ToSchema GovernanceActionSortMode where instance FromHttpApiData GovernanceActionSortMode where parseQueryParam t = case readMaybe $ Text.unpack t of - Just x -> Right x + Just x -> Right x Nothing -> Left ("incorrect governance action sort mode: " <> t) instance ToParamSchema GovernanceActionSortMode where @@ -206,7 +217,8 @@ instance ToParamSchema GovernanceActionSortMode where & enum_ ?~ map toJSON (enumFromTo minBound maxBound :: [GovernanceActionSortMode]) -newtype GovernanceActionDetails = GovernanceActionDetails { getValue :: Value } +newtype GovernanceActionDetails + = GovernanceActionDetails { getValue :: Value } deriving newtype (Show) instance FromJSON GovernanceActionDetails where @@ -216,8 +228,8 @@ instance FromJSON GovernanceActionDetails where (Aeson.Object _) -> fail "GovernanceActionDetails cannot have nested objects" (Aeson.Array a) -> forM_ (toList a) $ \case (Aeson.Object _) -> fail "GovernanceActionDetails cannot have nested objects" - (Aeson.Array _) -> fail "GovernanceActionDetails cannot have nested arrays" - _ -> pure () + (Aeson.Array _) -> fail "GovernanceActionDetails cannot have nested arrays" + _ -> pure () _ -> pure () return $ GovernanceActionDetails v parseJSON _ = fail "GovernanceActionDetails has to be an object" @@ -233,26 +245,50 @@ instance ToSchema GovernanceActionDetails where ?~ toJSON ("{\"some_key\": \"some value\", \"some_key2\": [1,2,3]}" :: Text) -data ProposalResponse = ProposalResponse - { proposalResponseId :: Text, - proposalResponseTxHash :: HexText, - proposalResponseIndex :: Integer, - proposalResponseType :: GovernanceActionType, - proposalResponseDetails :: Maybe GovernanceActionDetails, - proposalResponseExpiryDate :: Maybe UTCTime, - proposalResponseExpiryEpochNo :: Maybe Integer, - proposalResponseCreatedDate :: UTCTime, - proposalResponseCreatedEpochNo :: Integer, - proposalResponseUrl :: Text, - proposalResponseMetadataHash :: HexText, - proposalResponseTitle :: Maybe Text, - proposalResponseAbout :: Maybe Text, - proposalResponseMotivation :: Maybe Text, - proposalResponseRationale :: Maybe Text, - proposalResponseYesVotes :: Integer, - proposalResponseNoVotes :: Integer, - proposalResponseAbstainVotes :: Integer - } + +newtype GovernanceActionMetadata + = GovernanceActionMetadata Value + deriving newtype (Show) + +instance FromJSON GovernanceActionMetadata where + parseJSON v@(Aeson.Object o) = pure (GovernanceActionMetadata v) + parseJSON _ = fail "GovernanceActionMetadata has to be an object" + +instance ToJSON GovernanceActionMetadata where + toJSON (GovernanceActionMetadata g) = g + +instance ToSchema GovernanceActionMetadata where + declareNamedSchema _ = pure $ NamedSchema (Just "GovernanceActionMetadata") $ mempty + & type_ ?~ OpenApiObject + & description ?~ "A Governance Action metadata" + & example + ?~ toJSON + ("{\"some_key\": \"some value\", \"some_key2\": [1,2,3]}" :: Text) + + + +data ProposalResponse + = ProposalResponse + { proposalResponseId :: Text + , proposalResponseTxHash :: HexText + , proposalResponseIndex :: Integer + , proposalResponseType :: GovernanceActionType + , proposalResponseDetails :: Maybe GovernanceActionDetails + , proposalResponseExpiryDate :: Maybe UTCTime + , proposalResponseExpiryEpochNo :: Maybe Integer + , proposalResponseCreatedDate :: UTCTime + , proposalResponseCreatedEpochNo :: Integer + , proposalResponseUrl :: Text + , proposalResponseMetadataHash :: HexText + , proposalResponseTitle :: Maybe Text + , proposalResponseAbout :: Maybe Text + , proposalResponseMotivation :: Maybe Text + , proposalResponseRationale :: Maybe Text + , proposalResponseMetadata :: Maybe GovernanceActionMetadata + , proposalResponseYesVotes :: Integer + , proposalResponseNoVotes :: Integer + , proposalResponseAbstainVotes :: Integer + } deriving (Generic, Show) deriveJSON (jsonOptions "proposalResponse") ''ProposalResponse @@ -273,6 +309,7 @@ exampleProposalResponse = "{ \"id\": \"proposalId123\"," <> "\"about\": \"Proposal About\"," <> "\"motivation\": \"Proposal Motivation\"," <> "\"rationale\": \"Proposal Rationale\"," + <> "\"metadata\": {\"key\": \"value\"}," <> "\"yesVotes\": 0," <> "\"noVotes\": 0," <> "\"abstainVotes\": 0}" @@ -300,12 +337,14 @@ exampleListProposalsResponse = <> "\"elements\": [" <> exampleProposalResponse <> "]}" -data ListProposalsResponse = ListProposalsResponse - { listProposalsResponsePage :: Integer - , listProposalsResponsePageSize :: Integer - , listProposalsResponseTotal :: Integer - , listProposalsResponseElements :: [ProposalResponse] - } deriving (Generic, Show) +data ListProposalsResponse + = ListProposalsResponse + { listProposalsResponsePage :: Integer + , listProposalsResponsePageSize :: Integer + , listProposalsResponseTotal :: Integer + , listProposalsResponseElements :: [ProposalResponse] + } + deriving (Generic, Show) deriveJSON (jsonOptions "listProposalsResponse") ''ListProposalsResponse @@ -324,13 +363,14 @@ instance ToSchema ListProposalsResponse where & example ?~ toJSON exampleListProposalsResponse -data VoteParams = VoteParams - { voteParamsProposalId :: Text, - voteParamsDrepId :: HexText, - voteParamsVote :: Text, - voteParamsUrl :: Maybe Text, - voteParamsMetadataHash :: Maybe HexText - } +data VoteParams + = VoteParams + { voteParamsProposalId :: Text + , voteParamsDrepId :: HexText + , voteParamsVote :: Text + , voteParamsUrl :: Maybe Text + , voteParamsMetadataHash :: Maybe HexText + } deriving (Generic, Show) deriveJSON (jsonOptions "voteParams") ''VoteParams @@ -358,10 +398,11 @@ instance ToSchema VoteParams where & example ?~ toJSON exampleVoteParams -data VoteResponse = VoteResponse - { voteResponseVote :: VoteParams, - voteResponseProposal :: ProposalResponse - } +data VoteResponse + = VoteResponse + { voteResponseVote :: VoteParams + , voteResponseProposal :: ProposalResponse + } deriving (Generic, Show) deriveJSON (jsonOptions "voteResponse") ''VoteResponse @@ -386,16 +427,18 @@ instance ToSchema VoteResponse where & example ?~ toJSON exampleVoteResponse -data DRepInfoResponse = DRepInfoResponse - { dRepInfoResponseIsRegisteredAsDRep :: Bool - , dRepInfoResponseWasRegisteredAsDRep :: Bool - , dRepInfoResponseIsRegisteredAsSoleVoter :: Bool - , dRepInfoResponseWasRegisteredAsSoleVoter :: Bool - , dRepInfoResponseDeposit :: Maybe Integer - , dRepInfoResponseUrl :: Maybe Text - , dRepInfoResponseDataHash :: Maybe HexText - , dRepInfoResponseVotingPower :: Maybe Integer - } deriving (Generic, Show) +data DRepInfoResponse + = DRepInfoResponse + { dRepInfoResponseIsRegisteredAsDRep :: Bool + , dRepInfoResponseWasRegisteredAsDRep :: Bool + , dRepInfoResponseIsRegisteredAsSoleVoter :: Bool + , dRepInfoResponseWasRegisteredAsSoleVoter :: Bool + , dRepInfoResponseDeposit :: Maybe Integer + , dRepInfoResponseUrl :: Maybe Text + , dRepInfoResponseDataHash :: Maybe HexText + , dRepInfoResponseVotingPower :: Maybe Integer + } + deriving (Generic, Show) deriveJSON (jsonOptions "dRepInfoResponse") ''DRepInfoResponse @@ -424,10 +467,11 @@ instance ToSchema DRepInfoResponse where ?~ toJSON exampleDRepInfoResponse -data GetProposalResponse = GetProposalResponse - { getProposalResponseVote :: Maybe VoteParams, - getProposalResponseProposal :: ProposalResponse - } +data GetProposalResponse + = GetProposalResponse + { getProposalResponseVote :: Maybe VoteParams + , getProposalResponseProposal :: ProposalResponse + } deriving (Generic, Show) exampleGetProposalResponse :: Text @@ -454,14 +498,15 @@ instance ToSchema GetProposalResponse where ?~ toJSON exampleGetProposalResponse -newtype GetCurrentEpochParamsResponse = GetCurrentEpochParamsResponse { getCurrentEpochParamsResponse :: Maybe Value } +newtype GetCurrentEpochParamsResponse + = GetCurrentEpochParamsResponse { getCurrentEpochParamsResponse :: Maybe Value } deriving newtype (Show) instance FromJSON GetCurrentEpochParamsResponse where parseJSON = pure . GetCurrentEpochParamsResponse . Just instance ToJSON GetCurrentEpochParamsResponse where - toJSON (GetCurrentEpochParamsResponse Nothing) = Null + toJSON (GetCurrentEpochParamsResponse Nothing) = Null toJSON (GetCurrentEpochParamsResponse (Just params)) = toJSON params exampleGetCurrentEpochParamsResponse :: Text @@ -476,9 +521,8 @@ instance ToSchema GetCurrentEpochParamsResponse where ?~ toJSON exampleGetCurrentEpochParamsResponse newtype GetTransactionStatusResponse - = GetTransactionStatusResponse - { getTransactionstatusResponseTransactionConfirmed :: Bool - } deriving (Generic, Show) + = GetTransactionStatusResponse { getTransactionstatusResponseTransactionConfirmed :: Bool } + deriving (Generic, Show) deriveJSON (jsonOptions "getTransactionstatusResponse") ''GetTransactionStatusResponse @@ -501,12 +545,13 @@ instance ToSchema GetTransactionStatusResponse where & example ?~ toJSON exampleGetTransactionStatusResponse -newtype DRepHash = DRepHash Text +newtype DRepHash + = DRepHash Text deriving (Generic, Show) instance FromJSON DRepHash where parseJSON (Aeson.String s) = pure $ DRepHash s - parseJSON x = fail ("expected DRepHash to be a string but got: " <> Char8.unpack (encode x)) + parseJSON x = fail ("expected DRepHash to be a string but got: " <> Char8.unpack (encode x)) instance ToJSON DRepHash where toJSON (DRepHash raw) = toJSON raw @@ -523,22 +568,21 @@ instance ToSchema DRepHash where ?~ toJSON exampleDrepHash -data DRepStatus = Retired | Active | Inactive - deriving (Generic, Show) +data DRepStatus = Retired | Active | Inactive deriving (Generic, Show) -- ToJSON instance for DRepStatus instance ToJSON DRepStatus where - toJSON Retired = "Retired" - toJSON Active = "Active" + toJSON Retired = "Retired" + toJSON Active = "Active" toJSON Inactive = "Inactive" -- FromJSON instance for DRepStatus instance FromJSON DRepStatus where parseJSON = withText "DRepStatus" $ \case - "Retired" -> pure Retired - "Active" -> pure Active + "Retired" -> pure Retired + "Active" -> pure Active "Inactive" -> pure Inactive - _ -> fail "Invalid DRepStatus" + _ -> fail "Invalid DRepStatus" -- ToSchema instance for DRepStatus instance ToSchema DRepStatus where @@ -553,19 +597,19 @@ data DRepType = NormalDRep | SoleVoter instance Show DRepType where show NormalDRep = "DRep" - show SoleVoter = "SoleVoter" + show SoleVoter = "SoleVoter" -- ToJSON instance for DRepType instance ToJSON DRepType where toJSON NormalDRep = "DRep" - toJSON SoleVoter = "SoleVoter" + toJSON SoleVoter = "SoleVoter" -- FromJSON instance for DRepType instance FromJSON DRepType where parseJSON = withText "DRepType" $ \case - "DRep" -> pure NormalDRep + "DRep" -> pure NormalDRep "SoleVoter" -> pure SoleVoter - _ -> fail "Invalid DRepType" + _ -> fail "Invalid DRepType" -- ToSchema instance for DRepType instance ToSchema DRepType where @@ -574,16 +618,18 @@ instance ToSchema DRepType where & description ?~ "DRep Type" & enum_ ?~ map toJSON [NormalDRep, SoleVoter] -data DRep = DRep - { dRepDrepId :: DRepHash - , dRepView :: Text - , dRepUrl :: Maybe Text - , dRepMetadataHash :: Maybe Text - , dRepDeposit :: Integer - , dRepVotingPower :: Maybe Integer - , dRepStatus :: DRepStatus - , dRepType :: DRepType - } deriving (Generic, Show) +data DRep + = DRep + { dRepDrepId :: DRepHash + , dRepView :: Text + , dRepUrl :: Maybe Text + , dRepMetadataHash :: Maybe Text + , dRepDeposit :: Integer + , dRepVotingPower :: Maybe Integer + , dRepStatus :: DRepStatus + , dRepType :: DRepType + } + deriving (Generic, Show) deriveJSON (jsonOptions "dRep") ''DRep @@ -616,18 +662,19 @@ instance ToSchema DRep where -data GetNetworkMetricsResponse = GetNetworkMetricsResponse - { getNetworkMetricsResponseCurrentTime :: UTCTime - , getNetworkMetricsResponseCurrentEpoch :: Integer - , getNetworkMetricsResponseCurrentBlock :: Integer - , getNetworkMetricsResponseUniqueDelegators :: Integer - , getNetworkMetricsResponseTotalDelegations :: Integer - , getNetworkMetricsResponseTotalGovernanceActions :: Integer - , getNetworkMetricsResponseTotalDRepVotes :: Integer - , getNetworkMetricsResponseTotalRegisteredDReps :: Integer - , getNetworkMetricsResponseAlwaysAbstainVotingPower :: Integer - , getNetworkMetricsResponseAlwaysNoConfidenceVotingPower :: Integer - } +data GetNetworkMetricsResponse + = GetNetworkMetricsResponse + { getNetworkMetricsResponseCurrentTime :: UTCTime + , getNetworkMetricsResponseCurrentEpoch :: Integer + , getNetworkMetricsResponseCurrentBlock :: Integer + , getNetworkMetricsResponseUniqueDelegators :: Integer + , getNetworkMetricsResponseTotalDelegations :: Integer + , getNetworkMetricsResponseTotalGovernanceActions :: Integer + , getNetworkMetricsResponseTotalDRepVotes :: Integer + , getNetworkMetricsResponseTotalRegisteredDReps :: Integer + , getNetworkMetricsResponseAlwaysAbstainVotingPower :: Integer + , getNetworkMetricsResponseAlwaysNoConfidenceVotingPower :: Integer + } deriveJSON (jsonOptions "getNetworkMetricsResponse") ''GetNetworkMetricsResponse @@ -649,4 +696,4 @@ instance ToSchema GetNetworkMetricsResponse where & type_ ?~ OpenApiObject & description ?~ "GetNetworkMetricsResponse" & example - ?~ toJSON exampleGetNetworkMetricsResponse \ No newline at end of file + ?~ toJSON exampleGetNetworkMetricsResponse diff --git a/govtool/backend/src/VVA/API/Utils.hs b/govtool/backend/src/VVA/API/Utils.hs index 49a47f672..9ba8f26f8 100644 --- a/govtool/backend/src/VVA/API/Utils.hs +++ b/govtool/backend/src/VVA/API/Utils.hs @@ -1,13 +1,14 @@ module VVA.API.Utils where -import Data.Aeson (Options (..), defaultOptions) -import Data.Char -import Foreign (pooledMalloc) +import Data.Aeson (Options (..), defaultOptions) +import Data.Char + +import Foreign (pooledMalloc) -- | Apply function to first element in the list. applyFirst :: (a -> a) -> [a] -> [a] -applyFirst _ [] = [] -applyFirst f [x] = [f x] +applyFirst _ [] = [] +applyFirst f [x] = [f x] applyFirst f (x : xs) = f x : xs jsonOptions :: String -> Options diff --git a/govtool/backend/src/VVA/AdaHolder.hs b/govtool/backend/src/VVA/AdaHolder.hs index b6766ce67..1e177235f 100644 --- a/govtool/backend/src/VVA/AdaHolder.hs +++ b/govtool/backend/src/VVA/AdaHolder.hs @@ -1,25 +1,29 @@ -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module VVA.AdaHolder where -import Control.Monad.Except -import Control.Monad.Reader -import Crypto.Hash -import Data.ByteString (ByteString) -import qualified Data.ByteString.Char8 as C -import Data.FileEmbed (embedFile) -import Data.Scientific -import Data.String (fromString) -import Data.Text (Text, unpack) -import qualified Data.Text.Encoding as Text -import qualified Data.Text.IO as Text +import Control.Monad.Except +import Control.Monad.Reader + +import Crypto.Hash + +import Data.ByteString (ByteString) +import qualified Data.ByteString.Char8 as C +import Data.FileEmbed (embedFile) +import Data.Has (Has) +import Data.Scientific +import Data.String (fromString) +import Data.Text (Text, unpack) +import qualified Data.Text.Encoding as Text +import qualified Data.Text.IO as Text + import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import Data.Has (Has) -import VVA.Pool (withPool, ConnectionPool) + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -37,9 +41,9 @@ getCurrentDelegation :: getCurrentDelegation stakeKey = withPool $ \conn -> do result <- liftIO $ SQL.query conn getCurrentDelegationSql (SQL.Only stakeKey) case result of - [] -> return Nothing + [] -> return Nothing [SQL.Only delegation] -> return $ Just delegation - _ -> error ("multiple delegations for stake key: " <> unpack stakeKey) + _ -> error ("multiple delegations for stake key: " <> unpack stakeKey) getVotingPowerSql :: SQL.Query getVotingPowerSql = sqlFrom $(embedFile "sql/get-stake-key-voting-power.sql") @@ -54,4 +58,4 @@ getStakeKeyVotingPower stakeKey = withPool $ \conn -> do [(votingPower,_)] -> return $ floor votingPower _ -> do liftIO $ Text.putStrLn ("couldn't fetch voting power for stake key: " <> stakeKey) - return 0 \ No newline at end of file + return 0 diff --git a/govtool/backend/src/VVA/Cache.hs b/govtool/backend/src/VVA/Cache.hs index 5a5a2fcc6..f3cfe3299 100644 --- a/govtool/backend/src/VVA/Cache.hs +++ b/govtool/backend/src/VVA/Cache.hs @@ -1,10 +1,11 @@ module VVA.Cache where -import qualified Data.Cache as Cache -import Data.Hashable (Hashable) -import Control.Monad.IO.Class (MonadIO, liftIO) -import Data.Text (Text) -import Data.Aeson (Value) +import Control.Monad.IO.Class (MonadIO, liftIO) + +import Data.Aeson (Value) +import qualified Data.Cache as Cache +import Data.Hashable (Hashable) +import Data.Text (Text) cacheRequest :: (Monad m, MonadIO m, Hashable k) => Cache.Cache k v -> k -> m v -> m v @@ -15,4 +16,4 @@ cacheRequest cache key action = do v <- action liftIO $ Cache.insert cache key v return v - Just v -> return v \ No newline at end of file + Just v -> return v diff --git a/govtool/backend/src/VVA/CommandLine.hs b/govtool/backend/src/VVA/CommandLine.hs index 82d28082a..49e9ab94f 100644 --- a/govtool/backend/src/VVA/CommandLine.hs +++ b/govtool/backend/src/VVA/CommandLine.hs @@ -1,18 +1,18 @@ module VVA.CommandLine - ( cmdParser, - CommandLineConfig (..), - Command (..), - ) -where + ( Command (..) + , CommandLineConfig (..) + , cmdParser + ) where -import Options.Applicative +import Options.Applicative data Command = StartApp | ShowConfig deriving (Show) -data CommandLineConfig = CommandLineConfig - { clcConfigPath :: Maybe FilePath, - clcCommand :: Command - } +data CommandLineConfig + = CommandLineConfig + { clcConfigPath :: Maybe FilePath + , clcCommand :: Command + } deriving (Show) cmdParser :: ParserInfo CommandLineConfig diff --git a/govtool/backend/src/VVA/Config.hs b/govtool/backend/src/VVA/Config.hs index 03d0bdd48..7055c33ed 100644 --- a/govtool/backend/src/VVA/Config.hs +++ b/govtool/backend/src/VVA/Config.hs @@ -1,15 +1,12 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeOperators #-} -- | Module : VVA.Config -- Description : Configuration interface @@ -19,65 +16,70 @@ -- configuration backend (Conferer) and ensures that the provided configuration -- is valid and satisfies required invariants. module VVA.Config - ( -- * Data types and basic functions - VVAConfig (..), - loadVVAConfig, - - -- * Data type conversions - vvaConfigToText, - getDbSyncConnectionString, - getServerPort, - getServerHost, - ) -where - -import Conferer -import qualified Conferer.Source.Aeson as JSON -import qualified Conferer.Source.Env as Env -import Control.Monad.Reader -import Data.Aeson + ( -- * Data types and basic functions + VVAConfig (..) + , loadVVAConfig + -- * Data type conversions + , getDbSyncConnectionString + , getServerHost + , getServerPort + , vvaConfigToText + ) where + +import Conferer +import qualified Conferer.Source.Aeson as JSON +import qualified Conferer.Source.Env as Env + +import Control.Monad.Reader + +import Data.Aeson import qualified Data.Aeson.Encode.Pretty as AP -import Data.ByteString (ByteString, toStrict) -import Data.Maybe (fromMaybe) -import Data.Text (Text) -import qualified Data.Text as Text -import Data.Text.Encoding (decodeUtf8, encodeUtf8) -import GHC.Generics -import VVA.CommandLine (CommandLineConfig (..), clcConfigPath) -import Data.Has (Has, getter) +import Data.ByteString (ByteString, toStrict) +import Data.Has (Has, getter) +import Data.Maybe (fromMaybe) +import Data.Text (Text) +import qualified Data.Text as Text +import Data.Text.Encoding (decodeUtf8, encodeUtf8) + +import GHC.Generics + +import VVA.CommandLine (CommandLineConfig (..), + clcConfigPath) -- | PostgreSQL database access information. -data DBConfig = DBConfig - { -- | URL of host running the database - dBConfigHost :: Text, - -- | Database name - dBConfigDbname :: Text, - -- | User name - dBConfigUser :: Text, - -- | Database password - dBConfigPassword :: Text, - -- | Port - dBConfigPort :: Int - } - deriving (Generic, Show, FromConfig) +data DBConfig + = DBConfig + { -- | URL of host running the database + dBConfigHost :: Text + -- | Database name + , dBConfigDbname :: Text + -- | User name + , dBConfigUser :: Text + -- | Database password + , dBConfigPassword :: Text + -- | Port + , dBConfigPort :: Int + } + deriving (FromConfig, Generic, Show) instance DefaultConfig DBConfig where configDef = DBConfig "localhost" "cexplorer" "postgres" "test" 9903 -- | Internal, backend-dependent representation of configuration for DEX. This -- data type should not be exported from this module. -data VVAConfigInternal = VVAConfigInternal - { -- | db-sync database access. - vVAConfigInternalDbsyncconfig :: DBConfig, - -- | Server port. - vVAConfigInternalPort :: Int, - -- | Server host. - vVAConfigInternalHost :: Text, - -- | Request cache duration - vVaConfigInternalCacheDurationSeconds :: Int, - -- | Sentry DSN - vVAConfigInternalSentrydsn :: String - } - deriving (Generic, Show, FromConfig) +data VVAConfigInternal + = VVAConfigInternal + { -- | db-sync database access. + vVAConfigInternalDbsyncconfig :: DBConfig + -- | Server port. + , vVAConfigInternalPort :: Int + -- | Server host. + , vVAConfigInternalHost :: Text + -- | Request cache duration + , vVaConfigInternalCacheDurationSeconds :: Int + -- | Sentry DSN + , vVAConfigInternalSentrydsn :: String + } + deriving (FromConfig, Generic, Show) instance DefaultConfig VVAConfigInternal where configDef = @@ -90,19 +92,20 @@ instance DefaultConfig VVAConfigInternal where } -- | DEX configuration. -data VVAConfig = VVAConfig - { -- | db-sync database credentials. - dbSyncConnectionString :: Text, - -- | Server port. - serverPort :: Int, - -- | Server host. - serverHost :: Text, - -- | Request cache duration - cacheDurationSeconds :: Int, - -- | Sentry DSN - sentryDSN :: String - } - deriving (Generic, ToJSON, Show) +data VVAConfig + = VVAConfig + { -- | db-sync database credentials. + dbSyncConnectionString :: Text + -- | Server port. + , serverPort :: Int + -- | Server host. + , serverHost :: Text + -- | Request cache duration + , cacheDurationSeconds :: Int + -- | Sentry DSN + , sentryDSN :: String + } + deriving (Generic, Show, ToJSON) -- | Convert 'DBConfig' to a string required by PostgreSQL backend. dbConfigToText :: DBConfig -> Text @@ -165,7 +168,7 @@ loadVVAConfig configFile = do getDbSyncConnectionString :: (Has VVAConfig r, MonadReader r m) => m ByteString -getDbSyncConnectionString = encodeUtf8 <$> asks (dbSyncConnectionString . getter) +getDbSyncConnectionString = asks (encodeUtf8 . dbSyncConnectionString . getter) -- | Access server port. getServerPort :: diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index 211119a8e..52008a2cd 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -1,42 +1,38 @@ -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module VVA.DRep where -import Control.Monad.Except (MonadError) -import Control.Monad.Reader -import Crypto.Hash -import Data.Maybe (fromMaybe) -import Data.ByteString (ByteString) -import qualified Data.ByteString.Base16 as Base16 -import qualified Data.ByteString.Char8 as C -import Data.FileEmbed (embedFile) -import qualified Data.Map as M -import Data.Scientific -import Data.String (fromString) -import Data.Text (Text, unpack, pack) -import qualified Data.Text.Encoding as Text -import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import qualified VVA.Proposal as Proposal -import Data.Foldable (Foldable(sum)) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) -import VVA.Types - ( AppError - , DRepRegistration(..) - , Proposal(..) - , Vote(..) - , DRepInfo(..) - , DRepType(..) - , DRepStatus(..) - ) +import Control.Monad.Except (MonadError) +import Control.Monad.Reader + +import Crypto.Hash +import Data.ByteString (ByteString) +import qualified Data.ByteString.Base16 as Base16 +import qualified Data.ByteString.Char8 as C +import Data.FileEmbed (embedFile) +import Data.Foldable (Foldable (sum)) +import Data.Has (Has) +import qualified Data.Map as M +import Data.Maybe (fromMaybe, isJust, isNothing) +import Data.Scientific +import Data.String (fromString) +import Data.Text (Text, pack, unpack) +import qualified Data.Text.Encoding as Text +import qualified Database.PostgreSQL.Simple as SQL +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) +import qualified VVA.Proposal as Proposal +import VVA.Types (AppError, DRepInfo (..), + DRepRegistration (..), + DRepStatus (..), DRepType (..), + Proposal (..), Vote (..)) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -69,9 +65,9 @@ listDReps = withPool $ \conn -> do (_, d) | d < 0 -> Retired (isActive, d) | d >= 0 && isActive -> Active | d >= 0 && not isActive -> Inactive - , let drepType | url == Nothing && wasDRep = DRep - | url == Nothing && not wasDRep = SoleVoter - | url /= Nothing = DRep + , let drepType | isNothing url && wasDRep = DRep + | isNothing url && not wasDRep = SoleVoter + | Data.Maybe.isJust url = DRep ] getVotesSql :: SQL.Query @@ -130,4 +126,4 @@ getDRepInfo drepId = withPool $ \conn -> do , dRepInfoDataHash = dataHash , dRepInfoVotingPower = votingPower } - [] -> return $ DRepInfo False False False False Nothing Nothing Nothing Nothing \ No newline at end of file + [] -> return $ DRepInfo False False False False Nothing Nothing Nothing Nothing diff --git a/govtool/backend/src/VVA/Epoch.hs b/govtool/backend/src/VVA/Epoch.hs index f29819038..9c48db028 100644 --- a/govtool/backend/src/VVA/Epoch.hs +++ b/govtool/backend/src/VVA/Epoch.hs @@ -1,22 +1,24 @@ +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TemplateHaskell #-} module VVA.Epoch where +import Control.Monad.Except (MonadError, throwError) +import Control.Monad.Reader + +import Data.Aeson (Value) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Has (Has) +import Data.String (fromString) +import Data.Text (Text, unpack) +import qualified Data.Text.Encoding as Text -import Data.ByteString (ByteString) -import Control.Monad.Except (MonadError, throwError) -import Control.Monad.Reader -import Data.FileEmbed (embedFile) -import Data.Text (Text, unpack) -import Data.String (fromString) -import qualified Data.Text.Encoding as Text import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import Data.Aeson (Value) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -30,5 +32,5 @@ getCurrentEpochParams :: getCurrentEpochParams = withPool $ \conn -> do result <- liftIO $ SQL.query_ conn getCurrentEpochParamsSql case result of - [] -> return Nothing + [] -> return Nothing (SQL.Only params:_) -> return $ Just params diff --git a/govtool/backend/src/VVA/Network.hs b/govtool/backend/src/VVA/Network.hs index a76be9452..2329c689e 100644 --- a/govtool/backend/src/VVA/Network.hs +++ b/govtool/backend/src/VVA/Network.hs @@ -1,24 +1,26 @@ +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TemplateHaskell #-} module VVA.Network where +import Control.Monad.Except (MonadError, throwError) +import Control.Monad.Reader + +import Data.Aeson (Value) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Has (Has) +import Data.String (fromString) +import Data.Text (Text, unpack) +import qualified Data.Text.Encoding as Text +import Data.Time.Clock -import Data.ByteString (ByteString) -import Control.Monad.Except (MonadError, throwError) -import Control.Monad.Reader -import Data.FileEmbed (embedFile) -import Data.Text (Text, unpack) -import Data.String (fromString) -import qualified Data.Text.Encoding as Text import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import Data.Aeson (Value) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) -import VVA.Types -import Data.Time.Clock + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) +import VVA.Types sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -31,7 +33,7 @@ networkMetrics :: m NetworkMetrics networkMetrics = withPool $ \conn -> do result <- liftIO $ SQL.query_ conn networkMetricsSql - current_time <- liftIO $ getCurrentTime + current_time <- liftIO getCurrentTime case result of [( epoch_no , block_no diff --git a/govtool/backend/src/VVA/Pool.hs b/govtool/backend/src/VVA/Pool.hs index 5ecfa0a34..0431d596b 100644 --- a/govtool/backend/src/VVA/Pool.hs +++ b/govtool/backend/src/VVA/Pool.hs @@ -1,11 +1,14 @@ {-# LANGUAGE FlexibleContexts #-} + module VVA.Pool where -import Data.Has (Has, getter) -import Data.Pool (Pool, takeResource, putResource) -import Database.PostgreSQL.Simple (Connection) -import Control.Monad.Reader (MonadReader, asks) -import Control.Monad.IO.Class (MonadIO, liftIO) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Control.Monad.Reader (MonadReader, asks) + +import Data.Has (Has, getter) +import Data.Pool (Pool, putResource, takeResource) + +import Database.PostgreSQL.Simple (Connection) type ConnectionPool = Pool Connection @@ -18,4 +21,4 @@ withPool f = do (conn,localPool) <- liftIO $ takeResource pool result <- f conn liftIO $ putResource localPool conn - return result \ No newline at end of file + return result diff --git a/govtool/backend/src/VVA/Proposal.hs b/govtool/backend/src/VVA/Proposal.hs index 45980af7d..e2c949992 100644 --- a/govtool/backend/src/VVA/Proposal.hs +++ b/govtool/backend/src/VVA/Proposal.hs @@ -1,39 +1,41 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module VVA.Proposal where -import Control.Monad.Except (MonadError, throwError) -import Control.Monad.Reader -import Data.ByteString (ByteString) -import Data.FileEmbed (embedFile) -import Data.Foldable (fold) -import qualified Data.Map as Map -import Data.Maybe (fromMaybe) -import Data.Monoid (Sum (..), getSum) -import Data.Scientific -import Data.String (fromString) -import Data.Text (Text, unpack, pack) -import qualified Data.Text.Encoding as Text -import qualified Data.Text.IO as Text -import Data.Time +import Control.Exception (throw) +import Control.Monad.Except (MonadError, throwError) +import Control.Monad.Reader + +import Data.Aeson +import Data.Aeson.Types (Parser, parseMaybe) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Foldable (fold) +import Data.Has (Has) +import qualified Data.Map as Map +import Data.Maybe (fromMaybe) +import Data.Monoid (Sum (..), getSum) +import Data.Scientific +import Data.String (fromString) +import Data.Text (Text, pack, unpack) +import qualified Data.Text.Encoding as Text +import qualified Data.Text.IO as Text +import Data.Time + import qualified Database.PostgreSQL.Simple as SQL -import qualified GHC.Generics as SQL -import VVA.Config -import Data.Aeson (Value) -import Text.Read (readMaybe) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) -import Control.Exception (throw) -import VVA.Types (Proposal(..), AppError(..)) -import Data.Aeson -import Data.Aeson.Types (parseMaybe, Parser) -import Data.Text (Text) +import qualified GHC.Generics as SQL + +import Text.Read (readMaybe) + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) +import VVA.Types (AppError (..), Proposal (..)) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -58,7 +60,6 @@ getProposal txHash index = do [a] -> return a _ -> throwError $ CriticalError ("Multiple proposal found for id: " <> txHash <> "#" <> pack (show index) <> ". This should never happen") - getProposals :: (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadFail m, MonadError AppError m) => Maybe [Text] -> @@ -85,6 +86,7 @@ getProposals mProposalIds = withPool $ \conn -> do , about' , motivation' , rationale' + , metadataJson' , yesVotes' , noVotes' , abstainVotes' @@ -108,8 +110,9 @@ getProposals mProposalIds = withPool $ \conn -> do about' motivation' rationale' + metadataJson' (floor @Scientific yesVotes') (floor @Scientific noVotes') (floor @Scientific abstainVotes') ) - proposalResults \ No newline at end of file + proposalResults diff --git a/govtool/backend/src/VVA/Transaction.hs b/govtool/backend/src/VVA/Transaction.hs index a67cb1371..73ebc594a 100644 --- a/govtool/backend/src/VVA/Transaction.hs +++ b/govtool/backend/src/VVA/Transaction.hs @@ -1,29 +1,31 @@ +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TemplateHaskell #-} module VVA.Transaction where +import Control.Exception (throw) +import Control.Monad.Except (MonadError, throwError) +import Control.Monad.Reader + +import Data.Aeson (Value) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Has (Has) +import Data.String (fromString) +import Data.Text (Text, pack, unpack) +import qualified Data.Text.Encoding as Text -import Data.ByteString (ByteString) -import Control.Monad.Except (MonadError, throwError) -import Control.Monad.Reader -import Data.FileEmbed (embedFile) -import Data.Text (Text, unpack, pack) -import Data.String (fromString) -import qualified Data.Text.Encoding as Text import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import Data.Aeson (Value) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) -import VVA.Types (TransactionStatus(..), AppError(..)) -import Control.Exception (throw) + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) +import VVA.Types (AppError (..), + TransactionStatus (..)) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs - getTransactionStatusSql :: SQL.Query getTransactionStatusSql = sqlFrom $(embedFile "sql/get-transaction-status.sql") @@ -36,4 +38,4 @@ getTransactionStatus transactionId = withPool $ \conn -> do case result of [SQL.Only True] -> return TransactionConfirmed [SQL.Only False] -> return TransactionUnconfirmed - x -> throwError $ CriticalError ("Expected exactly one result from get-transaction-status.sql but got " <> pack (show (length x)) <> " of them. This should never happen") \ No newline at end of file + x -> throwError $ CriticalError ("Expected exactly one result from get-transaction-status.sql but got " <> pack (show (length x)) <> " of them. This should never happen") diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index 499bac20a..a741489be 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -1,33 +1,37 @@ -{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} module VVA.Types where -import Data.Text (Text) -import Control.Exception -import Control.Monad.IO.Class (MonadIO) -import Control.Monad.Reader (MonadReader) -import Control.Monad.Fail (MonadFail) -import Control.Monad.Except (MonadError) -import VVA.Config -import VVA.Cache -import Data.Has -import Data.Pool (Pool) -import Database.PostgreSQL.Simple (Connection) -import Data.Aeson (Value) -import Data.Time (UTCTime) -import qualified Data.Cache as Cache +import Control.Exception +import Control.Monad.Except (MonadError) +import Control.Monad.Fail (MonadFail) +import Control.Monad.IO.Class (MonadIO) +import Control.Monad.Reader (MonadReader) + +import Data.Aeson (Value) +import qualified Data.Cache as Cache +import Data.Has +import Data.Pool (Pool) +import Data.Text (Text) +import Data.Time (UTCTime) + +import Database.PostgreSQL.Simple (Connection) + +import VVA.Cache +import VVA.Config type App m = (MonadReader AppEnv m, MonadIO m, MonadFail m, MonadError AppError m) -data AppEnv = AppEnv - { vvaConfig :: VVAConfig - , vvaCache :: CacheEnv - , vvaConnectionPool :: Pool Connection - } +data AppEnv + = AppEnv + { vvaConfig :: VVAConfig + , vvaCache :: CacheEnv + , vvaConnectionPool :: Pool Connection + } instance Has VVAConfig AppEnv where getter AppEnv {vvaConfig} = vvaConfig @@ -42,103 +46,100 @@ instance Has (Pool Connection) AppEnv where modifier f a@AppEnv {vvaConnectionPool} = a {vvaConnectionPool = f vvaConnectionPool} data AppError - = ValidationError Text - | NotFoundError Text - | CriticalError Text - deriving Show - + = ValidationError Text + | NotFoundError Text + | CriticalError Text + deriving (Show) instance Exception AppError -data Vote = Vote - { voteProposalId :: Integer, - voteDrepId :: Text, - voteVote :: Text, - voteUrl :: Maybe Text, - voteDocHash :: Maybe Text - } - -data DRepInfo = DRepInfo - { dRepInfoIsRegisteredAsDRep :: Bool - , dRepInfoWasRegisteredAsDRep :: Bool - , dRepInfoIsRegisteredAsSoleVoter :: Bool - , dRepInfoWasRegisteredAsSoleVoter :: Bool - , dRepInfoDeposit :: Maybe Integer - , dRepInfoUrl :: Maybe Text - , dRepInfoDataHash :: Maybe Text - , dRepInfoVotingPower :: Maybe Integer - } - -data DRepStatus - = Retired - | Active - | Inactive - -data DRepType - = DRep - | SoleVoter - -data DRepRegistration = DRepRegistration - { dRepRegistrationDRepHash :: Text - , dRepRegistrationView :: Text - , dRepRegistrationUrl :: Maybe Text - , dRepRegistrationDataHash :: Maybe Text - , dRepRegistrationDeposit :: Integer - , dRepRegistrationVotingPower :: Maybe Integer - , dRepRegistrationStatus :: DRepStatus - , dRepRegistrationType :: DRepType - } - -data Proposal = Proposal - { proposalId :: Integer, - proposalTxHash :: Text, - proposalIndex :: Integer, - proposalType :: Text, - proposalDetails :: Maybe Value, - proposalExpiryDate :: Maybe UTCTime, - proposalExpiryEpochNo :: Maybe Integer, - proposalCreatedDate :: UTCTime, - proposalCreatedEpochNo :: Integer, - proposalUrl :: Text, - proposalDocHash :: Text, - proposalTitle :: Maybe Text, - proposalAbout :: Maybe Text, - proposalMotivaiton :: Maybe Text, - proposalRationale :: Maybe Text, - proposalYesVotes :: Integer, - proposalNoVotes :: Integer, - proposalAbstainVotes :: Integer - } deriving (Show) - - - -data TransactionStatus - = TransactionConfirmed - | TransactionUnconfirmed - -data CacheEnv = CacheEnv - { proposalListCache :: Cache.Cache () [Proposal] - , getProposalCache :: Cache.Cache (Text, Integer) Proposal - , currentEpochCache :: Cache.Cache () (Maybe Value) - , adaHolderVotingPowerCache :: Cache.Cache Text Integer - , adaHolderGetCurrentDelegationCache :: Cache.Cache Text (Maybe Text) - , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) - , dRepInfoCache :: Cache.Cache Text DRepInfo - , dRepVotingPowerCache :: Cache.Cache Text Integer - , dRepListCache :: Cache.Cache () [DRepRegistration] - , networkMetricsCache :: Cache.Cache () NetworkMetrics - } - -data NetworkMetrics = NetworkMetrics - { networkMetricsCurrentTime :: UTCTime - , networkMetricsCurrentEpoch :: Integer - , networkMetricsCurrentBlock :: Integer - , networkMetricsUniqueDelegators :: Integer - , networkMetricsTotalDelegations :: Integer - , networkMetricsTotalGovernanceActions :: Integer - , networkMetricsTotalDRepVotes :: Integer - , networkMetricsTotalRegisteredDReps :: Integer - , networkMetricsAlwaysAbstainVotingPower :: Integer - , networkMetricsAlwaysNoConfidenceVotingPower :: Integer - } - +data Vote + = Vote + { voteProposalId :: Integer + , voteDrepId :: Text + , voteVote :: Text + , voteUrl :: Maybe Text + , voteDocHash :: Maybe Text + } + +data DRepInfo + = DRepInfo + { dRepInfoIsRegisteredAsDRep :: Bool + , dRepInfoWasRegisteredAsDRep :: Bool + , dRepInfoIsRegisteredAsSoleVoter :: Bool + , dRepInfoWasRegisteredAsSoleVoter :: Bool + , dRepInfoDeposit :: Maybe Integer + , dRepInfoUrl :: Maybe Text + , dRepInfoDataHash :: Maybe Text + , dRepInfoVotingPower :: Maybe Integer + } + +data DRepStatus = Retired | Active | Inactive + +data DRepType = DRep | SoleVoter + +data DRepRegistration + = DRepRegistration + { dRepRegistrationDRepHash :: Text + , dRepRegistrationView :: Text + , dRepRegistrationUrl :: Maybe Text + , dRepRegistrationDataHash :: Maybe Text + , dRepRegistrationDeposit :: Integer + , dRepRegistrationVotingPower :: Maybe Integer + , dRepRegistrationStatus :: DRepStatus + , dRepRegistrationType :: DRepType + } + +data Proposal + = Proposal + { proposalId :: Integer + , proposalTxHash :: Text + , proposalIndex :: Integer + , proposalType :: Text + , proposalDetails :: Maybe Value + , proposalExpiryDate :: Maybe UTCTime + , proposalExpiryEpochNo :: Maybe Integer + , proposalCreatedDate :: UTCTime + , proposalCreatedEpochNo :: Integer + , proposalUrl :: Text + , proposalDocHash :: Text + , proposalTitle :: Maybe Text + , proposalAbout :: Maybe Text + , proposalMotivaiton :: Maybe Text + , proposalRationale :: Maybe Text + , proposalMetadata :: Maybe Value + , proposalYesVotes :: Integer + , proposalNoVotes :: Integer + , proposalAbstainVotes :: Integer + } + deriving (Show) + +data TransactionStatus = TransactionConfirmed | TransactionUnconfirmed + +data CacheEnv + = CacheEnv + { proposalListCache :: Cache.Cache () [Proposal] + , getProposalCache :: Cache.Cache (Text, Integer) Proposal + , currentEpochCache :: Cache.Cache () (Maybe Value) + , adaHolderVotingPowerCache :: Cache.Cache Text Integer + , adaHolderGetCurrentDelegationCache :: Cache.Cache Text (Maybe Text) + , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) + , dRepInfoCache :: Cache.Cache Text DRepInfo + , dRepVotingPowerCache :: Cache.Cache Text Integer + , dRepListCache :: Cache.Cache () [DRepRegistration] + , networkMetricsCache :: Cache.Cache () NetworkMetrics + } + +data NetworkMetrics + = NetworkMetrics + { networkMetricsCurrentTime :: UTCTime + , networkMetricsCurrentEpoch :: Integer + , networkMetricsCurrentBlock :: Integer + , networkMetricsUniqueDelegators :: Integer + , networkMetricsTotalDelegations :: Integer + , networkMetricsTotalGovernanceActions :: Integer + , networkMetricsTotalDRepVotes :: Integer + , networkMetricsTotalRegisteredDReps :: Integer + , networkMetricsAlwaysAbstainVotingPower :: Integer + , networkMetricsAlwaysNoConfidenceVotingPower :: Integer + } diff --git a/govtool/backend/stack.yaml b/govtool/backend/stack.yaml index 31940eb29..b6238c401 100644 --- a/govtool/backend/stack.yaml +++ b/govtool/backend/stack.yaml @@ -3,4 +3,4 @@ packages: - . extra-deps: -- raven-haskell-0.1.4.1@sha256:9187272adc064197528645b5ad9b89163b668f386f34016d97fa646d5c790784 \ No newline at end of file +- raven-haskell-0.1.4.1@sha256:9187272adc064197528645b5ad9b89163b668f386f34016d97fa646d5c790784 diff --git a/govtool/backend/vva-be.cabal b/govtool/backend/vva-be.cabal index 2df658d4e..ccd247dc4 100644 --- a/govtool/backend/vva-be.cabal +++ b/govtool/backend/vva-be.cabal @@ -60,7 +60,7 @@ executable vva-be , lens , cache , clock - , resource-pool + , resource-pool == 0.2.3.2 , postgresql-simple , data-has , bytestring @@ -113,4 +113,4 @@ library , VVA.Cache , VVA.Pool , VVA.Types - , VVA.Network \ No newline at end of file + , VVA.Network diff --git a/govtool/frontend/.lighthouserc.yml b/govtool/frontend/.lighthouserc.yml index 1f13674c0..5e963f7ae 100644 --- a/govtool/frontend/.lighthouserc.yml +++ b/govtool/frontend/.lighthouserc.yml @@ -2,4 +2,4 @@ ci: collect: staticDistDir: "./dist" url: - - "http://localhost" \ No newline at end of file + - "http://localhost" diff --git a/govtool/frontend/default.nix b/govtool/frontend/default.nix index 979ab1c95..29be71953 100644 --- a/govtool/frontend/default.nix +++ b/govtool/frontend/default.nix @@ -15,7 +15,7 @@ project.overrideAttrs (attrs: { tput bold warn "Welcome to GovTool!" 4 warn "This is a frontend development shell." 4 - warn "Read the govtool/frontend/README.md to get more info about this module." 8 + warn "Read the ${./README.md} to get more info about this module." 8 rm -rf ./node_modules ln -s ${project.out}/libexec/voltaire-voting-app/node_modules ./node_modules ''; diff --git a/govtool/frontend/src/components/atoms/TextArea.tsx b/govtool/frontend/src/components/atoms/TextArea.tsx index 59ee06bfa..823a8acdb 100644 --- a/govtool/frontend/src/components/atoms/TextArea.tsx +++ b/govtool/frontend/src/components/atoms/TextArea.tsx @@ -8,11 +8,9 @@ import { TextAreaProps } from "./types"; const TextAreaBase = styled(TextareaAutosize)( () => ` font-family: "Poppins"; - font-size: 16px; font-weight: 400; ::placeholder { font-family: "Poppins"; - font-size: 16px; font-weight: 400; color: #a6a6a6; } @@ -20,7 +18,17 @@ const TextAreaBase = styled(TextareaAutosize)( ); export const TextArea = forwardRef( - ({ errorMessage, maxLength = 500, onBlur, onFocus, ...props }, ref) => { + ( + { + errorMessage, + maxLength = 500, + onBlur, + onFocus, + isModifiedLayout, + ...props + }, + ref, + ) => { const { isMobile } = useScreenDimension(); const textAraeRef = useRef(null); @@ -51,19 +59,32 @@ export const TextArea = forwardRef( [handleBlur, handleFocus], ); + const getTexAreaHeight = () => { + if (isModifiedLayout && isMobile) return "312px"; + if (isModifiedLayout) return "208px"; + if (isMobile) return "104px"; + return "128px"; + }; + return ( ); diff --git a/govtool/frontend/src/components/atoms/types.ts b/govtool/frontend/src/components/atoms/types.ts index ff2e603b8..2a02f519b 100644 --- a/govtool/frontend/src/components/atoms/types.ts +++ b/govtool/frontend/src/components/atoms/types.ts @@ -11,6 +11,7 @@ import * as TooltipMUI from "@mui/material/Tooltip"; export type ButtonProps = Omit & { size?: "small" | "medium" | "large" | "extraLarge"; + dataTestId?: string; }; export type LoadingButtonProps = ButtonProps & { @@ -66,6 +67,7 @@ export type FormHelpfulTextProps = { export type TextAreaProps = TextareaAutosizeProps & { errorMessage?: string; + isModifiedLayout?: boolean; }; export type InfoTextProps = { diff --git a/govtool/frontend/src/components/molecules/Card.tsx b/govtool/frontend/src/components/molecules/Card.tsx index c1c5dceff..a6ef31177 100644 --- a/govtool/frontend/src/components/molecules/Card.tsx +++ b/govtool/frontend/src/components/molecules/Card.tsx @@ -39,7 +39,7 @@ export const Card = ({ variant = "default", border = variant !== "default", children, - elevation = 4, + elevation = 3, label, sx, }: CardProps) => { diff --git a/govtool/frontend/src/components/molecules/CopyableInfo.tsx b/govtool/frontend/src/components/molecules/CopyableInfo.tsx new file mode 100644 index 000000000..60597f572 --- /dev/null +++ b/govtool/frontend/src/components/molecules/CopyableInfo.tsx @@ -0,0 +1,47 @@ +import { Box, Typography } from "@mui/material"; + +import { CopyButton } from "@atoms"; +import { Card } from "./Card"; +import { gray } from "@/consts"; + +type CopyableInfoProps = { + dataTestId?: string; + label: string; + value: string; +}; + +export const CopyableInfo = ({ + dataTestId, + label, + value, +}: CopyableInfoProps) => ( + theme.palette.neutralWhite, + }} + > + + + {label} + + + + + + {value} + + + +); diff --git a/govtool/frontend/src/components/molecules/DRepInfoCard.tsx b/govtool/frontend/src/components/molecules/DRepInfoCard.tsx index f01d31897..00663ea63 100644 --- a/govtool/frontend/src/components/molecules/DRepInfoCard.tsx +++ b/govtool/frontend/src/components/molecules/DRepInfoCard.tsx @@ -3,15 +3,17 @@ import { Box, Typography } from "@mui/material"; import { useCardano } from "@context"; import { CopyButton } from "@atoms"; import { useTranslation } from "@hooks"; +import { Card } from "./Card"; +import { gray } from "@/consts"; export const DRepInfoCard = () => { const { dRepIDBech32 } = useCardano(); const { t } = useTranslation(); return ( - + - + {t("myDRepId")} @@ -28,6 +30,6 @@ export const DRepInfoCard = () => { {dRepIDBech32} - + ); }; diff --git a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx index 29d9cd940..55dde49ad 100644 --- a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx +++ b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx @@ -1,30 +1,18 @@ -import { Box, ButtonProps, Skeleton } from "@mui/material"; +import { Box, Skeleton } from "@mui/material"; import { FC, ReactNode } from "react"; -import { CopyButton, LoadingButton, Typography } from "@atoms"; +import { LoadingButton, LoadingButtonProps, Typography } from "@atoms"; import { useScreenDimension, useTranslation } from "@hooks"; -import { theme } from "@/theme"; +import { Card } from "./Card"; -type DashboardActionCardProps = { - cardId?: string; - cardTitle?: string; +export type DashboardActionCardProps = { + buttons?: LoadingButtonProps[]; + children?: ReactNode; dataTestidDelegationStatus?: string; - dataTestidDrepIdBox?: string; - dataTestidFirstButton?: string; - dataTestidSecondButton?: string; description?: ReactNode; - firstButtonAction?: () => void; - firstButtonDisabled?: boolean; - firstButtonIsLoading?: boolean; - firstButtonLabel?: string; - firstButtonVariant?: ButtonProps["variant"]; imageURL?: string; - inProgress?: boolean; isLoading?: boolean; - secondButtonAction?: () => void; - secondButtonIsLoading?: boolean; - secondButtonLabel?: string; - secondButtonVariant?: ButtonProps["variant"]; + state?: "active" | "inProgress" | "default"; title?: ReactNode; }; @@ -33,62 +21,32 @@ export const DashboardActionCard: FC = ({ }) => { const { t } = useTranslation(); const { - cardId, - cardTitle, - dataTestidDrepIdBox, - dataTestidFirstButton, - dataTestidSecondButton, + buttons, + children, description, - firstButtonAction, - firstButtonDisabled = false, - firstButtonIsLoading = false, - firstButtonLabel, - firstButtonVariant = "contained", imageURL, - inProgress, isLoading = false, - secondButtonAction, - secondButtonIsLoading = false, - secondButtonLabel, - secondButtonVariant = "outlined", + state = "default", title, } = props; - const { - palette: { boxShadow2 }, - } = theme; - const { isMobile, screenWidth } = useScreenDimension(); + const { screenWidth } = useScreenDimension(); return ( - - {inProgress && !isLoading && ( - - - {t("inProgress")} - - - )} {imageURL ? ( isLoading ? ( @@ -113,7 +71,7 @@ export const DashboardActionCard: FC = ({ {isLoading ? : title} ) : null} - {inProgress && !isLoading ? ( + {state === "inProgress" && !isLoading ? ( {t("inProgress")} @@ -122,7 +80,7 @@ export const DashboardActionCard: FC = ({ @@ -133,102 +91,46 @@ export const DashboardActionCard: FC = ({ )} ) : null} - {cardId && ( - - - - {cardTitle} - - - {cardId} - - - - - )} - {isLoading ? ( - - - - - ) : ( - - {firstButtonLabel ? ( - - {firstButtonLabel} - - ) : null} - {secondButtonLabel ? ( + {children} + + {isLoading ? ( + <> + + + + ) : ( + buttons?.map(({ dataTestId, ...buttonProps }) => ( - {secondButtonLabel} - - ) : null} - - )} - + {...buttonProps} + /> + )) + )} + + ); }; diff --git a/govtool/frontend/src/components/molecules/DataActionsBar.tsx b/govtool/frontend/src/components/molecules/DataActionsBar.tsx index 08e27a1db..f8f1587c5 100644 --- a/govtool/frontend/src/components/molecules/DataActionsBar.tsx +++ b/govtool/frontend/src/components/molecules/DataActionsBar.tsx @@ -2,7 +2,7 @@ import { Dispatch, FC, SetStateAction } from "react"; import { Box, InputBase } from "@mui/material"; import Search from "@mui/icons-material/Search"; -import { GovernanceActionsFilters, GovernanceActionsSorting } from "@molecules"; +import { DataActionsFilters, DataActionsSorting } from "@molecules"; import { OrderActionsChip } from "./OrderActionsChip"; import { theme } from "@/theme"; @@ -12,7 +12,12 @@ type DataActionsBarProps = { chosenSorting: string; closeFilters?: () => void; closeSorts: () => void; + filterOptions?: { + key: string; + label: string; + }[]; filtersOpen?: boolean; + filtersTitle?: string; isFiltering?: boolean; searchText: string; setChosenFilters?: Dispatch>; @@ -22,6 +27,10 @@ type DataActionsBarProps = { setSortOpen: Dispatch>; sortingActive: boolean; sortOpen: boolean; + sortOptions?: { + key: string; + label: string; + }[]; }; export const DataActionsBar: FC = ({ ...props }) => { @@ -31,7 +40,9 @@ export const DataActionsBar: FC = ({ ...props }) => { chosenSorting, closeFilters = () => {}, closeSorts, + filterOptions = [], filtersOpen, + filtersTitle, isFiltering = true, searchText, setChosenFilters = () => {}, @@ -41,6 +52,7 @@ export const DataActionsBar: FC = ({ ...props }) => { setSortOpen, sortingActive, sortOpen, + sortOptions = [], } = props; const { palette: { boxShadow2 }, @@ -87,17 +99,20 @@ export const DataActionsBar: FC = ({ ...props }) => { sortOpen={sortOpen} > {filtersOpen && ( - )} {sortOpen && ( - )} diff --git a/govtool/frontend/src/components/molecules/GovernanceActionsFilters.tsx b/govtool/frontend/src/components/molecules/DataActionsFilters.tsx similarity index 83% rename from govtool/frontend/src/components/molecules/GovernanceActionsFilters.tsx rename to govtool/frontend/src/components/molecules/DataActionsFilters.tsx index 29bf5cc99..98ec187e3 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionsFilters.tsx +++ b/govtool/frontend/src/components/molecules/DataActionsFilters.tsx @@ -7,19 +7,25 @@ import { Typography, } from "@mui/material"; -import { GOVERNANCE_ACTIONS_FILTERS } from "@consts"; -import { useOnClickOutside, useScreenDimension, useTranslation } from "@hooks"; +import { useOnClickOutside, useScreenDimension } from "@hooks"; interface Props { chosenFilters: string[]; setChosenFilters: Dispatch>; closeFilters: () => void; + options: { + key: string; + label: string; + }[]; + title?: string; } -export const GovernanceActionsFilters = ({ +export const DataActionsFilters = ({ chosenFilters, setChosenFilters, closeFilters, + options, + title, }: Props) => { const handleFilterChange = useCallback( (e: React.ChangeEvent) => { @@ -37,7 +43,6 @@ export const GovernanceActionsFilters = ({ [chosenFilters, setChosenFilters], ); - const { t } = useTranslation(); const { isMobile, screenWidth } = useScreenDimension(); const wrapperRef = useRef(null); @@ -60,17 +65,19 @@ export const GovernanceActionsFilters = ({ }} ref={wrapperRef} > - - {t("govActions.filterTitle")} - - {GOVERNANCE_ACTIONS_FILTERS.map((item) => ( + {title && ( + + {title} + + )} + {options.map((item) => ( >; closeSorts: () => void; + options: { + key: string; + label: string; + }[]; } -export const GovernanceActionsSorting = ({ +export const DataActionsSorting = ({ chosenSorting, setChosenSorting, closeSorts, + options, }: Props) => { const { t } = useTranslation(); @@ -63,7 +67,7 @@ export const GovernanceActionsSorting = ({ setChosenSorting(e.target.value); }} > - {GOVERNANCE_ACTIONS_SORTING.map((item) => ( + {options.map((item) => ( ( } as unknown as HTMLTextAreaElement), [handleBlur, handleFocus], ); + + const getCounterBottomSxValue = () => { + if (props.isModifiedLayout && errorMessage) return 30; + if (props.isModifiedLayout) return 10; + if (errorMessage) return 52.5; + return 35; + }; + return ( ( & { - txHash: string; - index: number; isDataMissing: boolean; onClick?: () => void; inProgress?: boolean; @@ -44,11 +42,15 @@ export const GovernanceActionCard: FC = ({ ...props }) => { type, inProgress = false, expiryDate, + expiryEpochNo, onClick, createdDate, + createdEpochNo, txHash, index, isDataMissing, + title, + about, } = props; const { isMobile, screenWidth } = useScreenDimension(); const { t } = useTranslation(); @@ -85,13 +87,14 @@ export const GovernanceActionCard: FC = ({ ...props }) => { }} > = ({ ...props }) => { - {data.map(({ label, content }) => ( + {Object.entries(data).map(([label, content]) => ( {label}: diff --git a/govtool/frontend/src/components/molecules/GovernanceActionsDatesBox.tsx b/govtool/frontend/src/components/molecules/GovernanceActionsDatesBox.tsx index 99065e13e..0e302b21d 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionsDatesBox.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionsDatesBox.tsx @@ -8,12 +8,16 @@ import { useScreenDimension, useTranslation } from "@hooks"; type GovernanceActionsDatesBoxProps = { createdDate: string; expiryDate: string; + expiryEpochNo: number; + createdEpochNo: number; isSliderCard?: boolean; }; export const GovernanceActionsDatesBox = ({ createdDate, expiryDate, + expiryEpochNo, + createdEpochNo, isSliderCard, }: GovernanceActionsDatesBoxProps) => { const { t } = useTranslation(); @@ -54,8 +58,7 @@ export const GovernanceActionsDatesBox = ({ > , , @@ -97,8 +100,7 @@ export const GovernanceActionsDatesBox = ({ > , , diff --git a/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx b/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx index 7e5a6aa2b..a93f191f6 100644 --- a/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx @@ -35,6 +35,17 @@ export const GovernanceVotedOnCard = ({ }: Props) => { const navigate = useNavigate(); const { proposal, vote } = votedProposal; + const { + createdDate, + createdEpochNo, + expiryDate, + expiryEpochNo, + type, + txHash, + index, + title, + about, + } = proposal; const { isMobile, screenWidth } = useScreenDimension(); const { t } = useTranslation(); @@ -60,9 +71,7 @@ export const GovernanceVotedOnCard = ({ ? "1px solid #F6D5D5" : "1px solid #C0E4BA", }} - data-testid={`govaction-${getProposalTypeNoEmptySpaces( - proposal.type, - )}-card`} + data-testid={`govaction-${getProposalTypeNoEmptySpaces(type)}-card`} > @@ -121,14 +130,14 @@ export const GovernanceVotedOnCard = ({ + ) : ( + )} { const { address, disconnectWallet } = useCardano(); @@ -18,17 +19,8 @@ export const WalletInfoCard = () => { return ( address && ( - - + + {t("wallet.connectedWallet")} @@ -52,7 +44,7 @@ export const WalletInfoCard = () => { {t("wallet.disconnect")} - + ) ); }; diff --git a/govtool/frontend/src/components/molecules/index.ts b/govtool/frontend/src/components/molecules/index.ts index 78d4f0563..ccd16ba85 100644 --- a/govtool/frontend/src/components/molecules/index.ts +++ b/govtool/frontend/src/components/molecules/index.ts @@ -3,8 +3,11 @@ export * from "./Breadcrumbs"; export * from "./Card"; export * from "./CenteredBoxBottomButtons"; export * from "./CenteredBoxPageWrapper"; +export * from "./CopyableInfo"; export * from "./DashboardActionCard"; export * from "./DataActionsBar"; +export * from "./DataActionsFilters"; +export * from "./DataActionsSorting"; export * from "./DataMissingInfoBox"; export * from "./DRepInfoCard"; export * from "./Field"; @@ -19,8 +22,6 @@ export * from "./GovernanceActionDetailsCardVotes"; export * from "./GovernanceActionDetailsCardHeader"; export * from "./GovernanceActionDetailsCardOnChainData"; export * from "./GovernanceActionsDatesBox"; -export * from "./GovernanceActionsFilters"; -export * from "./GovernanceActionsSorting"; export * from "./GovernanceVotedOnCard"; export * from "./LinkWithIcon"; export * from "./OrderActionsChip"; diff --git a/govtool/frontend/src/components/organisms/DashboardCards.tsx b/govtool/frontend/src/components/organisms/DashboardCards.tsx index 98a99630d..ec7747cf3 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards.tsx @@ -1,268 +1,48 @@ -import { useCallback, useMemo } from "react"; -import { useNavigate } from "react-router-dom"; import { Box, CircularProgress } from "@mui/material"; -import { Trans } from "react-i18next"; -import { IMAGES, PATHS } from "@consts"; import { useCardano } from "@context"; import { useGetAdaHolderVotingPowerQuery, useScreenDimension, useGetAdaHolderCurrentDelegationQuery, - useTranslation, useGetVoterInfo, } from "@hooks"; -import { DashboardActionCard } from "@molecules"; -import { correctAdaFormat, formHexToBech32, openInNewTab } from "@utils"; +import { DelegateDashboardCard } from "./DashboardCards/DelegateDashboardCard"; +import { DRepDashboardCard } from "./DashboardCards/DRepDashboardCard"; +import { SoleVoterDashboardCard } from "./DashboardCards/SoleVoterDashboardCard"; +import { ListGovActionsDashboardCards } from "./DashboardCards/ListGovActionsDashboardCard"; +import { ProposeGovActionDashboardCard } from "./DashboardCards/ProposeGovActionDashboardCard"; export const DashboardCards = () => { const { dRepID, dRepIDBech32, - isPendingTransaction, pendingTransaction, stakeKey, } = useCardano(); - const navigate = useNavigate(); - const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const { screenWidth } = useScreenDimension(); + + const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const { votingPower } = useGetAdaHolderVotingPowerQuery(stakeKey); - const { t } = useTranslation(); const { voter } = useGetVoterInfo(); - const delegationDescription = useMemo(() => { - const correctAdaRepresentation = correctAdaFormat(votingPower); - if (currentDelegation === dRepID) { - return ( - - ); - } - if (currentDelegation === "drep_always_no_confidence") { - return ( - - ); - } - if (currentDelegation === "drep_always_abstain") { - return ( - - ); - } - if (currentDelegation) { - return ( - - ); - } + if (!currentDelegation || !voter || !votingPower) { return ( - + + + ); - }, [currentDelegation, dRepID, votingPower]); - - const delegationStatusTestForId = useMemo(() => { - if (currentDelegation === dRepID) { - return "myself"; - } - if (currentDelegation === "drep_always_no_confidence") { - return "no-confidence"; - } - if (currentDelegation === "drep_always_abstain") { - return "abstain"; - } - if (currentDelegation) { - return "dRep"; - } - return "not_delegated"; - }, [currentDelegation, dRepID, votingPower]); - - const progressDescription = useMemo(() => { - const correctAdaRepresentation = correctAdaFormat(votingPower); - if (!pendingTransaction.delegate) return; - const { resourceId } = pendingTransaction.delegate; - - if (resourceId === dRepID) { - return ( - - ); - } - if (resourceId === "no confidence") { - return ( - - ); - } - if (resourceId === "abstain") { - return ( - - ); - } - if (resourceId) { - return ( - - ); - } - }, [pendingTransaction, dRepID, votingPower]); - - const navigateTo = useCallback( - (path: string) => { - const isPendingTx = isPendingTransaction(); - if (isPendingTx) return; - navigate(path); - }, - [isPendingTransaction, navigate], - ); - - const onClickGovernanceActionCardActionButton = useCallback(() => { - if (pendingTransaction.createGovAction) { - navigate(PATHS.dashboardGovernanceActions); - return; - } - navigate(PATHS.createGovernanceAction); - }, [pendingTransaction.createGovAction, navigate]); - - const displayedDelegationId = useMemo(() => { - const restrictedNames = [ - dRepID, - "drep_always_abstain", - "drep_always_no_confidence", - "abstain", - "no confidence", - ]; - if (pendingTransaction.delegate) { - const delegateTo = pendingTransaction.delegate.resourceId; - if (!restrictedNames.includes(delegateTo)) { - return delegateTo.includes("drep") - ? delegateTo - : formHexToBech32(delegateTo); - } - return undefined; - } - if (!restrictedNames.includes(currentDelegation)) { - return formHexToBech32(currentDelegation); - } - return undefined; - }, [currentDelegation, dRepID, pendingTransaction, formHexToBech32]); - - const registrationCardDescription = useMemo(() => { - if (pendingTransaction.registerAsDrep) - return t("dashboard.registration.registrationInProgress"); - - if (pendingTransaction.retireAsDrep) - return t("dashboard.registration.retirementInProgress"); - - if (pendingTransaction.updateMetaData) - return t("dashboard.registration.metadataUpdateInProgress"); - - if (voter?.isRegisteredAsDRep || voter?.wasRegisteredAsDRep) - return t("dashboard.registration.holdersCanDelegate"); - - return t("dashboard.registration.ifYouWant"); - }, [ - pendingTransaction, - voter?.isRegisteredAsDRep, - voter?.wasRegisteredAsDRep, - ]); - - const soleVoterCardDescription = useMemo(() => { - if (pendingTransaction.registerAsSoleVoter) - return "dashboard.soleVoter.registrationInProgress"; - - if (pendingTransaction.retireAsSoleVoter) - return "dashboard.soleVoter.retirementInProgress"; - - if (voter?.isRegisteredAsSoleVoter) - return "dashboard.soleVoter.isRegisteredDescription"; - - if (voter?.wasRegisteredAsSoleVoter) - return "dashboard.soleVoter.wasRegisteredDescription"; - - return "dashboard.soleVoter.registerDescription"; - }, [ - pendingTransaction, - voter?.isRegisteredAsSoleVoter, - voter?.wasRegisteredAsSoleVoter, - ]); + } - const registrationCardTitle = useMemo(() => { - if (pendingTransaction.retireAsDrep) - return t("dashboard.registration.dRepRetirement"); - - if (pendingTransaction.registerAsDrep) - return t("dashboard.registration.dRepRegistration"); - - if (pendingTransaction.updateMetaData) - return t("dashboard.registration.dRepUpdate"); - - if (voter?.isRegisteredAsDRep) - return t("dashboard.registration.youAreRegistered"); - - if (voter?.wasRegisteredAsDRep) - return t("dashboard.registration.registerAgain"); - - return t("dashboard.registration.registerAsDRep"); - }, [ - pendingTransaction, - voter?.isRegisteredAsDRep, - voter?.wasRegisteredAsDRep, - ]); - - const soleVoterCardTitle = useMemo(() => { - if (pendingTransaction.retireAsSoleVoter) - return t("dashboard.soleVoter.retirement"); - - if (pendingTransaction.registerAsSoleVoter) - return t("dashboard.soleVoter.registration"); - - if (voter?.isRegisteredAsSoleVoter) - return t("dashboard.soleVoter.youAreSoleVoterTitle"); - - if (voter?.wasRegisteredAsSoleVoter) - return t("dashboard.soleVoter.wasSoleVoterTitle"); - - return t("dashboard.soleVoter.registerTitle"); - }, [ - pendingTransaction, - voter?.isRegisteredAsSoleVoter, - voter?.isRegisteredAsSoleVoter, - ]); - - return !voter || !votingPower ? ( - - - - ) : ( + return ( { rowGap: 3, }} > - {/* DELEGATION CARD */} - navigateTo(PATHS.delegateTodRep)} - firstButtonLabel={ - pendingTransaction.delegate - ? "" - : currentDelegation - ? t("dashboard.delegation.changeDelegation") - : t("delegate") - } - firstButtonVariant={currentDelegation ? "outlined" : "contained"} - imageURL={IMAGES.govActionDelegateImage} - cardId={displayedDelegationId} - inProgress={!!pendingTransaction.delegate} - cardTitle={t("dashboard.delegation.dRepDelegatedTo")} - secondButtonAction={ - pendingTransaction.delegate - ? () => openInNewTab("https://adanordic.com/latest_transactions") - : () => - openInNewTab( - "https://docs.sanchogov.tools/faqs/ways-to-use-your-voting-power", - ) - } - secondButtonLabel={ - pendingTransaction.delegate - ? t("seeTransaction") - : currentDelegation - ? "" - : t("learnMore") - } - title={ - pendingTransaction.delegate ? ( - t("dashboard.delegation.votingPowerDelegation") - ) : currentDelegation ? ( - - ) : ( - t("dashboard.delegation.useYourVotingPower") - ) - } + - {/* DELEGATION CARD END */} - {/* REGISTARTION AS DREP CARD */} - - navigateTo( - voter?.isRegisteredAsDRep - ? PATHS.retireAsDrep - : PATHS.registerAsdRep, - ) - } - firstButtonLabel={ - pendingTransaction.registerAsDrep || pendingTransaction.retireAsDrep - ? "" - : t( - `dashboard.registration.${ - voter?.isRegisteredAsDRep ? "retire" : "register" - }`, - ) - } - inProgress={ - !!( - pendingTransaction.registerAsDrep || - pendingTransaction.retireAsDrep || - pendingTransaction.updateMetaData - ) - } - imageURL={IMAGES.govActionRegisterImage} - secondButtonAction={ - pendingTransaction.registerAsDrep || pendingTransaction.retireAsDrep - ? () => openInNewTab("https://adanordic.com/latest_transactions") - : voter?.isRegisteredAsDRep - ? () => { - navigateTo(PATHS.updateMetadata); - } - : () => - openInNewTab( - "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep", - ) - } - secondButtonLabel={ - pendingTransaction.registerAsDrep || pendingTransaction.retireAsDrep - ? t("seeTransaction") - : voter?.isRegisteredAsDRep - ? t("dashboard.registration.changeMetadata") - : t("learnMore") - } - cardId={ - voter?.isRegisteredAsDRep || voter?.wasRegisteredAsDRep - ? dRepIDBech32 - : "" - } - cardTitle={ - voter?.isRegisteredAsDRep || voter?.wasRegisteredAsDRep - ? t("myDRepId") - : "" - } - title={registrationCardTitle} - /> - {/* DREP CARD END */} - {/* SOLE VOTER CARD */} - - } - firstButtonLabel={ - pendingTransaction.registerAsSoleVoter - ? "" - : t( - voter?.isRegisteredAsSoleVoter - ? "dashboard.soleVoter.retire" - : voter?.wasRegisteredAsSoleVoter - ? "dashboard.soleVoter.reRegister" - : "dashboard.soleVoter.register", - ) - } - firstButtonAction={() => - navigateTo( - voter?.isRegisteredAsSoleVoter - ? PATHS.retireAsSoleVoter - : PATHS.registerAsSoleVoter, - ) - } - firstButtonVariant={ - voter?.isRegisteredAsSoleVoter ? "outlined" : "contained" - } - secondButtonLabel={t("learnMore")} - secondButtonAction={() => - openInNewTab( - "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep", - ) - } - secondButtonVariant="outlined" - imageURL={IMAGES.soleVoterImage} + + - {/* REGISTARTION AS SOLE VOTER CARD END */} - {/* GOV ACTIONS LIST CARD */} - navigate(PATHS.dashboardGovernanceActions)} - firstButtonLabel={t( - `dashboard.govActions.${ - voter?.isRegisteredAsDRep ? "reviewAndVote" : "view" - }`, - )} - imageURL={IMAGES.govActionListImage} - title={t("dashboard.govActions.title")} + + - {/* GOV ACTIONS LIST CARD END */} - {/* GOV ACTIONS LIST CARD */} - - openInNewTab( - "https://docs.sanchogov.tools/faqs/what-is-a-governance-action", - ) - } - secondButtonVariant="outlined" - imageURL={IMAGES.proposeGovActionImage} - title={t("dashboard.proposeGovernanceAction.title")} + + + + - {/* GOV ACTIONS LIST CARD END */} ); }; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/DRepDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/DRepDashboardCard.tsx new file mode 100644 index 000000000..4c9298d3e --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/DRepDashboardCard.tsx @@ -0,0 +1,133 @@ +import { useNavigate } from "react-router-dom"; + +import { IMAGES, PATHS } from "@consts"; +import { useTranslation } from "@hooks"; +import { + CopyableInfo, + DashboardActionCard, + DashboardActionCardProps, +} from "@molecules"; +import { openInNewTab } from "@utils"; +import { PendingTransaction } from "@/context/pendingTransaction"; +import { VoterInfo } from "@/models"; + +type DRepDashboardCardProps = { + dRepIDBech32: string; + pendingTransaction: PendingTransaction; + voter: VoterInfo; +}; + +export const DRepDashboardCard = ({ + dRepIDBech32, + pendingTransaction, + voter, +}: DRepDashboardCardProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + const inProgress = !!( + pendingTransaction.registerAsDrep || + pendingTransaction.retireAsDrep || + pendingTransaction.updateMetaData + ); + + const cardProps: Partial = (() => { + // transaction in progress + if (inProgress) { + return { + buttons: [ + { + children: t("seeTransaction"), + onClick: () => + openInNewTab("https://adanordic.com/latest_transactions"), + }, + ], + state: "inProgress", + ...(pendingTransaction.registerAsDrep && { + description: t("dashboard.registration.registrationInProgress"), + title: t("dashboard.registration.dRepRegistration"), + }), + ...(pendingTransaction.retireAsDrep && { + description: t("dashboard.registration.retirementInProgress"), + title: t("dashboard.registration.dRepRetirement"), + }), + ...(pendingTransaction.updateMetaData && { + description: t("dashboard.registration.metadataUpdateInProgress"), + title: t("dashboard.registration.dRepUpdate"), + }), + }; + } + + // currently registered + if (voter?.isRegisteredAsDRep) { + return { + buttons: [ + { + children: t("dashboard.registration.retire"), + dataTestId: "retire-button", + onClick: () => navigate(PATHS.retireAsDrep), + }, + { + children: t("dashboard.registration.changeMetadata"), + dataTestId: "change-metadata-button", + onClick: () => navigate(PATHS.updateMetadata), + variant: "text", + }, + ], + description: t("dashboard.registration.holdersCanDelegate"), + state: "active", + title: t("dashboard.registration.youAreRegistered"), + }; + } + + // common buttons for was registered or not registered + const wasRegisteredOrNotRegisteredButtons: DashboardActionCardProps["buttons"] = + [ + { + children: t("dashboard.registration.register"), + dataTestId: "register-button", + onClick: () => navigate(PATHS.registerAsdRep), + variant: "contained", + }, + { + children: t("learnMore"), + dataTestId: "register-learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep" + ), + }, + ]; + + // was registered + if (voter?.wasRegisteredAsDRep) { + return { + buttons: wasRegisteredOrNotRegisteredButtons, + description: t("dashboard.registration.holdersCanDelegate"), + title: t("dashboard.registration.registerAgain"), + }; + } + + // not registered + return { + buttons: wasRegisteredOrNotRegisteredButtons, + description: t("dashboard.registration.ifYouWant"), + title: t("dashboard.registration.registerAsDRep"), + }; + })(); + + return ( + + {(voter?.isRegisteredAsDRep || voter?.wasRegisteredAsDRep) && ( + + )} + + ); +}; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx new file mode 100644 index 000000000..6f68f06a9 --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx @@ -0,0 +1,185 @@ +import { useNavigate } from "react-router-dom"; +import { Trans } from "react-i18next"; + +import { IMAGES, PATHS } from "@consts"; +import { useTranslation } from "@hooks"; +import { + CopyableInfo, + DashboardActionCard, + DashboardActionCardProps, +} from "@molecules"; +import { correctAdaFormat, formHexToBech32, openInNewTab } from "@utils"; +import { PendingTransaction } from "@/context/pendingTransaction"; + +type DelegateDashboardCardProps = { + currentDelegation: string; + delegateTx: PendingTransaction["delegate"]; + dRepID: string; + votingPower: number; +}; + +export const DelegateDashboardCard = ({ + currentDelegation, + delegateTx, + dRepID, + votingPower, +}: DelegateDashboardCardProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + const ada = correctAdaFormat(votingPower); + + const cardProps: Partial = (() => { + // transaction in progress + if (delegateTx) { + return { + buttons: [ + { + children: t("seeTransaction"), + dataTestId: "see-transaction-button", + onClick: () => + openInNewTab("https://adanordic.com/latest_transactions"), + }, + ], + description: getProgressDescription( + delegateTx?.resourceId, + dRepID, + ada + ), + state: "inProgress", + title: t("dashboard.delegation.votingPowerDelegation"), + }; + } + + // current delegation + if (currentDelegation) { + return { + buttons: [ + { + children: t("dashboard.delegation.changeDelegation"), + dataTestId: "change-dRep-button", + onClick: () => navigate(PATHS.delegateTodRep), + }, + ], + description: getDelegationDescription(currentDelegation, dRepID, ada), + state: "active", + title: ( + + ), + }; + } + + // no current delegation + return { + buttons: [ + { + children: t("delegate"), + dataTestId: "delegate-button", + onClick: () => navigate(PATHS.delegateTodRep), + variant: "contained", + }, + { + children: t("learnMore"), + dataTestId: "delegate-learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/ways-to-use-your-voting-power" + ), + }, + ], + description: ( + + ), + title: t("dashboard.delegation.useYourVotingPower"), + }; + })(); + + const displayedDelegationId = getDisplayedDelegationId( + currentDelegation, + delegateTx?.resourceId, + dRepID + ); + + return ( + + {displayedDelegationId && ( + + )} + + ); +}; + +const getDelegationDescription = ( + currentDelegation: string, + dRepID: string, + ada: number +) => { + const key = + currentDelegation === dRepID + ? "dashboard.delegation.toYourself" + : currentDelegation === "drep_always_no_confidence" + ? "dashboard.delegation.voteNo" + : currentDelegation === "drep_always_abstain" + ? "dashboard.delegation.voteAbstain" + : currentDelegation + ? "dashboard.delegation.toDRep" + : undefined; + return ; +}; + +const getProgressDescription = ( + delegateTo: string, + dRepID: string, + ada: number +) => { + const key = (() => { + if (!delegateTo) return undefined; + switch (delegateTo) { + case dRepID: + return "dashboard.delegation.inProgress.toYourself"; + case "no confidence": + return "dashboard.delegation.inProgress.voteNo"; + case "abstain": + return "dashboard.delegation.inProgress.voteAbstain"; + default: + return "dashboard.delegation.inProgress.toDRep"; + } + })(); + return ; +}; + +const getDisplayedDelegationId = ( + currentDelegation: string, + delegateTo: string | undefined, + dRepID: string +) => { + const restrictedNames = [ + dRepID, + "drep_always_abstain", + "drep_always_no_confidence", + "abstain", + "no confidence", + ]; + if (delegateTo) { + if (!restrictedNames.includes(delegateTo)) { + return delegateTo.includes("drep") + ? delegateTo + : formHexToBech32(delegateTo); + } + return undefined; + } + if (!restrictedNames.includes(currentDelegation)) { + return formHexToBech32(currentDelegation); + } + return undefined; +}; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/ListGovActionsDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/ListGovActionsDashboardCard.tsx new file mode 100644 index 000000000..dfc42cc36 --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/ListGovActionsDashboardCard.tsx @@ -0,0 +1,34 @@ +import { useNavigate } from "react-router-dom"; + +import { IMAGES, PATHS } from "@consts"; +import { useTranslation } from "@hooks"; +import { DashboardActionCard } from "@molecules"; +import { VoterInfo } from "@/models"; + +type ListGovActionsDashboardCardsProps = { + voter: VoterInfo; +}; + +export const ListGovActionsDashboardCards = ({ voter }: ListGovActionsDashboardCardsProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + return ( + navigate(PATHS.dashboardGovernanceActions), + }, + ]} + description={t("dashboard.govActions.description")} + imageURL={IMAGES.govActionListImage} + title={t("dashboard.govActions.title")} + /> + ); +}; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/ProposeGovActionDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/ProposeGovActionDashboardCard.tsx new file mode 100644 index 000000000..6bace3a8d --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/ProposeGovActionDashboardCard.tsx @@ -0,0 +1,57 @@ +import { useNavigate } from "react-router-dom"; + +import { IMAGES, PATHS } from "@consts"; +import { useTranslation } from "@hooks"; +import { DashboardActionCard } from "@molecules"; +import { openInNewTab } from "@utils"; +import { PendingTransaction } from "@/context/pendingTransaction"; + +type ProposeGovActionDashboardCardProps = { + createGovActionTx: PendingTransaction["createGovAction"]; +}; + +export const ProposeGovActionDashboardCard = ({ + createGovActionTx, +}: ProposeGovActionDashboardCardProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + return ( + navigate(PATHS.dashboardGovernanceActions), + variant: "contained", + } as const, + ] + // default + : [ + { + children: t("dashboard.proposeGovernanceAction.propose"), + dataTestId: "propose-governance-actions-button", + onClick: () => navigate(PATHS.createGovernanceAction), + variant: "contained", + } as const, + ]), + // common + { + children: t("learnMore"), + dataTestId: "learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/what-is-a-governance-action" + ), + }, + ]} + description={t("dashboard.proposeGovernanceAction.description")} + imageURL={IMAGES.proposeGovActionImage} + state={createGovActionTx ? "inProgress" : "default"} + title={t("dashboard.proposeGovernanceAction.title")} + /> + ); +}; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx new file mode 100644 index 000000000..95eb1449b --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx @@ -0,0 +1,117 @@ +import { useNavigate } from "react-router-dom"; +import { Trans } from "react-i18next"; + +import { IMAGES, PATHS } from "@consts"; +import { useTranslation } from "@hooks"; +import { DashboardActionCard, DashboardActionCardProps } from "@molecules"; +import { correctAdaFormat, openInNewTab } from "@utils"; +import { LoadingButtonProps } from "@atoms"; +import { PendingTransaction } from "@/context/pendingTransaction"; +import { VoterInfo } from "@/models"; + +type SoleVoterDashboardCardProps = { + pendingTransaction: PendingTransaction; + voter: VoterInfo; + votingPower: number; +}; + +export const SoleVoterDashboardCard = ({ + pendingTransaction, + voter, + votingPower, +}: SoleVoterDashboardCardProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + const ada = correctAdaFormat(votingPower); + + const cardProps: Partial = (() => { + // transaction in progress + if ( + !!pendingTransaction.registerAsSoleVoter || + !!pendingTransaction.retireAsSoleVoter + ) { + return { + buttons: [ + { + children: t("seeTransaction"), + dataTestId: "see-transaction-button", + onClick: () => + openInNewTab("https://adanordic.com/latest_transactions"), + }, + ], + state: "inProgress", + ...(pendingTransaction.registerAsSoleVoter && { + description: t("dashboard.soleVoter.registrationInProgress"), + title: t("dashboard.soleVoter.registration"), + }), + ...(pendingTransaction.retireAsSoleVoter && { + description: t("dashboard.soleVoter.retirementInProgress"), + title: t("dashboard.soleVoter.retirement"), + }), + }; + } + + // learn more button + const learnMoreButton: LoadingButtonProps = { + children: t("learnMore"), + dataTestId: "learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep" + ), + }; + + // currently registered + if (voter?.isRegisteredAsSoleVoter) { + return { + buttons: [ + { + children: t("dashboard.soleVoter.retire"), + dataTestId: "retire-as-sole-voter-button", + onClick: () => navigate(PATHS.retireAsSoleVoter), + }, + learnMoreButton, + ], + description: , + state: "active", + title: t("dashboard.soleVoter.youAreSoleVoterTitle"), + }; + } + + // was registered + if (voter?.wasRegisteredAsSoleVoter) { + return { + buttons: [ + { + children: t("dashboard.soleVoter.reRegister"), + dataTestId: "register-as-sole-voter-button", + onClick: () => navigate(PATHS.registerAsSoleVoter), + }, + learnMoreButton, + ], + description: , + title: t("dashboard.soleVoter.wasSoleVoterTitle"), + }; + } + + // not registered + return { + buttons: [ + { + children: t("dashboard.soleVoter.register"), + dataTestId: "register-as-sole-voter-button", + onClick: () => navigate(PATHS.registerAsSoleVoter), + variant: "contained", + }, + learnMoreButton, + ], + description: , + title: t("dashboard.soleVoter.registerTitle"), + }; + })(); + + return ( + + ); +}; diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx index 480de270c..8efdeb77b 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx @@ -41,6 +41,7 @@ export const DashboardGovernanceActionDetails = () => { state ? state.txHash : data?.proposal.txHash ?? "", state ? state.index : data?.proposal.index ?? "", ); + const title = state ? state.title : data?.proposal.title; return ( { { ? formatDisplayDate(state.createdDate) : formatDisplayDate(data.proposal.createdDate) } + createdEpochNo={ + state ? state.createdEpochNo : data.proposal.createdEpochNo + } // TODO: Add data validation isDataMissing={isDataMissing} expiryDate={ @@ -115,6 +120,9 @@ export const DashboardGovernanceActionDetails = () => { ? formatDisplayDate(state.expiryDate) : formatDisplayDate(data.proposal.expiryDate) } + expiryEpochNo={ + state ? state.expiryEpochNo : data.proposal.expiryEpochNo + } isVoter={ voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter } @@ -124,8 +132,12 @@ export const DashboardGovernanceActionDetails = () => { ? getProposalTypeLabel(state.type) : getProposalTypeLabel(data.proposal.type) } - // TODO: To decide if we want to keep it when metadate BE is ready - // url={state ? state.url : data.proposal.url} + details={state ? state.details : data.proposal.details} + url={state ? state.url : data.proposal.url} + title={state ? state.title : data.proposal.title} + about={state ? state.about : data.proposal.about} + motivation={state ? state.motivation : data.proposal.motivation} + rationale={state ? state.rationale : data.proposal.rationale} yesVotes={state ? state.yesVotes : data.proposal.yesVotes} voteFromEP={data?.vote?.vote} govActionId={fullProposalId} diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx index f9b22bc1e..ab224032e 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx @@ -1,10 +1,11 @@ -import { useState, useCallback, useEffect } from "react"; +import { useState, useEffect } from "react"; import { Box, CircularProgress, Tab, Tabs, styled } from "@mui/material"; import { useLocation } from "react-router-dom"; -import { GOVERNANCE_ACTIONS_FILTERS } from "@consts"; +import { GOVERNANCE_ACTIONS_FILTERS, GOVERNANCE_ACTIONS_SORTING } from "@consts"; import { useCardano } from "@context"; import { + useDataActionsBar, useGetProposalsQuery, useGetVoterInfo, useScreenDimension, @@ -64,11 +65,8 @@ const StyledTab = styled((props: StyledTabProps) => ( })); export const DashboardGovernanceActions = () => { - const [searchText, setSearchText] = useState(""); - const [filtersOpen, setFiltersOpen] = useState(false); - const [chosenFilters, setChosenFilters] = useState([]); - const [sortOpen, setSortOpen] = useState(false); - const [chosenSorting, setChosenSorting] = useState(""); + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); + const { chosenFilters, chosenSorting } = dataActionsBarProps; const { voter } = useGetVoterInfo(); const { isMobile } = useScreenDimension(); const { t } = useTranslation(); @@ -80,7 +78,7 @@ export const DashboardGovernanceActions = () => { const { proposals, isProposalsLoading } = useGetProposalsQuery({ filters: queryFilters, sorting: chosenSorting, - searchPhrase: searchText, + searchPhrase: debouncedSearchText, }); const { state } = useLocation(); @@ -92,14 +90,6 @@ export const DashboardGovernanceActions = () => { setContent(newValue); }; - const closeFilters = useCallback(() => { - setFiltersOpen(false); - }, [setFiltersOpen]); - - const closeSorts = useCallback(() => { - setSortOpen(false); - }, [setSortOpen]); - useEffect(() => { window.history.replaceState({}, document.title); }, []); @@ -114,20 +104,10 @@ export const DashboardGovernanceActions = () => { > <> {!proposals || !voter || isEnableLoading || isProposalsLoading ? ( { @@ -185,7 +165,7 @@ export const DashboardGovernanceActions = () => { diff --git a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCard.tsx b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCard.tsx index f6fabab7b..581576861 100644 --- a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCard.tsx +++ b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCard.tsx @@ -10,10 +10,17 @@ import { useState } from "react"; type GovernanceActionDetailsCardProps = { abstainVotes: number; createdDate: string; + createdEpochNo: number; expiryDate: string; + expiryEpochNo: number; noVotes: number; type: string; - // url: string; + details?: ActionDetailsType; + url: string; + title: string | null; + about: string | null; + motivation: string | null; + rationale: string | null; yesVotes: number; govActionId: string; isDataMissing: boolean; @@ -26,10 +33,17 @@ type GovernanceActionDetailsCardProps = { export const GovernanceActionDetailsCard = ({ abstainVotes, createdDate, + createdEpochNo, expiryDate, + expiryEpochNo, noVotes, type, - // url, + details, + url, + title, + about, + motivation, + rationale, yesVotes, isDashboard, isVoter, @@ -72,7 +86,15 @@ export const GovernanceActionDetailsCard = ({ type={type} govActionId={govActionId} createdDate={createdDate} + createdEpochNo={createdEpochNo} expiryDate={expiryDate} + expiryEpochNo={expiryEpochNo} + details={details} + url={url} + title={title} + about={about} + motivation={motivation} + rationale={rationale} isDataMissing={isDataMissing} isDashboard={isDashboard} isOneColumn={isOneColumn} diff --git a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx index 9a6641d41..e6234c326 100644 --- a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx +++ b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx @@ -15,19 +15,19 @@ import { getProposalTypeNoEmptySpaces } from "@utils"; const mockedLongDescription = "I am the Cardano crusader carving his path in the blockchain battleground. With a mind sharper than a Ledger Nano X, this fearless crypto connoisseur fearlessly navigates the volatile seas of Cardano, turning code into currency. Armed with a keyboard and a heart pumping with blockchain beats, Mister Big Bad fearlessly champions decentralization, smart contracts, and the Cardano community. His Twitter feed is a mix of market analysis that rivals CNBC and memes that could break the internet."; -const mockedOnChainData = [ - { - label: "Reward Address", - content: "Lorem ipsum dolor sit amet consectetur.", - }, - { label: "Amount", content: "₳ 12,350" }, -]; - type GovernanceActionDetailsCardDataProps = { type: string; govActionId: string; createdDate: string; + createdEpochNo: number; expiryDate: string; + expiryEpochNo: number; + details?: ActionDetailsType; + url: string; + title: string | null; + about: string | null; + motivation: string | null; + rationale: string | null; isDataMissing: boolean; isOneColumn: boolean; isDashboard?: boolean; @@ -37,7 +37,15 @@ export const GovernanceActionDetailsCardData = ({ type, govActionId, createdDate, + createdEpochNo, expiryDate, + expiryEpochNo, + details, + url, + title, + about, + motivation, + rationale, isDataMissing, isOneColumn, isDashboard, @@ -58,8 +66,8 @@ export const GovernanceActionDetailsCardData = ({ }} > {isDataMissing && ( )} - - - {/* TODO: To add option display of on-chain data when BE is ready */} - + {details && Object.keys(details).length !== 0 && ( + + )} ); diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextCheckResult.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextCheckResult.tsx new file mode 100644 index 000000000..2e5ab3def --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextCheckResult.tsx @@ -0,0 +1,106 @@ +import { Dispatch, SetStateAction } from "react"; +import { Box } from "@mui/material"; + +import { IMAGES } from "@consts"; +import { Button, Typography } from "@atoms"; +import { useScreenDimension, useTranslation, useVoteContextForm } from "@hooks"; + +type VoteContextCheckResultProps = { + submitVoteContext: () => void; + closeModal: () => void; + setStep: Dispatch>; + errorMessage?: string; +}; + +export const VoteContextCheckResult = ({ + submitVoteContext, + closeModal, + setStep, + errorMessage, +}: VoteContextCheckResultProps) => { + const { t } = useTranslation(); + const { isMobile } = useScreenDimension(); + + const { watch } = useVoteContextForm(); + const isContinueDisabled = !watch("voteContextText"); + + return ( + + Status icon + + {errorMessage ? "Data validation failed" : "Success"} + + + {errorMessage ?? "Data check has been successful"} + + {!errorMessage ? ( + + ) : ( + + + + + )} + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextModal.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextModal.tsx new file mode 100644 index 000000000..9694d7d3d --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextModal.tsx @@ -0,0 +1,75 @@ +import { useState } from "react"; +import { useForm, FormProvider } from "react-hook-form"; + +import { ModalWrapper } from "@atoms"; +import { useModal } from "@context"; +import { + VoteContextStoringInformation, + VoteContextCheckResult, + VoteContextTerms, + VoteContextText, +} from "@organisms"; +import { VoteContextFormValues } from "@hooks"; + +type VoteContextModalState = { + onSubmit: (url: string, hash: string | null, voteContextText: string) => void; +}; + +export const VoteContextModal = () => { + const [step, setStep] = useState(1); + const [savedHash, setSavedHash] = useState(""); + const [errorMessage, setErrorMessage] = useState( + undefined, + ); + + const { state, closeModal } = useModal(); + + const methods = useForm({ mode: "onChange" }); + const { getValues } = methods; + + const submitVoteContext = () => { + if (state && savedHash) { + state.onSubmit( + getValues("storingURL"), + savedHash, + getValues("voteContextText"), + ); + } + closeModal(); + }; + + return ( + + + {step === 1 && ( + + )} + {step === 2 && ( + + )} + {step === 3 && ( + + )} + {step === 4 && ( + + )} + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx new file mode 100644 index 000000000..96d91b3a8 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx @@ -0,0 +1,154 @@ +import { Dispatch, SetStateAction, useEffect } from "react"; +import { Box } from "@mui/material"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; + +import { Button, Spacer, Typography } from "@atoms"; +import { ICONS } from "@consts"; +import { useTranslation, useScreenDimension, useVoteContextForm } from "@hooks"; +import { Step } from "@molecules"; +import { ControlledField, VoteContextWrapper } from "@organisms"; +import { URL_REGEX, openInNewTab } from "@utils"; + +type VoteContextStoringInformationProps = { + setStep: Dispatch>; + setSavedHash: Dispatch>; + setErrorMessage: Dispatch>; + onCancel: () => void; +}; + +export const VoteContextStoringInformation = ({ + setStep, + setSavedHash, + setErrorMessage, + onCancel, +}: VoteContextStoringInformationProps) => { + const { t } = useTranslation(); + const { screenWidth } = useScreenDimension(); + + const { + control, + errors, + validateURL, + watch, + generateMetadata, + onClickDownloadJson, + } = useVoteContextForm(setSavedHash, setStep, setErrorMessage); + + // TODO: Change link to correct + const openGuideAboutStoringInformation = () => + openInNewTab("https://sancho.network/"); + + const isContinueDisabled = !watch("storingURL"); + + useEffect(() => { + generateMetadata(); + }, []); + + return ( + + + {t("createGovernanceAction.storingInformationTitle")} + + + + {t("createGovernanceAction.storingInformationDescription")} + + + } + sx={{ + width: "fit-content", + ml: screenWidth < 1024 ? 0 : 1.75, + mt: screenWidth < 1024 ? 1.5 : 0, + }} + variant="outlined" + > + {t("govActions.voteContextJsonldFileName")} + + } + componentsLayoutStyles={{ + alignItems: screenWidth < 1024 ? undefined : "center", + flexDirection: screenWidth < 1024 ? "column" : "row", + }} + label={t("createGovernanceAction.storingInformationStep1Label")} + stepNumber={1} + /> + + + } + onClick={openGuideAboutStoringInformation} + size="extraLarge" + sx={{ width: "fit-content" }} + variant="text" + > + {t("createGovernanceAction.storingInformationStep2Link")} + + } + label={t("createGovernanceAction.storingInformationStep2Label")} + stepNumber={2} + /> + + + } + label={t("createGovernanceAction.storingInformationStep3Label")} + stepNumber={3} + /> + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextTerms.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextTerms.tsx new file mode 100644 index 000000000..22ac471c5 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextTerms.tsx @@ -0,0 +1,60 @@ +import { Dispatch, SetStateAction } from "react"; +import { Box, Link } from "@mui/material"; + +import { Spacer, Typography } from "@atoms"; +import { useScreenDimension, useTranslation, useVoteContextForm } from "@hooks"; +import { ControlledField, VoteContextWrapper } from "@organisms"; +import { openInNewTab } from "@utils"; + +type StoreDataInfoProps = { + setStep: Dispatch>; + onCancel: () => void; +}; + +export const VoteContextTerms = ({ setStep, onCancel }: StoreDataInfoProps) => { + const { t } = useTranslation(); + const { control, errors, watch } = useVoteContextForm(); + const { isMobile } = useScreenDimension(); + + // TODO: change link when available + const openLink = () => openInNewTab("https://docs.sanchogov.tools"); + + const isContinueDisabled = !watch("terms"); + + return ( + setStep(3)} + isContinueDisabled={isContinueDisabled} + onCancel={onCancel} + > + + {t("createGovernanceAction.storeDataTitle")} + + + {t("createGovernanceAction.storeDataLink")} + + + + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextText.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextText.tsx new file mode 100644 index 000000000..f241de814 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextText.tsx @@ -0,0 +1,77 @@ +import { Dispatch, SetStateAction } from "react"; + +import { orange } from "@consts"; +import { Typography } from "@atoms"; +import { VoteContextWrapper } from "@organisms"; +import { useTranslation, useVoteContextForm } from "@/hooks"; +import { ControlledField } from ".."; + +type VoteContextTextProps = { + setStep: Dispatch>; + onCancel: () => void; +}; + +export const VoteContextText = ({ + setStep, + onCancel, +}: VoteContextTextProps) => { + const { t } = useTranslation(); + + const { control, errors, watch } = useVoteContextForm(); + const isContinueDisabled = !watch("voteContextText"); + + const fieldProps = { + key: "voteContextText", + layoutStyles: { mb: 3 }, + name: "voteContextText", + placeholder: t("govActions.provideContext"), + rules: { + required: { + value: true, + message: t("createGovernanceAction.fields.validations.required"), + }, + maxLength: { + value: 500, + message: t("createGovernanceAction.fields.validations.maxLength", { + maxLength: 500, + }), + }, + }, + }; + + return ( + setStep(2)} + isContinueDisabled={isContinueDisabled} + onCancel={onCancel} + > + + {t("optional")} + + + {t("govActions.provideContextAboutYourVote")} + + + {/* TODO: Update text when design is finalised */} + Additional information about your vote + + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextWrapper.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextWrapper.tsx new file mode 100644 index 000000000..91ceb8ed5 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextWrapper.tsx @@ -0,0 +1,64 @@ +import { FC, PropsWithChildren } from "react"; +import { Box } from "@mui/material"; + +import { useScreenDimension, useTranslation } from "@hooks"; +import { Button } from "@atoms"; + +type VoteContextWrapperProps = { + onContinue: () => void; + isContinueDisabled?: boolean; + onCancel: () => void; +}; + +export const VoteContextWrapper: FC< + PropsWithChildren +> = ({ onContinue, isContinueDisabled, onCancel, children }) => { + const { isMobile } = useScreenDimension(); + const { t } = useTranslation(); + + return ( + <> + + {children} + + + + + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/index.ts b/govtool/frontend/src/components/organisms/VoteContext/index.ts new file mode 100644 index 000000000..b288c8de3 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/index.ts @@ -0,0 +1,6 @@ +export * from "./VoteContextModal"; +export * from "./VoteContextStoringInformation"; +export * from "./VoteContextCheckResult"; +export * from "./VoteContextTerms"; +export * from "./VoteContextText"; +export * from "./VoteContextWrapper"; diff --git a/govtool/frontend/src/components/organisms/index.ts b/govtool/frontend/src/components/organisms/index.ts index cccbc8400..f8da1ecb0 100644 --- a/govtool/frontend/src/components/organisms/index.ts +++ b/govtool/frontend/src/components/organisms/index.ts @@ -29,4 +29,5 @@ export * from "./RetireAsSoleVoterBoxContent"; export * from "./Slider"; export * from "./StatusModal"; export * from "./TopNav"; +export * from "./VoteContext"; export * from "./VotingPowerModal"; diff --git a/govtool/frontend/src/consts/governanceAction/fields.ts b/govtool/frontend/src/consts/governanceAction/fields.ts index b0395cab4..b172bf784 100644 --- a/govtool/frontend/src/consts/governanceAction/fields.ts +++ b/govtool/frontend/src/consts/governanceAction/fields.ts @@ -163,3 +163,33 @@ export const GOVERNANCE_ACTION_CONTEXT = { }, }, }; + +export const VOTE_TEST_CONTEXT = { + "@language": "en-us", + CIP100: + "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#", + CIP108: + "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0108/README.md#", + hashAlgorithm: "CIP100:hashAlgorithm", + body: { + "@id": "CIP108:body", + "@context": { + text: "CIP108:text", + }, + }, + authors: { + "@id": "CIP100:authors", + "@container": "@set" as const, + "@context": { + name: "http://xmlns.com/foaf/0.1/name", + witness: { + "@id": "CIP100:witness", + "@context": { + witnessAlgorithm: "CIP100:witnessAlgorithm", + publicKey: "CIP100:publicKey", + signature: "CIP100:signature", + }, + }, + }, + }, +}; diff --git a/govtool/frontend/src/context/modal.tsx b/govtool/frontend/src/context/modal.tsx index 7dbe36e15..cebfab96b 100644 --- a/govtool/frontend/src/context/modal.tsx +++ b/govtool/frontend/src/context/modal.tsx @@ -5,6 +5,7 @@ import { ChooseWalletModal, ExternalLinkModal, StatusModal, + VoteContextModal, VotingPowerModal, } from "@organisms"; import { basicReducer, callAll, BasicReducer } from "@utils"; @@ -25,7 +26,8 @@ export type ModalType = | "chooseWallet" | "statusModal" | "externalLink" - | "votingPower"; + | "votingPower" + | "voteContext"; const modals: Record = { none: { @@ -43,6 +45,9 @@ const modals: Record = { votingPower: { component: , }, + voteContext: { + component: , + }, }; type Optional = Pick, K> & Omit; diff --git a/govtool/frontend/src/hooks/forms/index.ts b/govtool/frontend/src/hooks/forms/index.ts index a1b5ca4e4..f86aeba6c 100644 --- a/govtool/frontend/src/hooks/forms/index.ts +++ b/govtool/frontend/src/hooks/forms/index.ts @@ -4,3 +4,4 @@ export * from "./useRegisterAsdRepForm"; export * from "./useUpdatedRepMetadataForm"; export * from "./useUrlAndHashFormController"; export * from "./useVoteActionForm"; +export * from "./useVoteContextForm"; diff --git a/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx b/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx index 107dc7cfd..50d85c5e6 100644 --- a/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx +++ b/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx @@ -6,54 +6,31 @@ import { useLocation, useNavigate } from "react-router-dom"; import { PATHS } from "@consts"; import { useCardano, useSnackbar } from "@context"; -import { HASH_REGEX, URL_REGEX } from "@utils"; -import { useTranslation } from "@hooks"; -import { UrlAndHashFormValues } from "./useUrlAndHashFormController"; -export interface VoteActionFormValues extends UrlAndHashFormValues { +export interface VoteActionFormValues { vote: string; } export const useVoteActionFormController = () => { - const { t } = useTranslation(); - const validationSchema = useMemo( () => Yup.object().shape({ vote: Yup.string().oneOf(["yes", "no", "abstain"]).required(), - url: Yup.string() - .trim() - .max(64, t("forms.errors.urlTooLong")) - .test( - "url-validation", - t("forms.errors.urlInvalidFormat"), - (value) => !value || URL_REGEX.test(value), - ), - hash: Yup.string() - .trim() - .test( - "hash-length-validation", - t("forms.errors.hashInvalidLength"), - (value) => !value || value.length === 64, - ) - .test( - "hash-format-validation", - t("forms.errors.hashInvalidFormat"), - (value) => !value || HASH_REGEX.test(value), - ), - storeData: Yup.boolean(), }), [], ); return useForm({ - defaultValues: { url: "", hash: "", vote: "" }, + defaultValues: { vote: "" }, mode: "onChange", resolver: yupResolver(validationSchema), }); }; -export const useVoteActionForm = () => { +export const useVoteActionForm = ( + voteContextHash?: string, + voteContextUrl?: string, +) => { const [isLoading, setIsLoading] = useState(false); const { buildSignSubmitConwayCertTx, buildVote, isPendingTransaction } = useCardano(); @@ -67,23 +44,22 @@ export const useVoteActionForm = () => { formState: { errors, isDirty }, setValue, register: registerInput, - clearErrors, } = useVoteActionFormController(); const watch = useWatch({ control, }); - const areFormErrors = !!errors.vote || !!errors.url || !!errors.hash; + const areFormErrors = !!errors.vote; const confirmVote = useCallback( async (values: VoteActionFormValues) => { setIsLoading(true); - const { url, hash, vote } = values; + const { vote } = values; - const urlSubmitValue = url ?? ""; - const hashSubmitValue = hash ?? ""; + const urlSubmitValue = voteContextUrl ?? ""; + const hashSubmitValue = voteContextHash ?? ""; try { const isPendingTx = isPendingTransaction(); @@ -118,14 +94,11 @@ export const useVoteActionForm = () => { ); return { - control, - errors, confirmVote: handleSubmit(confirmVote), setValue, vote: watch.vote, registerInput, isDirty, - clearErrors, areFormErrors, isVoteLoading: isLoading, }; diff --git a/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx new file mode 100644 index 000000000..27cfccede --- /dev/null +++ b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx @@ -0,0 +1,123 @@ +import { Dispatch, SetStateAction, useCallback, useState } from "react"; +import { NodeObject } from "jsonld"; +import { useFormContext } from "react-hook-form"; +import { blake2bHex } from "blakejs"; + +import { + CIP_108, + MetadataHashValidationErrors, + VOTE_TEST_CONTEXT, +} from "@consts"; +import { + canonizeJSON, + downloadJson, + generateJsonld, + validateMetadataHash, +} from "@utils"; +import { captureException } from "@sentry/react"; + +export type VoteContextFormValues = { + voteContextText: string; + terms?: boolean; + storingURL: string; +}; + +export const useVoteContextForm = ( + setSavedHash?: Dispatch>, + setStep?: Dispatch>, + setErrorMessage?: Dispatch>, +) => { + const [hash, setHash] = useState(null); + const [json, setJson] = useState(null); + + const { + control, + formState: { errors, isValid }, + getValues, + handleSubmit, + setValue, + watch, + register, + reset, + } = useFormContext(); + + const generateMetadata = useCallback(async () => { + const data = getValues(); + + const acceptedKeys = ["voteContextText"]; + + const filteredData = Object.entries(data) + .filter(([key]) => acceptedKeys.includes(key)) + .map(([key, value]) => [CIP_108 + key, value]); + + const body = { + ...Object.fromEntries(filteredData), + }; + + const jsonld = await generateJsonld(body, VOTE_TEST_CONTEXT); + const canonizedJson = await canonizeJSON(jsonld); + const canonizedJsonHash = blake2bHex(canonizedJson, undefined, 32); + + // That allows to validate metadata hash + setHash(canonizedJsonHash); + setJson(jsonld); + + return jsonld; + }, [getValues]); + + const onClickDownloadJson = useCallback(() => { + if (!json) return; + downloadJson(json, "Vote_Context"); + }, [json]); + + const validateHash = useCallback( + async (storingUrl: string, localHash: string | null) => { + try { + if (!localHash) { + throw new Error(MetadataHashValidationErrors.INVALID_HASH); + } + await validateMetadataHash(storingUrl, localHash); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if ( + Object.values(MetadataHashValidationErrors).includes(error.message) + ) { + if (setErrorMessage) setErrorMessage(error.message); + if (setStep) setStep(4); + } + throw error; + } + }, + [], + ); + + const onSubmit = useCallback( + async (data: VoteContextFormValues) => { + try { + await validateHash(data.storingURL, hash); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + captureException(error); + } finally { + if (setSavedHash) setSavedHash(hash); + if (setStep) setStep(4); + } + }, + [hash], + ); + + return { + control, + validateURL: handleSubmit(onSubmit), + errors, + generateMetadata, + getValues, + isValid, + onClickDownloadJson, + register, + reset, + setValue, + watch, + hash, + }; +}; diff --git a/govtool/frontend/src/hooks/index.ts b/govtool/frontend/src/hooks/index.ts index 126c2d0c9..da6b8743b 100644 --- a/govtool/frontend/src/hooks/index.ts +++ b/govtool/frontend/src/hooks/index.ts @@ -1,4 +1,7 @@ export { useTranslation } from "react-i18next"; + +export * from "./useDataActionsBar"; +export * from "./useDebounce"; export * from "./useFetchNextPageDetector"; export * from "./useOutsideClick"; export * from "./useSaveScrollPosition"; diff --git a/govtool/frontend/src/hooks/queries/useGetProposalsInfiniteQuery.ts b/govtool/frontend/src/hooks/queries/useGetProposalsInfiniteQuery.ts index 7e141842e..fee1e9482 100644 --- a/govtool/frontend/src/hooks/queries/useGetProposalsInfiniteQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetProposalsInfiniteQuery.ts @@ -2,13 +2,14 @@ import { useInfiniteQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; -import { getProposals, getProposalsArguments } from "@services"; +import { getProposals, GetProposalsArguments } from "@services"; export const useGetProposalsInfiniteQuery = ({ filters = [], pageSize = 10, + searchPhrase, sorting = "", -}: getProposalsArguments) => { +}: GetProposalsArguments) => { const { dRepID, isEnabled, pendingTransaction } = useCardano(); const fetchProposals = ({ pageParam = 0 }) => @@ -17,6 +18,7 @@ export const useGetProposalsInfiniteQuery = ({ filters, page: pageParam, pageSize, + searchPhrase, sorting, }); @@ -34,6 +36,7 @@ export const useGetProposalsInfiniteQuery = ({ filters, isEnabled, pendingTransaction.vote?.transactionHash, + searchPhrase, sorting, ], fetchProposals, diff --git a/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts b/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts index 242add713..981cd0b55 100644 --- a/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts @@ -2,20 +2,19 @@ import { useQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; -import { getProposals, getProposalsArguments } from "@services"; -import { getFullGovActionId } from "@utils"; +import { getProposals, GetProposalsArguments } from "@services"; export const useGetProposalsQuery = ({ filters = [], - sorting, searchPhrase, -}: getProposalsArguments) => { + sorting, +}: GetProposalsArguments) => { const { dRepID, pendingTransaction } = useCardano(); const fetchProposals = async (): Promise => { const allProposals = await Promise.all( filters.map((filter) => - getProposals({ dRepID, filters: [filter], sorting }), + getProposals({ dRepID, filters: [filter], searchPhrase, sorting }), ), ); @@ -26,6 +25,7 @@ export const useGetProposalsQuery = ({ [ QUERY_KEYS.useGetProposalsKey, filters, + searchPhrase, sorting, dRepID, pendingTransaction.vote?.transactionHash, @@ -33,38 +33,27 @@ export const useGetProposalsQuery = ({ fetchProposals, ); - const mappedData = Object.values( - (groupedByType( - data?.filter((i) => - getFullGovActionId(i.txHash, i.index) - .toLowerCase() - .includes(searchPhrase.toLowerCase())) - ) ?? []) as ToVoteDataType + const proposals = Object.values( + (groupByType(data) ?? []) ); return { isProposalsLoading: isLoading, - proposals: mappedData, + proposals, }; }; -const groupedByType = (data?: ActionType[]) => data?.reduce((groups, item) => { - const itemType = item.type; +const groupByType = (data?: ActionType[]) => + data?.reduce>>((groups, item) => { + const itemType = item.type; - // TODO: Provide better typing for groups - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - if (!groups[itemType]) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - groups[itemType] = { - title: itemType, - actions: [], - }; - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - groups[itemType].actions.push(item); + if (!groups[itemType]) { + groups[itemType] = { + title: itemType, + actions: [], + }; + } + groups[itemType].actions.push(item); - return groups; -}, {}); + return groups; + }, {}); diff --git a/govtool/frontend/src/hooks/useDataActionsBar.tsx b/govtool/frontend/src/hooks/useDataActionsBar.tsx new file mode 100644 index 000000000..61723d0ea --- /dev/null +++ b/govtool/frontend/src/hooks/useDataActionsBar.tsx @@ -0,0 +1,58 @@ +import { useState, useCallback, Dispatch, SetStateAction } from "react"; + +import { + useDebounce, +} from "@hooks"; + +type UseDataActionsBarReturnType = { + chosenFilters: string[]; + chosenFiltersLength: number; + chosenSorting: string; + closeFilters: () => void; + closeSorts: () => void; + debouncedSearchText: string; + filtersOpen: boolean; + searchText: string; + setChosenFilters: Dispatch>; + setChosenSorting: Dispatch>; + setFiltersOpen: Dispatch>; + setSearchText: Dispatch>; + setSortOpen: Dispatch>; + sortingActive: boolean; + sortOpen: boolean; +}; + +export const useDataActionsBar = (): UseDataActionsBarReturnType => { + const [searchText, setSearchText] = useState(""); + const debouncedSearchText = useDebounce(searchText, 300); + const [filtersOpen, setFiltersOpen] = useState(false); + const [chosenFilters, setChosenFilters] = useState([]); + const [sortOpen, setSortOpen] = useState(false); + const [chosenSorting, setChosenSorting] = useState(""); + + const closeFilters = useCallback(() => { + setFiltersOpen(false); + }, [setFiltersOpen]); + + const closeSorts = useCallback(() => { + setSortOpen(false); + }, [setSortOpen]); + + return { + chosenFilters, + chosenFiltersLength: chosenFilters.length, + chosenSorting, + closeFilters, + closeSorts, + debouncedSearchText, + filtersOpen, + searchText, + setChosenFilters, + setChosenSorting, + setFiltersOpen, + setSearchText, + setSortOpen, + sortingActive: Boolean(chosenSorting), + sortOpen, + }; +}; diff --git a/govtool/frontend/src/hooks/useDebounce.ts b/govtool/frontend/src/hooks/useDebounce.ts new file mode 100644 index 000000000..335b5cf26 --- /dev/null +++ b/govtool/frontend/src/hooks/useDebounce.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from 'react'; + +export function useDebounce(value: T, delay: number) { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timerID = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(timerID); + }; + }, [value, delay]); + + return debouncedValue; +} diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 73e9c4217..9f84a3429 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -307,6 +307,7 @@ export const en = { changeVote: "Change vote", changeYourVote: "Change your vote", chooseHowToVote: "Choose how you want to vote:", + contextAboutYourVote: "Context about your vote", dataMissing: "Data Missing", dataMissingTooltipExplanation: "Please click “View Details” for more information.", @@ -317,12 +318,14 @@ export const en = { forGovAction: "for this Governance Action", governanceActionId: "Governance Action ID:", governanceActionType: "Governance Action Type:", + goToVote: "Go to Vote", motivation: "Motivation", myVote: "My Vote:", noResultsForTheSearch: "No results for the search.", onChainTransactionDetails: "On-chain Transaction Details", optional: "(optional)", - provideContext: "Provide context about your vote", + provideContext: "Provide context", + provideContextAboutYourVote: "Provide context about your vote", rationale: "Rationale", seeExternalData: "See external data", selectDifferentOption: "Select a different option to change your vote", @@ -337,6 +340,7 @@ export const en = { viewOtherDetails: "View other details", viewProposalDetails: "View proposal details", vote: "Vote", + voteContextJsonldFileName: "Vote_Context.jsonld", votedOnByMe: "Voted on by me", voteOnGovActions: "Vote on Governance Action", voteSubmitted: "Vote submitted", @@ -345,6 +349,8 @@ export const en = { votesSubmitted: "Votes submitted", votesSubmittedOnChain: "Votes submitted on-chain by DReps, SPOs and Constitutional Committee members.", + youCanProvideContext: + "You can provide context about your vote. This information will be viewable by other users.", youHaventVotedYet: "You haven't voted on any Governance Actions yet. Check the 'To vote on' section to vote on Governance Actions.", withCategoryNotExist: { @@ -616,10 +622,12 @@ export const en = { cancel: "Cancel", clear: "Clear", clickToCopyLink: "Click to copy link", + close: "Close", confirm: "Confirm", continue: "Continue", delegate: "Delegate", filter: "Filter", + goBack: "Go back", here: "here", inProgress: "In progress", learnMore: "Learn more", @@ -634,6 +642,7 @@ export const en = { seeTransaction: "See transaction", select: "Select", share: "Share", + showLess: "Show less", showMore: "Show more", skip: "Skip", sort: "Sort", diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 9f16b07ff..2fd348e6d 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -26,9 +26,11 @@ export interface VotedProposal { proposal: { id: string; type: string; - details: string; + details?: ActionDetailsType; expiryDate: string; + expiryEpochNo: number; createdDate: string; + createdEpochNo: number; url: string; metadataHash: string; yesVotes: number; @@ -36,5 +38,9 @@ export interface VotedProposal { abstainVotes: number; txHash: string; index: number; + title: string | null; + about: string | null; + motivation: string | null; + rationale: string | null; }; } diff --git a/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx b/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx index 47384da36..3a50d324b 100644 --- a/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx +++ b/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx @@ -1,12 +1,13 @@ -import { useCallback, useMemo, useRef, useState } from "react"; +import { useMemo, useRef } from "react"; import { generatePath, useNavigate, useParams } from "react-router-dom"; import { Box, CircularProgress, Link } from "@mui/material"; import { Background, Typography } from "@atoms"; -import { ICONS, PATHS } from "@consts"; +import { GOVERNANCE_ACTIONS_SORTING, ICONS, PATHS } from "@consts"; import { useCardano } from "@context"; import { DataActionsBar, GovernanceActionCard } from "@molecules"; import { + useDataActionsBar, useFetchNextPageDetector, useGetProposalsInfiniteQuery, useGetVoterInfo, @@ -23,9 +24,8 @@ import { export const DashboardGovernanceActionsCategory = () => { const { category } = useParams(); - const [searchText, setSearchText] = useState(""); - const [sortOpen, setSortOpen] = useState(false); - const [chosenSorting, setChosenSorting] = useState(""); + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); + const { chosenSorting } = dataActionsBarProps; const { isMobile, screenWidth } = useScreenDimension(); const navigate = useNavigate(); const { pendingTransaction, isEnableLoading } = useCardano(); @@ -42,7 +42,7 @@ export const DashboardGovernanceActionsCategory = () => { } = useGetProposalsInfiniteQuery({ filters: [category?.replace(/ /g, "") ?? ""], sorting: chosenSorting, - searchPhrase: searchText, + searchPhrase: debouncedSearchText, }); const loadNextPageRef = useRef(null); @@ -57,25 +57,12 @@ export const DashboardGovernanceActionsCategory = () => { isProposalsFetching, ); - const mappedData = useMemo(() => { - const uniqueProposals = removeDuplicatedProposals(proposals); - - return uniqueProposals?.filter((i) => - getFullGovActionId(i.txHash, i.index) - .toLowerCase() - .includes(searchText.toLowerCase()), - ); - }, [ + const mappedData = useMemo(() => removeDuplicatedProposals(proposals), [ proposals, voter?.isRegisteredAsDRep, - searchText, isProposalsFetchingNextPage, ]); - const closeSorts = useCallback(() => { - setSortOpen(false); - }, [setSortOpen]); - return ( { { state ? state.txHash : data?.proposal.txHash ?? "", state ? state.index : data?.proposal.index ?? "", ); + const title = state ? state.title : data?.proposal.title; useEffect(() => { if (isEnabled && getItemFromLocalStorage(`${WALLET_LS_KEY}_stake_key`)) { @@ -86,7 +87,8 @@ export const GovernanceActionDetails = () => { { ? formatDisplayDate(state.createdDate) : formatDisplayDate(data.proposal.createdDate) } + createdEpochNo={ + state ? state.createdEpochNo : data.proposal.createdEpochNo + } // TODO: Add data validation isDataMissing={isDataMissing} expiryDate={ @@ -141,14 +146,23 @@ export const GovernanceActionDetails = () => { ? formatDisplayDate(state.expiryDate) : formatDisplayDate(data.proposal.expiryDate) } + expiryEpochNo={ + state ? state.expiryEpochNo : data.proposal.expiryEpochNo + } noVotes={state ? state.noVotes : data.proposal.noVotes} type={ state ? getProposalTypeLabel(state.type) : getProposalTypeLabel(data.proposal.type) } - // TODO: To decide if we want to keep it when metadate BE is ready - // url={state ? state.url : data.proposal.url} + details={state ? state.details : data.proposal.details} + url={state ? state.url : data.proposal.url} + title={state ? state.title : data.proposal.title} + about={state ? state.about : data.proposal.about} + motivation={ + state ? state.motivation : data.proposal.motivation + } + rationale={state ? state.rationale : data.proposal.rationale} yesVotes={state ? state.yesVotes : data.proposal.yesVotes} govActionId={fullProposalId} /> diff --git a/govtool/frontend/src/pages/GovernanceActions.tsx b/govtool/frontend/src/pages/GovernanceActions.tsx index 08eb11d15..bd4a807ed 100644 --- a/govtool/frontend/src/pages/GovernanceActions.tsx +++ b/govtool/frontend/src/pages/GovernanceActions.tsx @@ -1,11 +1,12 @@ -import { useState, useCallback, useEffect } from "react"; +import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { Box, CircularProgress, Divider } from "@mui/material"; import { Background, ScrollToManage, Typography } from "@atoms"; -import { GOVERNANCE_ACTIONS_FILTERS, PATHS } from "@consts"; +import { GOVERNANCE_ACTIONS_FILTERS, GOVERNANCE_ACTIONS_SORTING, PATHS } from "@consts"; import { useCardano } from "@context"; import { + useDataActionsBar, useGetProposalsQuery, useScreenDimension, useTranslation, @@ -19,11 +20,8 @@ const defaultCategories = GOVERNANCE_ACTIONS_FILTERS.map( ); export const GovernanceActions = () => { - const [searchText, setSearchText] = useState(""); - const [filtersOpen, setFiltersOpen] = useState(false); - const [chosenFilters, setChosenFilters] = useState([]); - const [sortOpen, setSortOpen] = useState(false); - const [chosenSorting, setChosenSorting] = useState(""); + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); + const { chosenFilters, chosenSorting } = dataActionsBarProps; const { isMobile, pagePadding } = useScreenDimension(); const { isEnabled } = useCardano(); const navigate = useNavigate(); @@ -35,7 +33,7 @@ export const GovernanceActions = () => { const { proposals, isProposalsLoading } = useGetProposalsQuery({ filters: queryFilters, sorting: chosenSorting, - searchPhrase: searchText, + searchPhrase: debouncedSearchText, }); useEffect(() => { @@ -44,14 +42,6 @@ export const GovernanceActions = () => { } }, [isEnabled]); - const closeFilters = useCallback(() => { - setFiltersOpen(false); - }, [setFiltersOpen]); - - const closeSorts = useCallback(() => { - setSortOpen(false); - }, [setSortOpen]); - return ( @@ -80,20 +70,10 @@ export const GovernanceActions = () => { )} {!proposals || isProposalsLoading ? ( @@ -110,7 +90,7 @@ export const GovernanceActions = () => { diff --git a/govtool/frontend/src/pages/GovernanceActionsCategory.tsx b/govtool/frontend/src/pages/GovernanceActionsCategory.tsx index 82b67ce46..8c2e8d800 100644 --- a/govtool/frontend/src/pages/GovernanceActionsCategory.tsx +++ b/govtool/frontend/src/pages/GovernanceActionsCategory.tsx @@ -1,9 +1,9 @@ -import { useState, useCallback, useEffect, useMemo, useRef } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Box, CircularProgress, Link } from "@mui/material"; import { Background, Typography } from "@atoms"; -import { ICONS, PATHS } from "@consts"; +import { GOVERNANCE_ACTIONS_SORTING, ICONS, PATHS } from "@consts"; import { useCardano } from "@context"; import { DataActionsBar, GovernanceActionCard } from "@molecules"; import { Footer, TopNav } from "@organisms"; @@ -14,6 +14,7 @@ import { useScreenDimension, useTranslation, useGetVoterInfo, + useDataActionsBar, } from "@hooks"; import { WALLET_LS_KEY, @@ -25,9 +26,8 @@ import { export const GovernanceActionsCategory = () => { const { category } = useParams(); - const [searchText, setSearchText] = useState(""); - const [sortOpen, setSortOpen] = useState(false); - const [chosenSorting, setChosenSorting] = useState(""); + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); + const { chosenSorting } = dataActionsBarProps; const { isMobile, pagePadding, screenWidth } = useScreenDimension(); const { isEnabled } = useCardano(); const navigate = useNavigate(); @@ -44,7 +44,7 @@ export const GovernanceActionsCategory = () => { } = useGetProposalsInfiniteQuery({ filters: [category?.replace(/ /g, "") ?? ""], sorting: chosenSorting, - searchPhrase: searchText, + searchPhrase: debouncedSearchText, }); const loadNextPageRef = useRef(null); @@ -59,19 +59,10 @@ export const GovernanceActionsCategory = () => { isProposalsFetching, ); - const mappedData = useMemo(() => { - const uniqueProposals = removeDuplicatedProposals(proposals); - - return uniqueProposals?.filter((i) => - getFullGovActionId(i.txHash, i.index) - .toLowerCase() - .includes(searchText.toLowerCase()), - ); - }, [ + const mappedData = useMemo(() => removeDuplicatedProposals(proposals), [ voter?.isRegisteredAsDRep, isProposalsFetchingNextPage, proposals, - searchText, ]); useEffect(() => { @@ -81,10 +72,6 @@ export const GovernanceActionsCategory = () => { } }, [isEnabled]); - const closeSorts = useCallback(() => { - setSortOpen(false); - }, [setSortOpen]); - return ( { { {category}   - {searchText && ( + {debouncedSearchText && ( <> {t("govActions.withCategoryNotExist.optional")}   - {searchText} + {debouncedSearchText} )} diff --git a/govtool/frontend/src/services/requests/getProposals.ts b/govtool/frontend/src/services/requests/getProposals.ts index e4d1c4bf6..96c52a99c 100644 --- a/govtool/frontend/src/services/requests/getProposals.ts +++ b/govtool/frontend/src/services/requests/getProposals.ts @@ -1,12 +1,12 @@ import { API } from "../API"; -export type getProposalsArguments = { +export type GetProposalsArguments = { dRepID?: string; filters?: string[]; page?: number; pageSize?: number; sorting?: string; - searchPhrase: string; + searchPhrase?: string; }; export const getProposals = async ({ @@ -15,23 +15,18 @@ export const getProposals = async ({ page = 0, // It allows fetch proposals and if we have 7 items, display 6 cards and "view all" button pageSize = 7, + searchPhrase = "", sorting = "", -}: Omit) => { - const urlBase = "/proposal/list"; - let urlParameters = `?page=${page}&pageSize=${pageSize}`; - - if (filters.length > 0) { - filters.forEach((item) => { - urlParameters += `&type=${item}`; - }); - } - if (sorting.length) { - urlParameters += `&sort=${sorting}`; - } - if (dRepID) { - urlParameters += `&drepId=${dRepID}`; - } - - const response = await API.get(`${urlBase}${urlParameters}`); +}: GetProposalsArguments) => { + const response = await API.get("/proposal/list", { + params: { + page, + pageSize, + ...(searchPhrase && { search: searchPhrase }), + ...(filters.length && { type: filters }), + ...(sorting && { sort: sorting }), + ...(dRepID && { drepId: dRepID }), + }, + }); return response.data; }; diff --git a/govtool/frontend/src/stories/DashboardCard.stories.ts b/govtool/frontend/src/stories/DashboardCard.stories.ts index 7574a9f5a..ac4fb83a2 100644 --- a/govtool/frontend/src/stories/DashboardCard.stories.ts +++ b/govtool/frontend/src/stories/DashboardCard.stories.ts @@ -19,10 +19,12 @@ type Story = StoryObj; export const DashboardCardComponent: Story = { args: { + buttons: [ + { children: "first button" }, + { children: "second button" }, + ], description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit.", - firstButtonLabel: "first button", imageURL: IMAGES.govActionDelegateImage, - secondButtonLabel: "second button", title: "Action card", }, play: async ({ canvasElement }) => { @@ -38,21 +40,24 @@ export const DashboardCardComponent: Story = { export const WithDRepIdDashboardCardComponent: Story = { args: { + buttons: [ + { children: "first button" }, + { children: "second button" }, + ], description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit.", - firstButtonLabel: "first button", imageURL: IMAGES.govActionDelegateImage, - secondButtonLabel: "second button", title: "Action card", - cardId: "drep1gwsw9ckkhuwscj9savt5f7u9xsrudw209hne7pggcktzuw5sv32", }, }; export const LoadingDashboardCard: Story = { args: { + buttons: [ + { children: "first button" }, + { children: "second button" }, + ], description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit.", - firstButtonLabel: "first button", imageURL: IMAGES.govActionDelegateImage, - secondButtonLabel: "second button", title: "Action card", isLoading: true, }, @@ -67,12 +72,14 @@ export const LoadingDashboardCard: Story = { export const InProgressDashboardCard: Story = { args: { + buttons: [ + { children: "first button" }, + { children: "second button" }, + ], description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit.", - firstButtonLabel: "first button", imageURL: IMAGES.govActionDelegateImage, - secondButtonLabel: "second button", title: "Action card", - inProgress: true, + state: "inProgress", }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); diff --git a/govtool/frontend/src/types/global.d.ts b/govtool/frontend/src/types/global.d.ts index 49890ef5b..1f3fce22c 100644 --- a/govtool/frontend/src/types/global.d.ts +++ b/govtool/frontend/src/types/global.d.ts @@ -12,12 +12,18 @@ declare global { metadataHash: string; }; + type ActionDetailsType = { + [key: string]: string | number; + }; + type ActionType = { id: string; type: string; - details?: string; + details?: ActionDetailsType; expiryDate: string; + expiryEpochNo: number; createdDate: string; + createdEpochNo: number; url?: string; metadataHash?: string; yesVotes: number; @@ -25,6 +31,10 @@ declare global { abstainVotes: number; index: number; txHash: string; + title: string | null; + about: string | null; + motivation: string | null; + rationale: string | null; }; interface ActionVotedOnType extends ActionType { @@ -56,4 +66,7 @@ declare global { | null | { [property: string]: JSONValue } | JSONValue[]; + + type ArrayElement = + ArrayType extends readonly (infer ElementType)[] ? ElementType : never; } diff --git a/govtool/frontend/src/utils/tests/removeDuplicatedProposals.test.ts b/govtool/frontend/src/utils/tests/removeDuplicatedProposals.test.ts index c59e45259..c1f902c80 100644 --- a/govtool/frontend/src/utils/tests/removeDuplicatedProposals.test.ts +++ b/govtool/frontend/src/utils/tests/removeDuplicatedProposals.test.ts @@ -6,45 +6,72 @@ const uniqueProposals = [ txHash: "2bca7756ba6c998518c1bccbcdd5165e32d3c8e0bfdf930d34359c98354e85a0", index: 0, type: "InfoAction", - details: "InfoAction 1", + details: { + description: "Info about InfoAction 1", + additionalInfo: "Additional information for InfoAction 1", + }, expiryDate: "2024-01-08T15:32:13.61165Z", + expiryEpochNo: 1673183533, createdDate: "2023-12-29T23:04:41Z", + createdEpochNo: 1672350281, url: "https://bit.ly/3zCH2HL", metadataHash: "1111111111111111111111111111111111111111111111111111111111111111", yesVotes: 0, noVotes: 0, abstainVotes: 81528377728, + title: "Proposal 1322 Title", + about: "This is about Proposal 1322", + motivation: "Motivation behind Proposal 1322", + rationale: "Rationale for Proposal 1322", }, { id: "1338", txHash: "5e37f4d48182c4d8ff8e8ee7472c066501459b7bc8aaf6ca2f93a522ae12b0ea", index: 0, type: "InfoAction", - details: "InfoAction 2", + details: { + description: "Info about InfoAction 2", + additionalInfo: "Additional information for InfoAction 2", + }, expiryDate: "2024-01-15T15:16:04.8932Z", + expiryEpochNo: 1673895364, createdDate: "2024-01-05T23:06:02Z", + createdEpochNo: 1672953962, url: "https://bit.ly/3zCH2HL", metadataHash: "2222222222222222222222222222222222222222222222222222222222222222", yesVotes: 0, noVotes: 0, abstainVotes: 81528377728, + title: "Proposal 1338 Title", + about: "This is about Proposal 1338", + motivation: "Motivation behind Proposal 1338", + rationale: "Rationale for Proposal 1338", }, { id: "1335", txHash: "e88ddff921de8b7f6079a1c25a301c034de6b3ec8a906ad75463f0f5b3597672", index: 0, type: "InfoAction", - details: "InfoAction 3", + details: { + description: "Info about InfoAction 3", + additionalInfo: "Additional information for InfoAction 3", + }, expiryDate: "2024-01-14T15:18:23.28155Z", + expiryEpochNo: 1673807903, createdDate: "2024-01-04T23:06:36Z", + createdEpochNo: 1672867596, url: "https://bit.ly/3zCH2HL", metadataHash: "3333333333333333333333333333333333333333333333333333333333333333", yesVotes: 3175400714, noVotes: 0, abstainVotes: 81528377728, + title: "Proposal 1335 Title", + about: "This is about Proposal 1335", + motivation: "Motivation behind Proposal 1335", + rationale: "Rationale for Proposal 1335", }, ]; diff --git a/infra/terraform/modules/ecr/outputs.tf b/infra/terraform/modules/ecr/outputs.tf index eccd5b6fe..50d112224 100644 --- a/infra/terraform/modules/ecr/outputs.tf +++ b/infra/terraform/modules/ecr/outputs.tf @@ -4,4 +4,4 @@ output "repo_arn" { output "repo_url" { value = aws_ecr_repository.ecr_repo.repository_url -} \ No newline at end of file +} diff --git a/scripts/govtool/Makefile b/scripts/govtool/Makefile index 6fdd639cd..8d6430e0e 100644 --- a/scripts/govtool/Makefile +++ b/scripts/govtool/Makefile @@ -7,8 +7,8 @@ include config.mk .DEFAULT_GOAL := info # image tags -cardano_node_image_tag := 8.8.0-pre -cardano_db_sync_image_tag := sancho-4-0-0-fix-config +cardano_node_image_tag := 8.9.0 +cardano_db_sync_image_tag := sancho-4.1.0 .PHONY: all all: deploy-stack notify diff --git a/scripts/govtool/config/templates/grafana-provisioning/dashboards/dashboard.yml b/scripts/govtool/config/templates/grafana-provisioning/dashboards/dashboard.yml index bc1403098..3f9c826ce 100644 --- a/scripts/govtool/config/templates/grafana-provisioning/dashboards/dashboard.yml +++ b/scripts/govtool/config/templates/grafana-provisioning/dashboards/dashboard.yml @@ -18,4 +18,4 @@ providers: # how often Grafana will scan for changed dashboards updateIntervalSeconds: 10 options: - path: /etc/grafana/provisioning/dashboards \ No newline at end of file + path: /etc/grafana/provisioning/dashboards diff --git a/scripts/govtool/config/templates/grafana-provisioning/dashboards/traefik_rev4.json b/scripts/govtool/config/templates/grafana-provisioning/dashboards/traefik_rev4.json index 05e4787d1..4ab3fd72d 100644 --- a/scripts/govtool/config/templates/grafana-provisioning/dashboards/traefik_rev4.json +++ b/scripts/govtool/config/templates/grafana-provisioning/dashboards/traefik_rev4.json @@ -690,4 +690,4 @@ "title": "Traefik2", "uid": "qPdAviJmz1", "version": 1 -} \ No newline at end of file +} diff --git a/scripts/govtool/default.nix b/scripts/govtool/default.nix index 1de27a45f..420c999e6 100644 --- a/scripts/govtool/default.nix +++ b/scripts/govtool/default.nix @@ -7,7 +7,7 @@ pkgs.mkShell { tput bold warn "Welcome to GovTool!" 4 warn "This is a deployment shell." 4 - warn "Read the scripts/govtool/README.md to get more info about the deployment processes." 8 + warn "Read the ${./README.md} to get more info about the deployment processes." 8 case "''${ENVIRONMENT}" in "dev") warn "Your configuration is set to deploy to DEV environment, you are safe." 2 diff --git a/tests/govtool-backend/.env.example b/tests/govtool-backend/.env.example index 0d5d4a60e..64603fc5f 100644 --- a/tests/govtool-backend/.env.example +++ b/tests/govtool-backend/.env.example @@ -4,4 +4,4 @@ METRICS_API_SECRET= `api_secret` # required for setup KUBER_API_URL = "" -KUBER_API_KEY = "" \ No newline at end of file +KUBER_API_KEY = "" diff --git a/tests/govtool-backend/README.md b/tests/govtool-backend/README.md index 33c6f211e..fd8068f33 100644 --- a/tests/govtool-backend/README.md +++ b/tests/govtool-backend/README.md @@ -8,14 +8,14 @@ This test is responsible for following ## Installation ```shell -python -m venv venv +python -m venv venv source venv/bin/activate pip install -r requirements.txt ``` ## Wallet Setup -Test requires that certain dreps/stakes be registered in the network. +Test requires that certain dreps/stakes be registered in the network. To run setup script the main wallet must have enough balance. The address for the main wallet is @@ -35,6 +35,6 @@ In the root directory of tests/govtool-backend run the following command ```shell export BASE_URL="url" # server's url e.g. https://staging.govtool.byron.network/api" export METRICS_URL="url" # metrics server Url -export METRICS_API_SECRET="metrics-api-secret" +export METRICS_API_SECRET="metrics-api-secret" pytest -v ``` diff --git a/tests/govtool-backend/config.py b/tests/govtool-backend/config.py index 65c213cb2..f5f382162 100644 --- a/tests/govtool-backend/config.py +++ b/tests/govtool-backend/config.py @@ -13,4 +13,3 @@ METRICS_API_SECRET= os.getenv("METRICS_API_SECRET") KUBER_API_URL = os.getenv("KUBER_API_URL") KUBER_API_KEY= os.getenv("KUBER_API_KEY") - diff --git a/tests/govtool-backend/requirements.txt b/tests/govtool-backend/requirements.txt index d193413a9..b20c7ad50 100644 --- a/tests/govtool-backend/requirements.txt +++ b/tests/govtool-backend/requirements.txt @@ -1,4 +1,4 @@ pytest==7.4.0 pytest-github-report==0.0.1 python-dotenv==1.0.0 -requests==2.31.0 \ No newline at end of file +requests==2.31.0 diff --git a/tests/govtool-backend/setup.py b/tests/govtool-backend/setup.py index aa3c19b57..a4b78da41 100644 --- a/tests/govtool-backend/setup.py +++ b/tests/govtool-backend/setup.py @@ -188,4 +188,4 @@ def main(): # write to the file in nice format \ -main() \ No newline at end of file +main() diff --git a/tests/govtool-backend/test_cases/fixtures/drep.py b/tests/govtool-backend/test_cases/fixtures/drep.py index f33b74ce5..3ef4fecd7 100644 --- a/tests/govtool-backend/test_cases/fixtures/drep.py +++ b/tests/govtool-backend/test_cases/fixtures/drep.py @@ -6,4 +6,4 @@ @pytest.fixture(scope="module", params=drep_data) def registered_drep(request): drep_datum: Drep = request.param - yield drep_datum \ No newline at end of file + yield drep_datum diff --git a/tests/govtool-backend/test_cases/govtool_api.py b/tests/govtool-backend/test_cases/govtool_api.py index 604c15459..c37f567ee 100644 --- a/tests/govtool-backend/test_cases/govtool_api.py +++ b/tests/govtool-backend/test_cases/govtool_api.py @@ -35,7 +35,7 @@ def __request(self, method: str, endpoint: str, param: Any | None = None, response_json_str = response_json[:200] except: response_json_str = "Something went wrong" - + request_info = { "method": method, "endpoint": endpoint, @@ -73,6 +73,6 @@ def ada_holder_get_current_delegation(self, stake_key: str) -> Response: def ada_holder_get_voting_power(self, stake_key) -> Response: return self.__get('/ada-holder/get-voting-power', stake_key) - + def add_test_metrics(self, metrics: Metrics): self.tests_log.append(metrics) diff --git a/tests/govtool-backend/test_data.json b/tests/govtool-backend/test_data.json index 790dddc6f..b28fb85db 100644 --- a/tests/govtool-backend/test_data.json +++ b/tests/govtool-backend/test_data.json @@ -1 +1 @@ -{"drep_wallets": [{"pay-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207da324397a403f89972ba63f2853c6c6043fd96dac3bdcc452f27c9ad5c75c83"}, "address": "addr_test1qzh73vyy0mtu5xfahdswmaclzcs9lrm8hsvq0n799ufhp53htvec6kdtxqls04v5ldacx342v5rsflxlep93s6t5k2hs70m6n2", "stake-skey": {"type": "StakeSigningKeyShelley_ed25519", "description": "Stake Signing Key", "cborHex": "582036742f9246e355e75318894cb31f7058510f827c6820f40f56cce9bbdab8ef08"}, "drep-id": "drep1xadn8r2e4vcr7p74jnahhq6x4fjswp8umlyykxrfwje2707cqh9", "stake-vkey": "375b338d59ab303f07d594fb7b8346aa650704fcdfc84b186974b2af", "url": "https://bit.ly/3zCH2HL", "data_hash": "1111111111111111111111111111111111111111111111111111111111111111"}, {"pay-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205db2e13ca102a6bcfea2d4651d24559ee933ab6c355796307cace0bd23584b17"}, "address": "addr_test1qqu3ny5xjfhg9hqg3yfdf9arftg20dv92u3r8hkc94833xlv4tvazt5672duf338dx5zf0stl05zgc8g08qy0asathfs8fewtx", "stake-skey": {"type": "StakeSigningKeyShelley_ed25519", "description": "Stake Signing Key", "cborHex": "582042ab191b40e5b1364beaa4b0d27fea48156d89c92ba749b738bf7891e27fbb6a"}, "drep-id": "drep1aj4dn5fwntefh3xxya56sf97p0a7sfrqapuuq3lkr4waxeelmwd", "stake-vkey": "ecaad9d12e9af29bc4c62769a824be0bfbe82460e879c047f61d5dd3", "url": "https://bit.ly/3zCH2HL", "data_hash": "1111111111111111111111111111111111111111111111111111111111111111"}], "ada_holder_wallets": [{"address": "addr_test1qrqwl94r7zhxqwq8n26p6ql9dzylmzupln8vwaake9njg6wlrxfdmq43utplzwyuaqq8q8xyjvqdul88rda02l95lm9qpauf3k", "pay-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c5b5ad023d8eb7ddc67b271d79705522b65740b9c249e205e39fa30dec775deb"}, "stake-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Stake Signing Key", "cborHex": "5820ea031c372c0617cf7137e7cfbfb821d63e61aa3277af993f84d2b4cdb9199dd6"}, "drep-id": "drep1muve9hvzk83v8ufcnn5qququcjfsphnuuudh4atuknlv5kh84lc", "stake-vkey": "df1992dd82b1e2c3f1389ce800701cc49300de7ce71b7af57cb4feca"}, {"pay-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e6b1bac201091179f8ef213b2dc0f63c72b23de45bc26a7b68eccdc718f65c83"}, "address": "addr_test1qrz8rz38rv37cx4hsgsneavsx4e84ppupwysxp6xp6mv6c9nny8znayz56vw8rfyt0gyyftg6pt5umr9njeey8fjekhqwkrrew", "stake-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Stake Signing Key", "cborHex": "5820826d043e62e04259ffb24c24994e8c8ffd2272eda6e8a610a65977c233077b6d"}, "drep-id": "drep1kwvsu205s2nf3cudy3daqs39drg9wnnvvkwt8ysaxtx6up8cy06", "stake-vkey": "b3990e29f482a698e38d245bd0422568d0574e6c659cb3921d32cdae"}]} \ No newline at end of file +{"drep_wallets": [{"pay-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207da324397a403f89972ba63f2853c6c6043fd96dac3bdcc452f27c9ad5c75c83"}, "address": "addr_test1qzh73vyy0mtu5xfahdswmaclzcs9lrm8hsvq0n799ufhp53htvec6kdtxqls04v5ldacx342v5rsflxlep93s6t5k2hs70m6n2", "stake-skey": {"type": "StakeSigningKeyShelley_ed25519", "description": "Stake Signing Key", "cborHex": "582036742f9246e355e75318894cb31f7058510f827c6820f40f56cce9bbdab8ef08"}, "drep-id": "drep1xadn8r2e4vcr7p74jnahhq6x4fjswp8umlyykxrfwje2707cqh9", "stake-vkey": "375b338d59ab303f07d594fb7b8346aa650704fcdfc84b186974b2af", "url": "https://bit.ly/3zCH2HL", "data_hash": "1111111111111111111111111111111111111111111111111111111111111111"}, {"pay-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205db2e13ca102a6bcfea2d4651d24559ee933ab6c355796307cace0bd23584b17"}, "address": "addr_test1qqu3ny5xjfhg9hqg3yfdf9arftg20dv92u3r8hkc94833xlv4tvazt5672duf338dx5zf0stl05zgc8g08qy0asathfs8fewtx", "stake-skey": {"type": "StakeSigningKeyShelley_ed25519", "description": "Stake Signing Key", "cborHex": "582042ab191b40e5b1364beaa4b0d27fea48156d89c92ba749b738bf7891e27fbb6a"}, "drep-id": "drep1aj4dn5fwntefh3xxya56sf97p0a7sfrqapuuq3lkr4waxeelmwd", "stake-vkey": "ecaad9d12e9af29bc4c62769a824be0bfbe82460e879c047f61d5dd3", "url": "https://bit.ly/3zCH2HL", "data_hash": "1111111111111111111111111111111111111111111111111111111111111111"}], "ada_holder_wallets": [{"address": "addr_test1qrqwl94r7zhxqwq8n26p6ql9dzylmzupln8vwaake9njg6wlrxfdmq43utplzwyuaqq8q8xyjvqdul88rda02l95lm9qpauf3k", "pay-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c5b5ad023d8eb7ddc67b271d79705522b65740b9c249e205e39fa30dec775deb"}, "stake-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Stake Signing Key", "cborHex": "5820ea031c372c0617cf7137e7cfbfb821d63e61aa3277af993f84d2b4cdb9199dd6"}, "drep-id": "drep1muve9hvzk83v8ufcnn5qququcjfsphnuuudh4atuknlv5kh84lc", "stake-vkey": "df1992dd82b1e2c3f1389ce800701cc49300de7ce71b7af57cb4feca"}, {"pay-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e6b1bac201091179f8ef213b2dc0f63c72b23de45bc26a7b68eccdc718f65c83"}, "address": "addr_test1qrz8rz38rv37cx4hsgsneavsx4e84ppupwysxp6xp6mv6c9nny8znayz56vw8rfyt0gyyftg6pt5umr9njeey8fjekhqwkrrew", "stake-skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Stake Signing Key", "cborHex": "5820826d043e62e04259ffb24c24994e8c8ffd2272eda6e8a610a65977c233077b6d"}, "drep-id": "drep1kwvsu205s2nf3cudy3daqs39drg9wnnvvkwt8ysaxtx6up8cy06", "stake-vkey": "b3990e29f482a698e38d245bd0422568d0574e6c659cb3921d32cdae"}]} diff --git a/tests/govtool-frontend/.gitignore b/tests/govtool-frontend/.gitignore index 2f97b077f..250e6b539 100644 --- a/tests/govtool-frontend/.gitignore +++ b/tests/govtool-frontend/.gitignore @@ -1,4 +1,4 @@ videos node_modules screenshots -cypress/fixtures/ \ No newline at end of file +cypress/fixtures/ diff --git a/tests/govtool-frontend/README.md b/tests/govtool-frontend/README.md index 94ca1fab3..32b5389f5 100644 --- a/tests/govtool-frontend/README.md +++ b/tests/govtool-frontend/README.md @@ -8,7 +8,7 @@ GovTool frontend Integration Test yarn # run cypress tests -yarn cypress run +yarn cypress run ``` ## Run tests in dev environment diff --git a/tests/load-testing/.gitignore b/tests/load-testing/.gitignore index 9cb4fce91..5e3f2b696 100644 --- a/tests/load-testing/.gitignore +++ b/tests/load-testing/.gitignore @@ -2,4 +2,4 @@ /.mvn/wrapper/*.jar /gatling .idea -.gitlab-ci.yml \ No newline at end of file +.gitlab-ci.yml diff --git a/tests/load-testing/README.md b/tests/load-testing/README.md index a404a7f20..8bbc1cca1 100644 --- a/tests/load-testing/README.md +++ b/tests/load-testing/README.md @@ -38,4 +38,4 @@ Explain the environment variables used in the project and their purpose. - RAMP_DURATION:The duration over which the user rate gradually increases. - PEAK_USERS: The number of users to be injected during the stress peak. - STRESS_DURATION: The duration over which the stress peak occurs. -- API_URL: The URL of the API being tested. \ No newline at end of file +- API_URL: The URL of the API being tested. diff --git a/tests/test-infrastructure/.gitignore b/tests/test-infrastructure/.gitignore index d47d3a143..e433f6cb7 100644 --- a/tests/test-infrastructure/.gitignore +++ b/tests/test-infrastructure/.gitignore @@ -2,4 +2,4 @@ secrets/ configs/ docker-compose-rendered.yml docker-compose-swarm-rendered.yml -docker-compose-services-rendered.yml \ No newline at end of file +docker-compose-services-rendered.yml diff --git a/tests/test-infrastructure/README.md b/tests/test-infrastructure/README.md index 99a872d26..d91eeaa44 100644 --- a/tests/test-infrastructure/README.md +++ b/tests/test-infrastructure/README.md @@ -21,7 +21,7 @@ Services required for testing GovTool There's a helper script `deploy-swarm.sh` to load the environment variables from `.env` file and generate rendered docker compose file. ```bash cd ./test/test-infrastructire # cd into the test-infrastructure folder -docker swarm init # if swarm mode is not enabled yet. +docker swarm init # if swarm mode is not enabled yet. docker compose build # build the images docker node update xxxx --label-add govtool-test-stack=true ## set the node to be used for deploying the services ./gen-configs.sh # generate configs and secrets. @@ -67,7 +67,7 @@ password: admin #### Requires - postgres database -Metabase provides UI to show graphs and visualization from different datasource. +Metabase provides UI to show graphs and visualization from different datasource. It is used for visualizing the test metrics and the api response times over time. **Docker Image:** [metabase/metabase:v0.46.6.4](https://hub.docker.com/layers/metabase/metabase/v0.46.6.4/images/sha256-95c60db0c87c5da9cb81f6aefd0cd548fe2c14ff8c8dcba2ea58a338865cdbd9?context=explore) @@ -122,8 +122,8 @@ The results are visualized in metabase. - cardano-node's socket connection #### Used by -- Cypress integration test -- Governance Data Loader +- Cypress integration test +- Governance Data Loader Opensource API server for transaction building and querying the ledger . Kuber makes it easy to construct and submit transaction from the frontend. diff --git a/tests/test-infrastructure/configs_template/postgres_db_setup.sql b/tests/test-infrastructure/configs_template/postgres_db_setup.sql index 14d05a906..2934a2840 100644 --- a/tests/test-infrastructure/configs_template/postgres_db_setup.sql +++ b/tests/test-infrastructure/configs_template/postgres_db_setup.sql @@ -1,4 +1,4 @@ CREATE database ${STACK_NAME}_metabase; CREATE database ${STACK_NAME}_lighthouse; CREATE database ${STACK_NAME}_metrics; -CREATE database ${STACK_NAME}_sonarqube; \ No newline at end of file +CREATE database ${STACK_NAME}_sonarqube; diff --git a/tests/test-infrastructure/deploy-swarm.sh b/tests/test-infrastructure/deploy-swarm.sh index 0c94386a8..90c7fc269 100755 --- a/tests/test-infrastructure/deploy-swarm.sh +++ b/tests/test-infrastructure/deploy-swarm.sh @@ -7,7 +7,7 @@ set -eo pipefail set -a . ./.env -set +a +set +a if [ "$1" == "destroy" ] then @@ -15,7 +15,7 @@ then echo "Are you Sure? (Y/N)" read user_input if ! ( [ "$user_input" = "y" ] || [ "$user_input" = "Y" ]) - then + then exit 1 fi echo "Proceeding..." # Delete the Docker stack if "destroy" argument is provided @@ -31,7 +31,7 @@ then elif [ "$1" == "prepare" ] then - ## apply the enviroment to services compose file + ## apply the enviroment to services compose file ## and deploy the stack envsubst < ./docker-compose-services.yml > ./docker-compose-services-rendered.yml docker stack deploy -c './docker-compose-services-rendered.yml' ${STACK_NAME}-services @@ -53,5 +53,3 @@ else echo " finalize -> deploys the test infrastructure services" echo " destroy -> teardown everything except the volumes" fi - - diff --git a/tests/test-infrastructure/docker-compose.yml b/tests/test-infrastructure/docker-compose.yml index 918fba7dc..9e8f77da5 100644 --- a/tests/test-infrastructure/docker-compose.yml +++ b/tests/test-infrastructure/docker-compose.yml @@ -229,7 +229,7 @@ services: restart_policy: condition: on-failure delay: 15s - kuber: + kuber: image: dquadrant/kuber environment: CARDANO_NODE_SOCKET_PATH: /ipc/node.socket @@ -244,4 +244,3 @@ services: - node.labels.govtool-test-stack == true restart_policy: delay: "30s" - diff --git a/tests/test-infrastructure/gen-configs.sh b/tests/test-infrastructure/gen-configs.sh index b92d6d65d..19acbcc75 100755 --- a/tests/test-infrastructure/gen-configs.sh +++ b/tests/test-infrastructure/gen-configs.sh @@ -1,18 +1,18 @@ #!/bin/bash ####### Script for generating docker secret files and configs. ####### If the docker is in swarm mode, it will also generate the docker swarm secrets. -####### -if ! [ -f ./.env ] -then +####### +if ! [ -f ./.env ] +then echo ".env file is missing" exit 1 fi -set -a +set -a . ./.env set +a # Function to generate a random secret in base64 format without padding and '+' function generate_secret() { - openssl rand -base64 16 | tr -d '=+/' + openssl rand -base64 16 | tr -d '=+/' } # Generate random secrets diff --git a/tests/test-infrastructure/secrets_template/lighthouserc.json b/tests/test-infrastructure/secrets_template/lighthouserc.json index 65310cfdb..65930f8fa 100644 --- a/tests/test-infrastructure/secrets_template/lighthouserc.json +++ b/tests/test-infrastructure/secrets_template/lighthouserc.json @@ -7,4 +7,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/test-metrics-api/Dockerfile b/tests/test-metrics-api/Dockerfile index 151240e93..fd39b64e9 100644 --- a/tests/test-metrics-api/Dockerfile +++ b/tests/test-metrics-api/Dockerfile @@ -2,10 +2,10 @@ FROM node:19 as prod USER node WORKDIR /home/node COPY --chown=node:node package.json yarn.lock ./ -RUN yarn install --prod --frozen-lockfile +RUN yarn install --prod --frozen-lockfile FROM prod as builder -RUN yarn install --frozen-lockfile +RUN yarn install --frozen-lockfile COPY ./src ./src COPY ./tsconfig.json . RUN yarn build @@ -17,7 +17,6 @@ ENV PGHOST=postgres \ PGUSER=postgres \ NODE_OPTIONS="--max-old-space-size=2048" \ PORT=8080 \ - NODE_ENV=production + NODE_ENV=production EXPOSE 8080 CMD node index.js - diff --git a/tests/test-metrics-api/src/db.ts b/tests/test-metrics-api/src/db.ts index 5c11dfe3f..2ab7ff6fe 100644 --- a/tests/test-metrics-api/src/db.ts +++ b/tests/test-metrics-api/src/db.ts @@ -17,9 +17,9 @@ export async function migrate() { outcome TEXT NOT NULL, start_date BIGINT NOT NULL, end_date BIGINT NOT NULL, - build_id TEXT NOT NULL, + build_id TEXT NOT NULL, test_name TEXT NOT NULL, - commit_hash TEXT NOT NULL + commit_hash TEXT NOT NULL )` await pool.query(createTableQuery) @@ -29,11 +29,11 @@ export async function migrate() { build_id TEXT NOT NULL, method TEXT NOT NULL, endpoint TEXT NOT NULL, - path_param TEXT, + path_param TEXT, json TEXT, status_code INTEGER NOT NULL, - response_json TEXT , - response_time BIGINT NOT NULL, + response_json TEXT , + response_time BIGINT NOT NULL, start_date BIGINT NOT NULL )` await pool.query(createTableQuery)