From 3257612eb921affd966599b0dd0b3f49cf9133d3 Mon Sep 17 00:00:00 2001 From: Niraj Date: Wed, 12 Mar 2025 12:29:37 +0545 Subject: [PATCH 01/11] fix: check success alert is not visible before fill the form field --- tests/govtool-frontend/playwright/lib/forms/dRepForm.ts | 2 +- .../playwright/tests/2-delegation/delegation.drep.spec.ts | 2 ++ .../3-drep-registration/dRepRegistration.loggedin.spec.ts | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts b/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts index da80eed4d..e129edab9 100644 --- a/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts +++ b/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts @@ -205,7 +205,7 @@ export default class DRepForm { message: isPaymentAddressErrorVisible && `${dRepInfo.paymentAddress} is an invalid paymentAddress`, - }).toBeHidden(); + }).toBeHidden({ timeout: 60_000 }); await expect(this.continueBtn).toBeEnabled(); } diff --git a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.drep.spec.ts b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.drep.spec.ts index 104ad40fa..b4f6ecf4b 100644 --- a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.drep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.drep.spec.ts @@ -56,6 +56,8 @@ test("2N. Should show DRep information on details page", async ({ }, ]; + await expect(dRepPage.getByTestId("alert-success")).not.toBeVisible(); + await dRepRegistrationPage.register({ name, objectives, diff --git a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.loggedin.spec.ts index 63592c0a1..2a19d17c0 100644 --- a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.loggedin.spec.ts @@ -59,6 +59,8 @@ test.describe("Validation of dRep Registration Form", () => { const dRepRegistrationPage = new DRepRegistrationPage(page); await dRepRegistrationPage.goto(); + await expect(page.getByTestId("alert-success")).not.toBeVisible(); + for (let i = 0; i < 100; i++) { await dRepRegistrationPage.validateForm({ name: mockValid.name(), @@ -102,6 +104,7 @@ test.describe("Validation of dRep Registration Form", () => { const dRepRegistrationPage = new DRepRegistrationPage(page); await dRepRegistrationPage.goto(); + await expect(page.getByTestId("alert-success")).not.toBeVisible(); for (let i = 0; i < 100; i++) { await dRepRegistrationPage.inValidateForm({ From 0f26352ee73925e9edf2c061111835b5aa4e3e14 Mon Sep 17 00:00:00 2001 From: Niraj Date: Wed, 12 Mar 2025 12:41:58 +0545 Subject: [PATCH 02/11] fix: skip tests related to bootstrapping phase as it is no longer applicable --- .../proposalFunctionality.dRep.spec.ts | 5 +++++ .../playwright/tests/6-miscellaneous/miscellaneous.spec.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts index dd4f8e5a4..013310d04 100644 --- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -17,6 +17,7 @@ import GovernanceActionsPage from "@pages/governanceActionsPage"; import { Page, expect } from "@playwright/test"; import kuberService from "@services/kuberService"; import { BootstrapGovernanceActionType, GovernanceActionType } from "@types"; +import { allure } from "allure-playwright"; import walletManager from "lib/walletManager"; test.beforeEach(async () => { @@ -274,6 +275,10 @@ test.describe("Bootstrap phase", () => { test("5L. Should restrict dRep votes to Info Governance actions During Bootstrapping Phase", async ({ browser, }) => { + await allure.description( + "Skipping this test as bootstrapping is no longer applicable." + ); + test.skip(); const voteBlacklistOptions = Object.keys( BootstrapGovernanceActionType ).filter((option) => option !== GovernanceActionType.InfoAction); diff --git a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts index ca88fb50b..9d6dce579 100644 --- a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts +++ b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts @@ -13,6 +13,7 @@ import { test } from "@fixtures/walletExtension"; import { setAllureEpic } from "@helpers/allure"; import { isMobile, openDrawer } from "@helpers/mobile"; import { expect, Page } from "@playwright/test"; +import { allure } from "allure-playwright"; import environments from "lib/constants/environments"; test.beforeEach(async () => { @@ -106,6 +107,10 @@ test("6N. Should Warn users that they are in bootstrapping phase via banner", as page, context, }) => { + await allure.description( + "Skipping this test as bootstrapping is no longer applicable." + ); + test.skip(); await page.route("**/epoch/params", async (route) => { // Fetch the original response from the server const response = await route.fetch(); From 0c893e838a7d7ce39ef2306168a966e949890e39 Mon Sep 17 00:00:00 2001 From: Niraj Date: Wed, 12 Mar 2025 13:51:56 +0545 Subject: [PATCH 03/11] fix: update dRep directory delegation format --- .../playwright/lib/helpers/adaFormat.ts | 7 +++++++ .../delegationFunctionality.delegation.spec.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/helpers/adaFormat.ts b/tests/govtool-frontend/playwright/lib/helpers/adaFormat.ts index 5a3521511..3082c39af 100644 --- a/tests/govtool-frontend/playwright/lib/helpers/adaFormat.ts +++ b/tests/govtool-frontend/playwright/lib/helpers/adaFormat.ts @@ -29,3 +29,10 @@ export const correctDelegatedVoteAdaFormat = (ada: number | undefined) => { } return "0"; }; + +export const correctDRepDirectoryFormat = (ada: number | undefined) => { + if (ada) { + return Number(ada.toFixed(0))?.toLocaleString("en-US"); + } + return "0"; +}; diff --git a/tests/govtool-frontend/playwright/tests/2-delegation/delegationFunctionality.delegation.spec.ts b/tests/govtool-frontend/playwright/tests/2-delegation/delegationFunctionality.delegation.spec.ts index aa824c536..9f71eefa2 100644 --- a/tests/govtool-frontend/playwright/tests/2-delegation/delegationFunctionality.delegation.spec.ts +++ b/tests/govtool-frontend/playwright/tests/2-delegation/delegationFunctionality.delegation.spec.ts @@ -11,7 +11,10 @@ import { } from "@constants/staticWallets"; import { createTempDRepAuth } from "@datafactory/createAuth"; import { test } from "@fixtures/walletExtension"; -import { correctDelegatedVoteAdaFormat } from "@helpers/adaFormat"; +import { + correctDelegatedVoteAdaFormat, + correctDRepDirectoryFormat, +} from "@helpers/adaFormat"; import { setAllureEpic } from "@helpers/allure"; import { skipIfMainnet, skipIfNotHardFork } from "@helpers/cardano"; import { createNewPageWithWallet } from "@helpers/page"; @@ -265,7 +268,7 @@ test.describe("Abstain delegation", () => { await expect( page.getByText( - `You have delegated ₳${correctDelegatedVoteAdaFormat(balance)}` + `You have delegated ₳${correctDRepDirectoryFormat(balance)}` ) ).toBeVisible({ timeout: 60_000, @@ -296,7 +299,7 @@ test.describe("No confidence delegation", () => { const balance = await kuberService.getBalance(adaHolder04Wallet.address); await expect( page.getByText( - `You have delegated ₳${correctDelegatedVoteAdaFormat(balance)}` + `You have delegated ₳${correctDRepDirectoryFormat(balance)}` ) ).toBeVisible({ timeout: 60_000, @@ -325,7 +328,7 @@ test.describe("Delegated ADA visibility", () => { ); await expect( page.getByText( - `You have delegated ₳ ${correctDelegatedVoteAdaFormat(adaHolderVotingPower)}` + `You have delegated ₳ ${correctDRepDirectoryFormat(adaHolderVotingPower)}` ) ).toBeVisible({ timeout: 60_000 }); From fc836e33ad1696a75dcd9206aa0e7d51554105e1 Mon Sep 17 00:00:00 2001 From: Niraj Date: Wed, 12 Mar 2025 14:05:31 +0545 Subject: [PATCH 04/11] fix: increase timeout and assert popup modal hidden --- .../playwright/lib/pages/dRepDirectoryPage.ts | 2 +- .../playwright/tests/2-delegation/delegation.drep.spec.ts | 7 +++++-- .../3-drep-registration/dRepRegistration.dRep.spec.ts | 2 +- .../tests/3-drep-registration/editDRep.dRep.spec.ts | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/dRepDirectoryPage.ts b/tests/govtool-frontend/playwright/lib/pages/dRepDirectoryPage.ts index 6bf65cd4d..83dae3ba1 100644 --- a/tests/govtool-frontend/playwright/lib/pages/dRepDirectoryPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/dRepDirectoryPage.ts @@ -217,7 +217,7 @@ export default class DRepDirectoryPage { !isEmptyContainerVisible && `DRep with id ${dRepId} is found in the list`, }).toBeVisible({ - timeout: 20_000, + timeout: 60_000, }); } } diff --git a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.drep.spec.ts b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.drep.spec.ts index b4f6ecf4b..01bc92533 100644 --- a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.drep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.drep.spec.ts @@ -73,7 +73,7 @@ test("2N. Should show DRep information on details page", async ({ // Add an assertion to prevent clicking on "View Your dRep Details". await expect( dRepPage.getByTestId("dRep-id-display-card-dashboard") - ).toContainText(wallet.dRepId, { timeout: 20_000 }); + ).toContainText(wallet.dRepId, { timeout: 60_000 }); await dRepPage.getByTestId("view-drep-details-button").click(); // Verification @@ -81,7 +81,10 @@ test("2N. Should show DRep information on details page", async ({ wallet.dRepId ); await expect(dRepPage.getByTestId("copy-payment-address-button")).toHaveText( - paymentAddress + paymentAddress, + { + timeout: 60_000, + } ); await expect(dRepPage.getByTestId("Active-pill")).toHaveText("Active"); await expect(dRepPage.getByTestId("voting-power")).toHaveText("₳ 0"); diff --git a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts index 9b87b0669..504248997 100644 --- a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts @@ -147,7 +147,7 @@ test.describe("Temporary DReps", () => { await dRepRegistrationPage.confirmBtn.click(); await expect(dRepPage.getByTestId("d-rep-in-progress")).not.toBeVisible({ - timeout: 20_000, + timeout: 60_000, }); // connected state diff --git a/tests/govtool-frontend/playwright/tests/3-drep-registration/editDRep.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/3-drep-registration/editDRep.dRep.spec.ts index 16a622102..873755f55 100644 --- a/tests/govtool-frontend/playwright/tests/3-drep-registration/editDRep.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/3-drep-registration/editDRep.dRep.spec.ts @@ -25,7 +25,7 @@ test.describe("Validation of edit dRep Form", () => { await editDRepPage.goto(); // wait until wallet alert close - await page.waitForTimeout(5_000); + await expect(page.getByTestId("alert-success")).not.toBeVisible(); for (let i = 0; i < 100; i++) { await editDRepPage.validateForm({ @@ -73,7 +73,7 @@ test.describe("Validation of edit dRep Form", () => { const editDRepPage = new EditDRepPage(page); await editDRepPage.goto(); - await expect(editDRepPage.nameInput).toBeVisible({ timeout: 60_000 }); // assert to wait for the page to load + await expect(page.getByTestId("alert-success")).not.toBeVisible(); for (let i = 0; i < 100; i++) { await editDRepPage.inValidateForm({ From 305400a5516045b0d1f032f6ce5f0171217da0e1 Mon Sep 17 00:00:00 2001 From: Aaron Boyle Date: Wed, 12 Mar 2025 08:43:39 +0000 Subject: [PATCH 05/11] adds support for dev branch using dev-secrets to ensure preview remains unaffected --- .github/workflows/build-from-main.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-from-main.yml b/.github/workflows/build-from-main.yml index de0c3ea44..6a71f6947 100644 --- a/.github/workflows/build-from-main.yml +++ b/.github/workflows/build-from-main.yml @@ -14,7 +14,7 @@ permissions: jobs: check-build: - if: contains(fromJson('["main", "staging"]'), github.ref_name) + if: contains(fromJson('["main", "staging", "develop"]'), github.ref_name) environment: ${{ matrix.network }} strategy: fail-fast: false @@ -146,7 +146,27 @@ jobs: dockerfile: ./govtool/metadata-validation/Dockerfile image: ghcr.io/${{ github.repository }}-metadata-validation qovery_container_name: govtool-metadata-validation - + - branch: develop + network: dev-govtool + workdir: ./govtool/backend + name: govtool-backend + dockerfile: ./govtool/backend/Dockerfile.qovery + image: ghcr.io/${{ github.repository }}-backend + qovery_container_name: govtool-backend + - branch: develop + network: dev-govtool + workdir: ./govtool/frontend + name: govtool-frontend + dockerfile: ./govtool/frontend/Dockerfile.qovery + image: ghcr.io/${{ github.repository }}-frontend + qovery_container_name: govtool-frontend + - branch: develop + network: dev-govtool + workdir: ./govtool/metadata-validation + name: govtool-metadata-validation + dockerfile: ./govtool/metadata-validation/Dockerfile + image: ghcr.io/${{ github.repository }}-metadata-validation + qovery_container_name: govtool-metadata-validation runs-on: ubuntu-latest steps: From 7fc1110733ab80a7510e16673de818b1b41dea96 Mon Sep 17 00:00:00 2001 From: Niraj Date: Wed, 12 Mar 2025 15:32:55 +0545 Subject: [PATCH 06/11] fix: reload dRep page after re-voting to ensure updated state --- .../proposalFunctionality.dRep.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts index 013310d04..a90a286cd 100644 --- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -189,6 +189,9 @@ test.describe("Perform voting", () => { govActionDetailsPage = await governanceActionsPage.viewFirstVotedProposal(); await govActionDetailsPage.reVote(); + + await dRepPage.reload(); + await governanceActionsPage.votedTab.click(); const isNoVoteVisible = await govActionDetailsPage.currentPage From acd176e8de7c6562273c7d60b8b0275f641c4799 Mon Sep 17 00:00:00 2001 From: Niraj Date: Wed, 12 Mar 2025 16:55:22 +0545 Subject: [PATCH 07/11] chore: remove bootstraping test --- .../proposalFunctionality.dRep.spec.ts | 67 ------------------- .../6-miscellaneous/miscellaneous.spec.ts | 38 ----------- 2 files changed, 105 deletions(-) diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts index a90a286cd..3f7e7e838 100644 --- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -273,70 +273,3 @@ test.describe("Check voting power", () => { expect(balance, "Retirement deposit not returned").toBeGreaterThan(500); }); }); - -test.describe("Bootstrap phase", () => { - test("5L. Should restrict dRep votes to Info Governance actions During Bootstrapping Phase", async ({ - browser, - }) => { - await allure.description( - "Skipping this test as bootstrapping is no longer applicable." - ); - test.skip(); - const voteBlacklistOptions = Object.keys( - BootstrapGovernanceActionType - ).filter((option) => option !== GovernanceActionType.InfoAction); - - await Promise.all( - voteBlacklistOptions.map(async (voteBlacklistOption) => { - const dRepPage = await createNewPageWithWallet(browser, { - storageState: ".auth/dRep01.json", - wallet: dRep01Wallet, - }); - - await dRepPage.route("**/epoch/params", async (route) => { - // Fetch the original response from the server - const response = await route.fetch(); - const json = await response.json(); - - // update protocol major version - json["protocol_major"] = 9; - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify(json), - }); - }); - - const governanceActionsPage = new GovernanceActionsPage(dRepPage); - await governanceActionsPage.goto(); - - // assert to wait until proposal cards are visible - await expect(dRepPage.getByTestId("voting-power-chips")).toBeVisible({ - timeout: 60_000, - }); - // wait until the loading button is hidden - await expect(dRepPage.getByTestId("to-vote-tab")).toBeVisible({ - timeout: 60_000, - }); - - const governanceActionDetailsPage = - await governanceActionsPage.viewFirstProposalByGovernanceAction( - voteBlacklistOption as GovernanceActionType - ); - - if (governanceActionDetailsPage) { - await expect( - dRepPage.getByTestId("governance-action-details-card-header") - ).toBeVisible({ timeout: 60_000 }); - await expect( - governanceActionDetailsPage.yesVoteRadio - ).not.toBeVisible(); - await expect( - governanceActionDetailsPage.contextBtn - ).not.toBeVisible(); - await expect(governanceActionDetailsPage.voteBtn).not.toBeVisible(); - } - }) - ); - }); -}); diff --git a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts index 9d6dce579..ad97b87d1 100644 --- a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts +++ b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.spec.ts @@ -103,44 +103,6 @@ test("6M. Should navigate between footer links", async ({ page, context }) => { await expect(helpUrl).toHaveURL(HELP_DOC_URL); }); -test("6N. Should Warn users that they are in bootstrapping phase via banner", async ({ - page, - context, -}) => { - await allure.description( - "Skipping this test as bootstrapping is no longer applicable." - ); - test.skip(); - await page.route("**/epoch/params", async (route) => { - // Fetch the original response from the server - const response = await route.fetch(); - const json = await response.json(); - - // update protocol major version - json["protocol_major"] = 9; - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify(json), - }); - }); - - const responsePromise = page.waitForResponse("**/epoch/params"); - await page.goto("/"); - - await responsePromise; - - await expect(page.getByTestId("system-bootstrapping-warning")).toBeVisible({ - timeout: 60_000, - }); - - const [bootstrap] = await Promise.all([ - context.waitForEvent("page"), - page.getByTestId("system-bootstrapping-warning-link").click(), - ]); - await expect(bootstrap).toHaveURL(BOOTSTRAP_DOC_URL); -}); - test("6O. Should display proper network name", async ({ page }) => { await page.route("**/network/metrics", async (route) => { // Fetch the original response from the server From 1b551ea58981de7dc7f479d5d1b70bdbcef1f42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Thu, 13 Mar 2025 21:42:04 +0100 Subject: [PATCH 08/11] feat(#3189): exclude network information from network metrics endpoint --- CHANGELOG.md | 6 +- govtool/backend/README.md | 34 +++---- govtool/backend/app/Main.hs | 5 + govtool/backend/sql/get-network-info.sql | 6 ++ govtool/backend/sql/get-network-metrics.sql | 48 +--------- .../backend/sql/get-network-total-stake.sql | 93 +++++++++++++++++++ govtool/backend/src/VVA/API.hs | 38 ++++++-- govtool/backend/src/VVA/API/Types.hs | 62 ++++++++++--- govtool/backend/src/VVA/Network.hs | 58 ++++++++---- govtool/backend/src/VVA/Types.hs | 48 ++++++---- govtool/backend/vva-be.cabal | 2 + .../components/molecules/VotesSubmitted.tsx | 29 ++++-- .../organisms/AutomatedVotingOptions.tsx | 16 ++-- govtool/frontend/src/consts/queryKeys.ts | 2 + govtool/frontend/src/context/appContext.tsx | 28 +++--- govtool/frontend/src/hooks/queries/index.ts | 4 +- .../src/hooks/queries/useGetNetworkInfo.ts | 14 +++ .../hooks/queries/useGetNetworkTotalStake.ts | 16 ++++ govtool/frontend/src/models/api.ts | 18 ++-- govtool/frontend/src/services/AdaHandle.ts | 4 +- .../src/services/requests/getNetworkInfo.ts | 8 ++ .../services/requests/getNetworkTotalStake.ts | 8 ++ .../frontend/src/services/requests/index.ts | 8 +- govtool/frontend/src/utils/localStorage.ts | 2 + 24 files changed, 389 insertions(+), 168 deletions(-) create mode 100644 govtool/backend/sql/get-network-info.sql create mode 100644 govtool/backend/sql/get-network-total-stake.sql create mode 100644 govtool/frontend/src/hooks/queries/useGetNetworkInfo.ts create mode 100644 govtool/frontend/src/hooks/queries/useGetNetworkTotalStake.ts create mode 100644 govtool/frontend/src/services/requests/getNetworkInfo.ts create mode 100644 govtool/frontend/src/services/requests/getNetworkTotalStake.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 34361b48a..1c586129c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,14 @@ changes. ### Added -### Fixed +- Add support for preprod in matomo analytics [Issue 3173](https://github.com/IntersectMBO/govtool/issues/3173) -- hotfix for ada handle and payment address validation order [Issue 3155](https://github.com/IntersectMBO/govtool/issues/3155) +### Fixed ### Changed +- Exclude network total stake and info from network metrics [Issue 3189](https://github.com/IntersectMBO/govtool/issues/3189) + ### Removed ## [v2.0.15](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.15) 2025-03-11 diff --git a/govtool/backend/README.md b/govtool/backend/README.md index 566293184..cc5a9a285 100644 --- a/govtool/backend/README.md +++ b/govtool/backend/README.md @@ -6,9 +6,9 @@ This is a backend application of GovTool project. 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` -- PostgreSQL database +- [cardano-node](https://github.com/IntersectMBO/cardano-node) +- [cardano-db-sync](https://github.com/IntersectMBO/cardano-db-sync) +- [PostgreSQL database](https://www.postgresql.org/download/) (psql needs to be installed on your machine in order to compile the project) You will need your `cardano-node` and `cardano-db-sync` to be compatible with Sancho testnet. Until these features will be merged to the master branch the new Sancho compatible versions are available as releases on [github](https://github.com/IntersectMBO/cardano-db-sync/releases). You will also need a correct `cardano-node` version. The release notes for `cardano-db-sync` usualy specify that. @@ -30,44 +30,44 @@ Due to problems with openapi3 package it's hard to build this project with plain 2. Get [direnv](https://direnv.net/). -3. Set GHC version to 9.10.1: +3. Set GHC version to 9.2.8: ```sh - ghcup install ghc 9.10.1 + ghcup install ghc 9.2.8 - ghcup set ghc 9.10.1 + ghcup set ghc 9.2.8 ``` 4. Install cabal - ```sh - ghcup install cabal - ghcup set cabal - ``` + ```sh + ghcup install cabal + ghcup set cabal + ``` 5. Enter `govtool/backend` directory: ```sh - cd govtool/backend + cd govtool/backend ``` 6. Allow direnv to setup your environment: ```sh - direnv allow + direnv allow ``` 7. Update cabal & build project ```sh - cabal update - cabal build all + cabal update + cabal build all ``` 8. Create a config file. You can use `example-config.json` as a template. 9. Run project - `sh - cabal run vva-be -- --config start-app - ` + ```sh + cabal run vva-be -- --config start-app + ``` > [!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. diff --git a/govtool/backend/app/Main.hs b/govtool/backend/app/Main.hs index 6311288f0..66514e170 100644 --- a/govtool/backend/app/Main.hs +++ b/govtool/backend/app/Main.hs @@ -122,6 +122,8 @@ startApp vvaConfig sentryService = do dRepVotingPowerCache <- newCache dRepListCache <- newCache networkMetricsCache <- newCache + networkInfoCache <- newCache + networkTotalStakeCache <- newCache return $ CacheEnv { proposalListCache , getProposalCache @@ -133,6 +135,8 @@ startApp vvaConfig sentryService = do , dRepVotingPowerCache , dRepListCache , networkMetricsCache + , networkInfoCache + , networkTotalStakeCache } let connectionString = encodeUtf8 (dbSyncConnectionString $ getter vvaConfig) @@ -144,6 +148,7 @@ startApp vvaConfig sentryService = do exceptionHandler :: VVAConfig -> SentryService -> Maybe Request -> SomeException -> IO () exceptionHandler vvaConfig sentryService mRequest exception = do + print exception let isNotTimeoutThread x = case fromException x of Just TimeoutThread -> False _ -> True diff --git a/govtool/backend/sql/get-network-info.sql b/govtool/backend/sql/get-network-info.sql new file mode 100644 index 000000000..86953993e --- /dev/null +++ b/govtool/backend/sql/get-network-info.sql @@ -0,0 +1,6 @@ +SELECT + (SELECT MAX(no) FROM epoch) AS current_epoch, + (SELECT MAX(block_no) FROM block) AS current_block, + network_name +FROM + meta; \ No newline at end of file diff --git a/govtool/backend/sql/get-network-metrics.sql b/govtool/backend/sql/get-network-metrics.sql index 21efd2a91..4aa9c8cbb 100644 --- a/govtool/backend/sql/get-network-metrics.sql +++ b/govtool/backend/sql/get-network-metrics.sql @@ -110,23 +110,6 @@ TotalActiveCIP119CompliantDReps AS ( AND lve.epoch_no >= (SELECT epoch_no FROM ActiveDRepBoundaryEpoch) ))) ), -TotalStakeControlledByActiveDReps AS ( - SELECT - COALESCE(SUM(dd.amount), 0)::bigint AS total - FROM - drep_hash dh - LEFT JOIN DRepDistr dd ON dd.hash_id = dh.id AND dd.rn = 1 - LEFT JOIN RankedDRepRegistration rd ON dd.hash_id = rd.drep_hash_id AND rd.rn = 1 - LEFT JOIN LatestVoteEpoch lve ON lve.drep_id = dh.id - CROSS JOIN DRepActivity - WHERE - dd.epoch_no = (SELECT no FROM CurrentEpoch) - AND COALESCE(rd.deposit, 0) >= 0 - AND ((DRepActivity.epoch_no - GREATEST(COALESCE(lve.epoch_no, 0), COALESCE(rd.epoch_no, 0))) <= DRepActivity.drep_activity) -), -CurrentBlock AS ( - SELECT MAX(block_no) AS block_no FROM block -), UniqueDelegators AS ( SELECT COUNT(DISTINCT addr_id) AS count FROM delegation_vote ), @@ -139,9 +122,6 @@ TotalGovActionProposals AS ( TotalDRepVotes AS ( SELECT COUNT(*) AS count FROM voting_procedure WHERE voter_role = 'DRep' ), -TotalStakeControlledBySPOs AS ( - SELECT SUM(ps.stake)::bigint AS total FROM pool_stat ps WHERE ps.epoch_no = (SELECT no FROM CurrentEpoch) -), LatestExistingVotingAnchor AS ( SELECT subquery.drep_registration_id, @@ -192,18 +172,6 @@ TotalRegisteredDirectVoters AS ( LEFT JOIN HasNonDeregisterVotingAnchor hndva ON hndva.drep_hash_id = rdr.drep_hash_id WHERE rdr.rn = 1 AND COALESCE(rdr.deposit, 0) >= 0 AND (leva.url IS NULL OR hndva.value = true) ), -AlwaysAbstainVotingPower AS ( - SELECT COALESCE((SELECT amount FROM drep_hash - LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id - WHERE drep_hash.view = 'drep_always_abstain' - ORDER BY epoch_no DESC LIMIT 1), 0) AS amount -), -AlwaysNoConfidenceVotingPower AS ( - SELECT COALESCE((SELECT amount FROM drep_hash - LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id - WHERE drep_hash.view = 'drep_always_no_confidence' - ORDER BY epoch_no DESC LIMIT 1), 0) AS amount -), TotalDRepDistr AS ( SELECT SUM(COALESCE(amount, 0))::bigint total_drep_distr FROM drep_distr where epoch_no = (SELECT no from CurrentEpoch) ), @@ -223,42 +191,28 @@ CommitteeThreshold AS ( OR (c.gov_action_proposal_id IS NULL) ) SELECT - CurrentEpoch.no AS epoch_no, - CurrentBlock.block_no, UniqueDelegators.count AS unique_delegators, TotalDelegations.count AS total_delegations, TotalGovActionProposals.count AS total_gov_action_proposals, TotalDRepVotes.count AS total_drep_votes, TotalRegisteredDReps.unique_registrations AS total_registered_dreps, TotalDRepDistr.total_drep_distr, - COALESCE(TotalStakeControlledByActiveDReps.total, 0) + COALESCE(AlwaysNoConfidenceVotingPower.amount, 0) AS total_stake_controlled_by_active_dreps, - COALESCE(TotalStakeControlledBySPOs.total, 0) AS total_stake_controlled_by_spos, TotalActiveDReps.unique_active_drep_registrations AS total_active_dreps, TotalInactiveDReps.total_inactive_dreps AS total_inactive_dreps, TotalActiveCIP119CompliantDReps.unique_active_cip119_compliant_drep_registrations AS total_active_cip119_compliant_dreps, TotalRegisteredDirectVoters.unique_direct_voters AS total_registered_direct_voters, - AlwaysAbstainVotingPower.amount AS always_abstain_voting_power, - AlwaysNoConfidenceVotingPower.amount AS always_no_confidence_voting_power, - meta.network_name, NoOfCommitteeMembers.total no_of_committee_members, CommitteeThreshold.quorum_numerator, CommitteeThreshold.quorum_denominator -FROM CurrentEpoch -CROSS JOIN CurrentBlock -CROSS JOIN UniqueDelegators +FROM UniqueDelegators CROSS JOIN TotalDRepDistr CROSS JOIN TotalDelegations CROSS JOIN TotalGovActionProposals CROSS JOIN TotalDRepVotes CROSS JOIN TotalRegisteredDReps -CROSS JOIN TotalStakeControlledByActiveDReps -CROSS JOIN TotalStakeControlledBySPOs CROSS JOIN TotalActiveDReps CROSS JOIN TotalInactiveDReps CROSS JOIN TotalActiveCIP119CompliantDReps CROSS JOIN TotalRegisteredDirectVoters -CROSS JOIN AlwaysAbstainVotingPower -CROSS JOIN AlwaysNoConfidenceVotingPower CROSS JOIN NoOfCommitteeMembers CROSS JOIN CommitteeThreshold -CROSS JOIN meta; diff --git a/govtool/backend/sql/get-network-total-stake.sql b/govtool/backend/sql/get-network-total-stake.sql new file mode 100644 index 000000000..9399df5c3 --- /dev/null +++ b/govtool/backend/sql/get-network-total-stake.sql @@ -0,0 +1,93 @@ +WITH DRepActivity AS ( + SELECT + drep_activity AS drep_activity, + epoch_no AS epoch_no + FROM + epoch_param + WHERE + epoch_no IS NOT NULL + ORDER BY + epoch_no DESC + LIMIT 1 +), +LatestVotingProcedure AS ( + SELECT + vp.*, + ROW_NUMBER() OVER (PARTITION BY drep_voter ORDER BY tx_id DESC) AS rn + FROM + voting_procedure vp +), +DRepDistr AS ( + SELECT + drep_distr.*, + ROW_NUMBER() OVER (PARTITION BY drep_hash.id ORDER BY drep_distr.epoch_no DESC) AS rn + FROM + drep_distr + JOIN drep_hash ON drep_hash.id = drep_distr.hash_id +), +CurrentEpoch AS ( + SELECT MAX(no) AS no FROM epoch +), +LatestVoteEpoch AS ( + SELECT + block.epoch_no, + lvp.drep_voter AS drep_id + FROM + LatestVotingProcedure lvp + JOIN tx ON tx.id = lvp.tx_id + JOIN block ON block.id = tx.block_id + WHERE + lvp.rn = 1 +), +RankedDRepRegistration AS ( + SELECT + dr.id, + dr.drep_hash_id, + dr.deposit, + dr.voting_anchor_id, + ROW_NUMBER() OVER (PARTITION BY dr.drep_hash_id ORDER BY dr.tx_id DESC) AS rn, + encode(tx.hash, 'hex') AS tx_hash, + block.epoch_no + FROM + drep_registration dr + JOIN tx ON tx.id = dr.tx_id + JOIN block ON block.id = tx.block_id +), +TotalStakeControlledByActiveDReps AS ( + SELECT + COALESCE(SUM(dd.amount), 0)::bigint AS total + FROM + drep_hash dh + LEFT JOIN DRepDistr dd ON dd.hash_id = dh.id AND dd.rn = 1 + LEFT JOIN RankedDRepRegistration rd ON dd.hash_id = rd.drep_hash_id AND rd.rn = 1 + LEFT JOIN LatestVoteEpoch lve ON lve.drep_id = dh.id + CROSS JOIN DRepActivity + WHERE + dd.epoch_no = (SELECT no FROM CurrentEpoch) + AND COALESCE(rd.deposit, 0) >= 0 + AND ((DRepActivity.epoch_no - GREATEST(COALESCE(lve.epoch_no, 0), COALESCE(rd.epoch_no, 0))) <= DRepActivity.drep_activity) +), +TotalStakeControlledBySPOs AS ( + SELECT SUM(ps.stake)::bigint AS total FROM pool_stat ps WHERE ps.epoch_no = (SELECT no FROM CurrentEpoch) +), +AlwaysAbstainVotingPower AS ( + SELECT COALESCE((SELECT amount FROM drep_hash + LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id + WHERE drep_hash.view = 'drep_always_abstain' + ORDER BY epoch_no DESC LIMIT 1), 0) AS amount +), +AlwaysNoConfidenceVotingPower AS ( + SELECT COALESCE((SELECT amount FROM drep_hash + LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id + WHERE drep_hash.view = 'drep_always_no_confidence' + ORDER BY epoch_no DESC LIMIT 1), 0) AS amount +) +SELECT + COALESCE(TotalStakeControlledByActiveDReps.total, 0) + COALESCE(AlwaysNoConfidenceVotingPower.amount, 0) AS total_stake_controlled_by_active_dreps, + COALESCE(TotalStakeControlledBySPOs.total, 0) AS total_stake_controlled_by_spos, + AlwaysAbstainVotingPower.amount AS always_abstain_voting_power, + AlwaysNoConfidenceVotingPower.amount AS always_no_confidence_voting_power +FROM TotalStakeControlledByActiveDReps +LEFT JOIN TotalStakeControlledBySPOs ON TRUE +LEFT JOIN AlwaysAbstainVotingPower ON TRUE +LEFT JOIN AlwaysNoConfidenceVotingPower ON TRUE \ No newline at end of file diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index 0e553cfaf..5bfbd304d 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -79,6 +79,9 @@ type VVAApi = :<|> "transaction" :> "status" :> Capture "transactionId" HexText :> Get '[JSON] GetTransactionStatusResponse :<|> "throw500" :> Get '[JSON] () :<|> "network" :> "metrics" :> Get '[JSON] GetNetworkMetricsResponse + :<|> "network" :> "info" :> Get '[JSON] GetNetworkInfoResponse + :<|> "network" :> "total-stake" :> Get '[JSON] GetNetworkTotalStakeResponse + server :: App m => ServerT VVAApi m server = drepList :<|> getVotingPower @@ -92,7 +95,8 @@ server = drepList :<|> getTransactionStatus :<|> throw500 :<|> getNetworkMetrics - + :<|> getNetworkInfo + :<|> getNetworkTotalStake mapDRepType :: Types.DRepType -> DRepType mapDRepType Types.DRep = NormalDRep @@ -428,29 +432,43 @@ getTransactionStatus (unHexText -> transactionId) = do throw500 :: App m => m () throw500 = throwError $ CriticalError "intentional system break for testing purposes" +getNetworkInfo :: App m => m GetNetworkInfoResponse +getNetworkInfo = do + CacheEnv {networkInfoCache} <- asks vvaCache + Types.NetworkInfo {..} <- Network.networkInfo + return $ GetNetworkInfoResponse + { getNetworkInfoResponseCurrentTime = networkInfoCurrentTime + , getNetworkInfoResponseEpochNo = networkInfoEpochNo + , getNetworkInfoResponseBlockNo = networkInfoBlockNo + , getNetworkInfoResponseNetworkName = networkInfoNetworkName + } + +getNetworkTotalStake :: App m => m GetNetworkTotalStakeResponse +getNetworkTotalStake = do + CacheEnv {networkTotalStakeCache} <- asks vvaCache + Types.NetworkTotalStake {..} <- Network.networkTotalStake + return $ GetNetworkTotalStakeResponse + { getNetworkTotalStakeResponseTotalStakeControlledByDReps = networkTotalStakeControlledByDReps + , getNetworkTotalStakeResponseTotalStakeControlledBySPOs = networkTotalStakeControlledBySPOs + , getNetworkTotalStakeResponseAlwaysAbstainVotingPower = networkTotalAlwaysAbstainVotingPower + , getNetworkTotalStakeResponseAlwaysNoConfidenceVotingPower = networkTotalAlwaysNoConfidenceVotingPower + } + getNetworkMetrics :: App m => m GetNetworkMetricsResponse getNetworkMetrics = do CacheEnv {networkMetricsCache} <- asks vvaCache Types.NetworkMetrics {..} <- Network.networkMetrics return $ GetNetworkMetricsResponse - { getNetworkMetricsResponseCurrentTime = networkMetricsCurrentTime - , getNetworkMetricsResponseCurrentEpoch = networkMetricsCurrentEpoch - , getNetworkMetricsResponseCurrentBlock = networkMetricsCurrentBlock - , getNetworkMetricsResponseUniqueDelegators = networkMetricsUniqueDelegators + { getNetworkMetricsResponseUniqueDelegators = networkMetricsUniqueDelegators , getNetworkMetricsResponseTotalDelegations = networkMetricsTotalDelegations , getNetworkMetricsResponseTotalGovernanceActions = networkMetricsTotalGovernanceActions , getNetworkMetricsResponseTotalDRepVotes = networkMetricsTotalDRepVotes , getNetworkMetricsResponseTotalRegisteredDReps = networkMetricsTotalRegisteredDReps , getNetworkMetricsResponseTotalDRepDistr = networkMetricsTotalDRepDistr - , getNetworkMetricsResponseTotalStakeControlledByDReps = networkMetricsTotalStakeControlledByDReps - , getNetworkMetricsResponseTotalStakeControlledBySPOs = networkMetricsTotalStakeControlledBySPOs , getNetworkMetricsResponseTotalActiveDReps = networkMetricsTotalActiveDReps , getNetworkMetricsResponseTotalInactiveDReps = networkMetricsTotalInactiveDReps , getNetworkMetricsResponseTotalActiveCIP119CompliantDReps = networkMetricsTotalActiveCIP119CompliantDReps , getNetworkMetricsResponseTotalRegisteredDirectVoters = networkMetricsTotalRegisteredDirectVoters - , getNetworkMetricsResponseAlwaysAbstainVotingPower = networkMetricsAlwaysAbstainVotingPower - , getNetworkMetricsResponseAlwaysNoConfidenceVotingPower = networkMetricsAlwaysNoConfidenceVotingPower - , getNetworkMetricsResponseNetworkName = networkMetricsNetworkName , getNetworkMetricsResponseNoOfCommitteeMembers = networkMetricsNoOfCommitteeMembers , getNetworkMetricsResponseQuorumNumerator = networkMetricsQuorumNumerator , getNetworkMetricsResponseQuorumDenominator = networkMetricsQuorumDenominator diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index 461ca5dfe..ec95925e9 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -874,26 +874,66 @@ instance ToSchema DelegationResponse where & example ?~ toJSON exampleDelegationResponse +data GetNetworkInfoResponse + = GetNetworkInfoResponse + { getNetworkInfoResponseCurrentTime :: UTCTime + , getNetworkInfoResponseEpochNo :: Integer + , getNetworkInfoResponseBlockNo :: Integer + , getNetworkInfoResponseNetworkName :: Text + } + +deriveJSON (jsonOptions "getNetworkInfoResponse") ''GetNetworkInfoResponse + +exampleGetNetworkInfoResponse :: Text +exampleGetNetworkInfoResponse = + "{\"currentTime\": \"1970-01-01T00:00:00Z\"," + <> "\"currentEpoch\": 0," + <> "\"currentBlock\": 0," + <> "\"networkName\": \"Mainnet\"}" + +instance ToSchema GetNetworkInfoResponse where + declareNamedSchema _ = pure $ NamedSchema (Just "GetNetworkInfoResponse") $ mempty + & type_ ?~ OpenApiObject + & description ?~ "GetNetworkInfoResponse" + & example + ?~ toJSON exampleGetNetworkInfoResponse + +data GetNetworkTotalStakeResponse + = GetNetworkTotalStakeResponse + { getNetworkTotalStakeResponseTotalStakeControlledByDReps :: Integer + , getNetworkTotalStakeResponseTotalStakeControlledBySPOs :: Integer + , getNetworkTotalStakeResponseAlwaysAbstainVotingPower :: Integer + , getNetworkTotalStakeResponseAlwaysNoConfidenceVotingPower :: Integer + } + +deriveJSON (jsonOptions "getNetworkTotalStakeResponse") ''GetNetworkTotalStakeResponse + +exampleGetNetworkTotalStakeResponse :: Text +exampleGetNetworkTotalStakeResponse = + "{\"totalStakeControlledByDReps\": 0," + <> "\"totalStakeControlledBySPOs\": 0," + <> "\"alwaysAbstainVotingPower\": 0," + <> "\"alwaysNoConfidenceVotingPower\": 0}" + +instance ToSchema GetNetworkTotalStakeResponse where + declareNamedSchema _ = pure $ NamedSchema (Just "GetNetworkTotalStakeResponse") $ mempty + & type_ ?~ OpenApiObject + & description ?~ "GetNetworkTotalStakeResponse" + & example + ?~ toJSON exampleGetNetworkTotalStakeResponse + data GetNetworkMetricsResponse = GetNetworkMetricsResponse - { getNetworkMetricsResponseCurrentTime :: UTCTime - , getNetworkMetricsResponseCurrentEpoch :: Integer - , getNetworkMetricsResponseCurrentBlock :: Integer - , getNetworkMetricsResponseUniqueDelegators :: Integer + { getNetworkMetricsResponseUniqueDelegators :: Integer , getNetworkMetricsResponseTotalDelegations :: Integer , getNetworkMetricsResponseTotalGovernanceActions :: Integer , getNetworkMetricsResponseTotalDRepVotes :: Integer , getNetworkMetricsResponseTotalRegisteredDReps :: Integer , getNetworkMetricsResponseTotalDRepDistr :: Integer - , getNetworkMetricsResponseTotalStakeControlledByDReps :: Integer - , getNetworkMetricsResponseTotalStakeControlledBySPOs :: Integer , getNetworkMetricsResponseTotalActiveDReps :: Integer , getNetworkMetricsResponseTotalInactiveDReps :: Integer , getNetworkMetricsResponseTotalActiveCIP119CompliantDReps :: Integer , getNetworkMetricsResponseTotalRegisteredDirectVoters :: Integer - , getNetworkMetricsResponseAlwaysAbstainVotingPower :: Integer - , getNetworkMetricsResponseAlwaysNoConfidenceVotingPower :: Integer - , getNetworkMetricsResponseNetworkName :: Text , getNetworkMetricsResponseNoOfCommitteeMembers :: Integer , getNetworkMetricsResponseQuorumNumerator :: Integer , getNetworkMetricsResponseQuorumDenominator :: Integer @@ -912,14 +952,10 @@ exampleGetNetworkMetricsResponse = <> "\"totalDRepVotes\": 0," <> "\"totalRegisteredDReps\": 0," <> "\"totalDRepDistr\": 0," - <> "\"totalStakeControlledByDReps\": 0," - <> "\"totalStakeControlledBySPOs\": 0," <> "\"totalActiveDReps\": 0," <> "\"totalInactiveDReps\": 0," <> "\"totalActiveCIP119CompliantDReps\": 0," <> "\"totalRegisteredDirectVoters\": 0," - <> "\"alwaysAbstainVotingPower\": 0," - <> "\"alwaysNoConfidenceVotingPower\": 0," <> "\"networkName\": \"Mainnet\"," <> "\"noOfCommitteeMembers\": 7," <> "\"quorumNumerator\": 2," diff --git a/govtool/backend/src/VVA/Network.hs b/govtool/backend/src/VVA/Network.hs index 550c354c2..797d47ac3 100644 --- a/govtool/backend/src/VVA/Network.hs +++ b/govtool/backend/src/VVA/Network.hs @@ -25,6 +25,46 @@ import VVA.Types sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs +networkInfoSql :: SQL.Query +networkInfoSql = sqlFrom $(embedFile "sql/get-network-info.sql") + +networkInfo :: + (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadError AppError m) => + m NetworkInfo +networkInfo = withPool $ \conn -> do + result <- liftIO $ SQL.query_ conn networkInfoSql + current_time <- liftIO getCurrentTime + case result of + [( network_epoch + , network_block_no + , network_name + )] -> return $ NetworkInfo + current_time + network_epoch + network_block_no + network_name + _ -> throwError $ CriticalError "Could not query the network info. This should never happen." + +networkTotalStakeSql :: SQL.Query +networkTotalStakeSql = sqlFrom $(embedFile "sql/get-network-total-stake.sql") + +networkTotalStake :: + (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadError AppError m) => + m NetworkTotalStake +networkTotalStake = withPool $ \conn -> do + result <- liftIO $ SQL.query_ conn networkTotalStakeSql + case result of + [( total_stake_controlled_by_dreps + , total_stake_controlled_by_spos + , always_abstain_voting_power + , always_no_confidence_voting_power + )] -> return $ NetworkTotalStake + total_stake_controlled_by_dreps + total_stake_controlled_by_spos + always_abstain_voting_power + always_no_confidence_voting_power + _ -> throwError $ CriticalError "Could not query the network total stake. This should never happen." + networkMetricsSql :: SQL.Query networkMetricsSql = sqlFrom $(embedFile "sql/get-network-metrics.sql") @@ -33,47 +73,31 @@ networkMetrics :: m NetworkMetrics networkMetrics = withPool $ \conn -> do result <- liftIO $ SQL.query_ conn networkMetricsSql - current_time <- liftIO getCurrentTime case result of - [( epoch_no - , block_no - , unique_delegators + [( unique_delegators , total_delegations , total_gov_action_proposals , total_drep_votes , total_registered_dreps , total_drep_distr - , total_stake_controlled_by_dreps - , total_stake_controlled_by_spos , total_active_dreps , total_inactive_dreps , total_active_cip119_compliant_dreps , total_registered_direct_voters - , always_abstain_voting_power - , always_no_confidence_voting_power - , network_name , no_of_committee_members , quorum_numerator , quorum_denominator )] -> return $ NetworkMetrics - current_time - epoch_no - block_no unique_delegators total_delegations total_gov_action_proposals total_drep_votes total_registered_dreps total_drep_distr - total_stake_controlled_by_dreps - total_stake_controlled_by_spos total_active_dreps total_inactive_dreps total_active_cip119_compliant_dreps total_registered_direct_voters - always_abstain_voting_power - always_no_confidence_voting_power - network_name no_of_committee_members quorum_numerator quorum_denominator diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index 0e69988d6..5d1f3ca5f 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -203,38 +203,48 @@ instance ToJSON TransactionStatus where 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 Delegation) - , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) - , dRepInfoCache :: Cache.Cache Text DRepInfo - , dRepVotingPowerCache :: Cache.Cache Text Integer - , dRepListCache :: Cache.Cache Text [DRepRegistration] - , networkMetricsCache :: Cache.Cache () NetworkMetrics + { 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 Delegation) + , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) + , dRepInfoCache :: Cache.Cache Text DRepInfo + , dRepVotingPowerCache :: Cache.Cache Text Integer + , dRepListCache :: Cache.Cache Text [DRepRegistration] + , networkMetricsCache :: Cache.Cache () NetworkMetrics + , networkInfoCache :: Cache.Cache () NetworkInfo + , networkTotalStakeCache :: Cache.Cache () NetworkTotalStake + } + +data NetworkInfo + = NetworkInfo + { networkInfoCurrentTime :: UTCTime + , networkInfoEpochNo :: Integer + , networkInfoBlockNo :: Integer + , networkInfoNetworkName :: Text + } + +data NetworkTotalStake + = NetworkTotalStake + { networkTotalStakeControlledByDReps :: Integer + , networkTotalStakeControlledBySPOs :: Integer + , networkTotalAlwaysAbstainVotingPower :: Integer + , networkTotalAlwaysNoConfidenceVotingPower :: Integer } data NetworkMetrics = NetworkMetrics - { networkMetricsCurrentTime :: UTCTime - , networkMetricsCurrentEpoch :: Integer - , networkMetricsCurrentBlock :: Integer - , networkMetricsUniqueDelegators :: Integer + { networkMetricsUniqueDelegators :: Integer , networkMetricsTotalDelegations :: Integer , networkMetricsTotalGovernanceActions :: Integer , networkMetricsTotalDRepVotes :: Integer , networkMetricsTotalRegisteredDReps :: Integer , networkMetricsTotalDRepDistr :: Integer - , networkMetricsTotalStakeControlledByDReps :: Integer - , networkMetricsTotalStakeControlledBySPOs :: Integer , networkMetricsTotalActiveDReps :: Integer , networkMetricsTotalInactiveDReps :: Integer , networkMetricsTotalActiveCIP119CompliantDReps :: Integer , networkMetricsTotalRegisteredDirectVoters :: Integer - , networkMetricsAlwaysAbstainVotingPower :: Integer - , networkMetricsAlwaysNoConfidenceVotingPower :: Integer - , networkMetricsNetworkName :: Text , networkMetricsNoOfCommitteeMembers :: Integer , networkMetricsQuorumNumerator :: Integer , networkMetricsQuorumDenominator :: Integer diff --git a/govtool/backend/vva-be.cabal b/govtool/backend/vva-be.cabal index a3e807dc6..3ad2bd4e5 100644 --- a/govtool/backend/vva-be.cabal +++ b/govtool/backend/vva-be.cabal @@ -32,6 +32,8 @@ extra-source-files: sql/get-transaction-status.sql sql/get-stake-key-voting-power.sql sql/get-network-metrics.sql + sql/get-network-info.sql + sql/get-network-total-stake.sql executable vva-be main-is: Main.hs diff --git a/govtool/frontend/src/components/molecules/VotesSubmitted.tsx b/govtool/frontend/src/components/molecules/VotesSubmitted.tsx index c04f5aa60..87f3ab8a0 100644 --- a/govtool/frontend/src/components/molecules/VotesSubmitted.tsx +++ b/govtool/frontend/src/components/molecules/VotesSubmitted.tsx @@ -1,9 +1,13 @@ -import { useCallback } from "react"; +import { useCallback, useEffect } from "react"; import { Box } from "@mui/material"; import { IMAGES, SECURITY_RELEVANT_PARAMS_MAP } from "@consts"; import { Typography, VotePill } from "@atoms"; -import { useTranslation } from "@hooks"; +import { + useGetNetworkMetrics, + useGetNetworkTotalStake, + useTranslation, +} from "@hooks"; import { getGovActionVotingThresholdKey, correctAdaFormatWithSuffix, @@ -48,7 +52,18 @@ export const VotesSubmitted = ({ areCCVoteTotalsDisplayed, } = useFeatureFlag(); const { t } = useTranslation(); - const { epochParams, networkMetrics } = useAppContext(); + const { networkTotalStake, fetchNetworkTotalStake } = + useGetNetworkTotalStake(); + const { networkMetrics, fetchNetworkMetrics } = useGetNetworkMetrics(); + const { epochParams } = useAppContext(); + + useEffect(() => { + const init = async () => { + await fetchNetworkTotalStake(); + await fetchNetworkMetrics(); + }; + init(); + }, []); const noOfCommitteeMembers = networkMetrics?.noOfCommitteeMembers ?? 0; const ccThreshold = ( @@ -60,13 +75,13 @@ export const VotesSubmitted = ({ // Coming from be // Equal to: total active drep stake + auto no-confidence stake const totalStakeControlledByDReps = - (networkMetrics?.totalStakeControlledByDReps ?? 0) - + (networkTotalStake?.totalStakeControlledByDReps ?? 0) - // As this being voted for the action becomes part of the total active stake dRepAbstainVotes; // Governance action abstain votesa + auto abstain votes const totalAbstainVotes = - dRepAbstainVotes + (networkMetrics?.alwaysAbstainVotingPower ?? 0); + dRepAbstainVotes + (networkTotalStake?.alwaysAbstainVotingPower ?? 0); // TODO: Move this logic to backend const dRepYesVotesPercentage = totalStakeControlledByDReps @@ -82,13 +97,13 @@ export const VotesSubmitted = ({ (dRepYesVotes - // As this is already added on backend (govActionType === GovernanceActionType.NoConfidence - ? networkMetrics?.alwaysNoConfidenceVotingPower ?? 0 + ? networkTotalStake?.alwaysNoConfidenceVotingPower ?? 0 : 0)) - (dRepNoVotes - // As this is already added on backend (govActionType === GovernanceActionType.NoConfidence ? 0 - : networkMetrics?.alwaysNoConfidenceVotingPower ?? 0)) + : networkTotalStake?.alwaysNoConfidenceVotingPower ?? 0)) : undefined; const dRepNotVotedVotesPercentage = 100 - (dRepYesVotesPercentage ?? 0) - (dRepNoVotesPercentage ?? 0); diff --git a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx index 411d8d2d3..bbcf7d5ca 100644 --- a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx +++ b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx @@ -9,7 +9,7 @@ import { import { Typography } from "@atoms"; import { ICONS } from "@consts"; import { PendingTransaction } from "@context"; -import { useGetNetworkMetrics, useTranslation } from "@hooks"; +import { useGetNetworkTotalStake, useTranslation } from "@hooks"; import { AutomatedVotingCard } from "@molecules"; import { correctDRepDirectoryFormat, openInNewTab } from "@/utils"; import { @@ -40,8 +40,8 @@ export const AutomatedVotingOptions = ({ txHash, }: AutomatedVotingOptionsProps) => { const { t } = useTranslation(); - // TODO: Get network metrics from useAppContext - const { networkMetrics, fetchNetworkMetrics } = useGetNetworkMetrics(); + const { networkTotalStake, fetchNetworkTotalStake } = + useGetNetworkTotalStake(); const [isOpen, setIsOpen] = useState(false); @@ -57,7 +57,7 @@ export const AutomatedVotingOptions = ({ delegationInProgress === AutomatedVotingOptionDelegationId.no_confidence; useEffect(() => { - fetchNetworkMetrics(); + fetchNetworkTotalStake(); }, []); useEffect(() => { @@ -128,9 +128,9 @@ export const AutomatedVotingOptions = ({ : t("dRepDirectory.abstainCardDefaultTitle") } votingPower={ - networkMetrics + networkTotalStake ? correctDRepDirectoryFormat( - networkMetrics?.alwaysAbstainVotingPower, + networkTotalStake?.alwaysAbstainVotingPower, ) : "" } @@ -171,9 +171,9 @@ export const AutomatedVotingOptions = ({ : t("dRepDirectory.noConfidenceDefaultTitle") } votingPower={ - networkMetrics + networkTotalStake ? correctDRepDirectoryFormat( - networkMetrics?.alwaysNoConfidenceVotingPower, + networkTotalStake?.alwaysNoConfidenceVotingPower, ) : "" } diff --git a/govtool/frontend/src/consts/queryKeys.ts b/govtool/frontend/src/consts/queryKeys.ts index c6cfa2cec..825b5f316 100644 --- a/govtool/frontend/src/consts/queryKeys.ts +++ b/govtool/frontend/src/consts/queryKeys.ts @@ -7,6 +7,8 @@ export const QUERY_KEYS = { useGetDRepVotingPowerKey: "useGetDRepVotingPowerKey", useGetEpochParamsKey: "useGetEpochParamsKey", useGetNetworkMetricsKey: "useGetNetworkMetricsKey", + useGetNetworkTotalStakeKey: "useGetNetworkTotalStakeKey", + useGetNetworkInfoKey: "useGetNetworkInfoKey", useGetProposalKey: "useGetProposalKey", useGetProposalsInfiniteKey: "useGetProposalsInfiniteKey", useGetProposalsKey: "useGetProposalsKey", diff --git a/govtool/frontend/src/context/appContext.tsx b/govtool/frontend/src/context/appContext.tsx index 14c18c9ea..dff1684f1 100644 --- a/govtool/frontend/src/context/appContext.tsx +++ b/govtool/frontend/src/context/appContext.tsx @@ -9,13 +9,13 @@ import { import * as Sentry from "@sentry/react"; import { NETWORK_NAMES, CEXPLORER_BASE_URLS } from "@/consts"; -import { useGetNetworkMetrics, useGetEpochParams } from "@/hooks"; +import { useGetEpochParams, useGetNetworkInfo } from "@/hooks"; import { - NETWORK_METRICS_KEY, + NETWORK_INFO_KEY, PROTOCOL_PARAMS_KEY, setItemToLocalStorage, } from "@/utils"; -import { EpochParams, NetworkMetrics, Network } from "@/models"; +import { EpochParams, Network } from "@/models"; import { adaHandleService } from "@/services/AdaHandle"; const BOOTSTRAPPING_PHASE_MAJOR = 9; @@ -29,7 +29,6 @@ type AppContextType = { network: Network; cExplorerBaseUrl: string; epochParams?: EpochParams; - networkMetrics?: NetworkMetrics; }; const AppContext = createContext(null); @@ -44,7 +43,7 @@ const AppContextProvider = ({ children }: PropsWithChildren) => { Sentry.setTag("component_name", "AppContextProvider"); }, []); const { fetchEpochParams, epochParams } = useGetEpochParams(); - const { fetchNetworkMetrics, networkMetrics } = useGetNetworkMetrics(); + const { fetchNetworkInfo, networkInfo } = useGetNetworkInfo(); const [isAppInitializing, setIsAppInitializing] = useState(true); @@ -56,12 +55,12 @@ const AppContextProvider = ({ children }: PropsWithChildren) => { setItemToLocalStorage(PROTOCOL_PARAMS_KEY, epochParamsData); } - const { data: networkMetricsData } = await fetchNetworkMetrics(); - if (networkMetricsData) { - setItemToLocalStorage(NETWORK_METRICS_KEY, networkMetricsData); + const { data: networkInfoData } = await fetchNetworkInfo(); + if (networkInfoData) { + setItemToLocalStorage(NETWORK_INFO_KEY, networkInfoData); // Initialize ada handle service - adaHandleService.initialize(networkMetricsData.networkName); + adaHandleService.initialize(networkInfoData?.networkName); } setIsAppInitializing(false); @@ -76,23 +75,20 @@ const AppContextProvider = ({ children }: PropsWithChildren) => { const value = useMemo( () => ({ isAppInitializing, - isMainnet: networkMetrics?.networkName === "mainnet", + isMainnet: networkInfo?.networkName === "mainnet", isInBootstrapPhase: epochParams?.protocol_major === BOOTSTRAPPING_PHASE_MAJOR, isFullGovernance: Number(epochParams?.protocol_major) >= 10, networkName: NETWORK_NAMES[ - (networkMetrics?.networkName as keyof typeof NETWORK_NAMES) || - "preview" + (networkInfo?.networkName as keyof typeof NETWORK_NAMES) || "preview" ], - network: networkMetrics?.networkName ?? Network.preview, + network: networkInfo?.networkName ?? Network.preview, cExplorerBaseUrl: CEXPLORER_BASE_URLS[ - (networkMetrics?.networkName as keyof typeof NETWORK_NAMES) || - "preview" + (networkInfo?.networkName as keyof typeof NETWORK_NAMES) || "preview" ], epochParams, - networkMetrics, }), [isAppInitializing], ); diff --git a/govtool/frontend/src/hooks/queries/index.ts b/govtool/frontend/src/hooks/queries/index.ts index aa02b7321..db72778d2 100644 --- a/govtool/frontend/src/hooks/queries/index.ts +++ b/govtool/frontend/src/hooks/queries/index.ts @@ -1,11 +1,13 @@ export * from "./useGetAdaHolderCurrentDelegationQuery"; export * from "./useGetAdaHolderVotingPowerQuery"; -export * from "./useGetDrepDetailsQuery"; export * from "./useGetDRepListQuery"; export * from "./useGetDRepVotesQuery"; export * from "./useGetDRepVotingPowerQuery"; +export * from "./useGetDrepDetailsQuery"; export * from "./useGetEpochParams"; +export * from "./useGetNetworkInfo"; export * from "./useGetNetworkMetrics"; +export * from "./useGetNetworkTotalStake"; export * from "./useGetProposalQuery"; export * from "./useGetProposalsInfiniteQuery"; export * from "./useGetProposalsQuery"; diff --git a/govtool/frontend/src/hooks/queries/useGetNetworkInfo.ts b/govtool/frontend/src/hooks/queries/useGetNetworkInfo.ts new file mode 100644 index 000000000..b0e0d940a --- /dev/null +++ b/govtool/frontend/src/hooks/queries/useGetNetworkInfo.ts @@ -0,0 +1,14 @@ +import { useQuery } from "react-query"; + +import { getNetworkInfo } from "@services"; +import { QUERY_KEYS } from "@consts"; + +export const useGetNetworkInfo = () => { + const { data: networkInfo, refetch: fetchNetworkInfo } = useQuery({ + queryKey: QUERY_KEYS.useGetNetworkInfoKey, + queryFn: () => getNetworkInfo(), + enabled: false, + }); + + return { networkInfo, fetchNetworkInfo }; +}; diff --git a/govtool/frontend/src/hooks/queries/useGetNetworkTotalStake.ts b/govtool/frontend/src/hooks/queries/useGetNetworkTotalStake.ts new file mode 100644 index 000000000..c6b3df59e --- /dev/null +++ b/govtool/frontend/src/hooks/queries/useGetNetworkTotalStake.ts @@ -0,0 +1,16 @@ +import { useQuery } from "react-query"; + +import { getNetworkTotalStake } from "@services"; +import { QUERY_KEYS } from "@consts"; + +export const useGetNetworkTotalStake = () => { + const { data: networkTotalStake, refetch: fetchNetworkTotalStake } = useQuery( + { + queryKey: QUERY_KEYS.useGetNetworkTotalStakeKey, + queryFn: () => getNetworkTotalStake(), + enabled: false, + }, + ); + + return { networkTotalStake, fetchNetworkTotalStake }; +}; diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 32f710f41..60890fd6d 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -86,25 +86,31 @@ export enum Network { mainnet = "mainnet", } -export type NetworkMetrics = { +export type NetworkInfo = { currentTime: string; currentEpoch: number; currentBlock: number; + networkName: Network; +}; + +export type NetworkTotalStake = { + totalStakeControlledByDReps: number; + totalStakeControlledBySPOs: number; + alwaysAbstainVotingPower: number; + alwaysNoConfidenceVotingPower: number; +}; + +export type NetworkMetrics = { uniqueDelegators: number; totalDelegations: number; totalGovernanceActions: number; totalDRepVotes: number; totalRegisteredDReps: number; totalDRepDistr: number; - totalStakeControlledByDReps: number; - totalStakeControlledBySPOs: number; totalActiveDReps: number; totalInactiveDReps: number; totalActiveCIP119CompliantDReps: number; totalRegisteredDirectVoters: number; - alwaysAbstainVotingPower: number; - alwaysNoConfidenceVotingPower: number; - networkName: Network; noOfCommitteeMembers: number; quorumNumerator: number; quorumDenominator: number; diff --git a/govtool/frontend/src/services/AdaHandle.ts b/govtool/frontend/src/services/AdaHandle.ts index a130ad1aa..9a4f93874 100644 --- a/govtool/frontend/src/services/AdaHandle.ts +++ b/govtool/frontend/src/services/AdaHandle.ts @@ -1,4 +1,4 @@ -import { NetworkMetrics } from "@/models"; +import { NetworkInfo } from "@/models"; import { HandleObject } from "@/models/adaHandle"; export const ADAHANDLE_BASE_URL = { @@ -38,7 +38,7 @@ class AdaHandleService { * Initialize the AdaHandleService with the base URL for a specific network. * @param network - The name of the network. */ - initialize(network: NetworkMetrics["networkName"]): void { + initialize(network: NetworkInfo["networkName"]): void { if (this.adaHandleBaseUrl !== ADAHANDLE_BASE_URL[network]) { this.adaHandleBaseUrl = ADAHANDLE_BASE_URL[network]; } diff --git a/govtool/frontend/src/services/requests/getNetworkInfo.ts b/govtool/frontend/src/services/requests/getNetworkInfo.ts new file mode 100644 index 000000000..2ec061d11 --- /dev/null +++ b/govtool/frontend/src/services/requests/getNetworkInfo.ts @@ -0,0 +1,8 @@ +import { NetworkInfo } from "@/models"; +import { API } from "../API"; + +export const getNetworkInfo = async () => { + const response = await API.get("/network/info"); + + return response.data; +}; diff --git a/govtool/frontend/src/services/requests/getNetworkTotalStake.ts b/govtool/frontend/src/services/requests/getNetworkTotalStake.ts new file mode 100644 index 000000000..ed1899d5d --- /dev/null +++ b/govtool/frontend/src/services/requests/getNetworkTotalStake.ts @@ -0,0 +1,8 @@ +import { NetworkTotalStake } from "@models"; +import { API } from "../API"; + +export const getNetworkTotalStake = async () => { + const response = await API.get("/network/total-stake"); + + return response.data; +}; diff --git a/govtool/frontend/src/services/requests/index.ts b/govtool/frontend/src/services/requests/index.ts index f6721175a..20047744c 100644 --- a/govtool/frontend/src/services/requests/index.ts +++ b/govtool/frontend/src/services/requests/index.ts @@ -1,14 +1,18 @@ export * from "./getAdaHolderCurrentDelegation"; export * from "./getAdaHolderVotingPower"; -export * from "./getVoterInfo"; export * from "./getDRepList"; export * from "./getDRepVotes"; export * from "./getDRepVotingPower"; export * from "./getEpochParams"; +export * from "./getNetworkInfo"; +export * from "./getNetworkMetrics"; +export * from "./getNetworkTotalStake"; export * from "./getProposal"; export * from "./getProposals"; export * from "./getTransactionStatus"; export * from "./getVoteContextTextFromFile"; +export * from "./getVoterInfo"; +export * from "./metadataValidation"; export * from "./postAdaHolderDelegate"; export * from "./postAdaHolderDelegateAbstain"; export * from "./postAdaHolderDelegateNo"; @@ -17,5 +21,3 @@ export * from "./postDRepRegister"; export * from "./postDRepRemoveVote"; export * from "./postDRepRetire"; export * from "./postDRepVote"; -export * from "./metadataValidation"; -export * from "./getNetworkMetrics"; diff --git a/govtool/frontend/src/utils/localStorage.ts b/govtool/frontend/src/utils/localStorage.ts index 692a857b5..0bc866244 100644 --- a/govtool/frontend/src/utils/localStorage.ts +++ b/govtool/frontend/src/utils/localStorage.ts @@ -2,6 +2,8 @@ export const PENDING_TRANSACTION_KEY = "pending_transaction"; export const PROTOCOL_PARAMS_KEY = "protocol_params"; export const NETWORK_METRICS_KEY = "network_metrics"; export const NETWORK_INFO_KEY = "network_info"; +export const NETWORK_TOTAL_STAKE_KEY = "network_total_stake"; + export const WALLET_LS_KEY = "wallet_data"; export function getItemFromLocalStorage(key: string) { From 411be1ff5f69ed6199885e02b3167f872ae6c9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Wed, 12 Mar 2025 11:31:20 +0100 Subject: [PATCH 09/11] feat(#3173): add support for preprod matomo analyics --- govtool/frontend/index.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/govtool/frontend/index.html b/govtool/frontend/index.html index 16173bfe2..c97a2ed91 100644 --- a/govtool/frontend/index.html +++ b/govtool/frontend/index.html @@ -24,6 +24,14 @@