Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ changes.

### Removed

- Remove additional canonicalization of the metadata [Issue 3591](https://github.com/IntersectMBO/govtool/issues/3591)

## [v2.0.20](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.20) 2025-04-16

### Added
Expand Down
14 changes: 14 additions & 0 deletions gov-action-loader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Governance Action Loader

This directory contains the platform for submitting governance action data transactions on-chain, supporting both individual and bulk submission methods.

## 📍 Navigation

- [Frontend](./frontend/)
- [Backend](./backend/)

## Frontend
The Governance Action Loader frontend is a web application that communicates with the backend via a REST interface to facilitate the submission of on-chain governance data transactions.

## Backend
The Governance Action Loader backend uses a predefined wallet to execute transactions for on-chain governance data.
8 changes: 4 additions & 4 deletions govtool/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion govtool/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@hookform/resolvers": "^3.3.1",
"@intersect.mbo/govtool-outcomes-pillar-ui": "1.4.1",
"@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8",
"@intersect.mbo/pdf-ui": "0.7.0-beta-25",
"@intersect.mbo/pdf-ui": "0.7.0-beta-26",
"@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.4",
"@rollup/plugin-babel": "^6.0.4",
Expand Down
8 changes: 4 additions & 4 deletions govtool/frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1512,10 +1512,10 @@
resolved "https://registry.npmjs.org/@intersect.mbo/intersectmbo.org-icons-set/-/intersectmbo.org-icons-set-1.1.0.tgz"
integrity sha512-sjKEtnK9eLYH/8kCD0YRQCms3byFA/tnSsei9NHTZbBYX9sBpeX6ErfR0sKYjOSxQOxl4FumX9D0X+vHIqxo8g==

"@intersect.mbo/pdf-ui@0.7.0-beta-25":
version "0.7.0-beta-25"
resolved "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.7.0-beta-25.tgz"
integrity sha512-TDeWjJVMvLOR6sgTT6bCoHspZbybiRH0C5OzDDaU1yLSFD7xKx1aW5eAVdG0uzxrO+C0X7ceBGFoN5ucHICdlg==
"@intersect.mbo/pdf-ui@0.7.0-beta-26":
version "0.7.0-beta-26"
resolved "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.7.0-beta-26.tgz"
integrity sha512-05HR82ZKpJzitH7MgdNEdGr/Rc9A8PNPfPF5bX/z4We2y+iSi5kEoZdflpaUUWG3fFDwiaQCKe2xMiNwbGMOIQ==
dependencies:
"@emurgo/cardano-serialization-lib-asmjs" "^12.0.0-beta.2"
"@fontsource/poppins" "^5.0.14"
Expand Down
6 changes: 3 additions & 3 deletions govtool/metadata-validation/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ WORKDIR /dist

COPY package*.json ./

RUN yarn
RUN npm install

COPY . /dist

RUN yarn build
RUN npm run build

ENV IPFS_GATEWAY=$IPFS_GATEWAY
ENV IPFS_PROJECT_ID=$IPFS_PROJECT_ID

ENTRYPOINT ["/bin/sh", "-c", "yarn start:prod"]
ENTRYPOINT ["/bin/sh", "-c", "npm run start:prod"]
25 changes: 1 addition & 24 deletions govtool/metadata-validation/src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
import { catchError, finalize, firstValueFrom } from 'rxjs';
import { HttpService } from '@nestjs/axios';
import * as blake from 'blakejs';
import * as jsonld from 'jsonld';

import { ValidateMetadataDTO } from '@dto';
import { LoggerMessage, MetadataValidationStatus } from '@enums';
Expand Down Expand Up @@ -81,29 +80,7 @@ export class AppService {
const hashedMetadata = blake.blake2bHex(rawData, undefined, 32);

if (hashedMetadata !== hash) {
// Optionally validate on a parsed metadata
const hashedParsedMetadata = blake.blake2bHex(
JSON.stringify(parsedData, null, 2),
undefined,
32,
);
if (hashedParsedMetadata !== hash) {
// Optional support for the canonized data hash
// Validate canonized data hash
const canonizedMetadata = await jsonld.canonize(JSON.parse(rawData), {
safe: false,
});

const hashedCanonizedMetadata = blake.blake2bHex(
canonizedMetadata,
undefined,
32,
);

if (hashedCanonizedMetadata !== hash) {
throw MetadataValidationStatus.INVALID_HASH;
}
}
throw MetadataValidationStatus.INVALID_HASH;
}
} catch (error) {
Logger.error(LoggerMessage.METADATA_VALIDATION_ERROR, error);
Expand Down
6 changes: 4 additions & 2 deletions tests/govtool-frontend/playwright/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
HOST_URL=http://localhost:3000

DOCS_URL=https://docs.gov.tools
DOCS_URL=https://docs.gov.tools/cardano-govtool

#Blockfrost
BLOCKFROST_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXX
Expand All @@ -12,7 +12,9 @@ KUBER_API_KEY=
TX_TIMEOUT=240000 # milliseconds

# Metadata Bucket
METADATA_BUCKET_URL=https://metadata-govtool.cardanoapi.io
CARDANOAPI_METADATA_URL=https://metadata-govtool.cardanoapi.io

NETWORK=preview

FAUCET_ADDRESS=
FAUCET_PAYMENT_PRIVATE=
Expand Down
44 changes: 30 additions & 14 deletions tests/govtool-frontend/playwright/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ npx playwright install

### HOST URL

| Environment | URL |
| :---------- | :----------------------------------------------------------------------------------------------------------------------- |
| Development | [https://p80-z78acf3c2-zded6a792-gtw.z937eb260.rustrocks.fr](https://p80-z78acf3c2-zded6a792-gtw.z937eb260.rustrocks.fr) |
| QA | [https://govtool.cardanoapi.io](https://govtool.cardanoapi.io) |
| Preview | [https://preview.gov.tools](https://preview.gov.tools) |
| Pre-Prod | [https://pre-prod.gov.tools](https://pre-prod.gov.tools) |
| Mainnet | [https://gov.tools](https://gov.tools) |
| Environment | URL | Network |
| :---------- | :----------------------------------------------------------------------------------------------------------------------- | :------ |
| Development | [https://p80-z78acf3c2-zded6a792-gtw.z937eb260.rustrocks.fr](https://p80-z78acf3c2-zded6a792-gtw.z937eb260.rustrocks.fr) | Preview |
| QA | [https://govtool.cardanoapi.io](https://govtool.cardanoapi.io) | Preview |
| Preview | [https://preview.gov.tools](https://preview.gov.tools) | Preview |
| Pre-Prod | [https://pre-prod.gov.tools](https://pre-prod.gov.tools) | Preprod |
| Mainnet | [https://gov.tools](https://gov.tools) | Mainnet |

---

Expand All @@ -66,16 +66,21 @@ npx playwright install
### Blockfrost API Key

- To generate a Blockfrost API key (Project ID):
1. Follow the instructions in the [Blockfrost documentation](https://blockfrost.dev/overview/getting-started).

1. Follow the instructions in the [Blockfrost documentation](https://blockfrost.dev/overview/getting-started) 📚.
2. The **Project ID** you create there serves as your **Blockfrost API Key**.
3. Copy the **Project ID** and set it as `BLOCKFROST_API_KEY`.

🔐 Note: Ensure you select the correct network for the **Project ID** that matches the host URL from the environment listed above.

### Kuber API Key

- To generate a Kuber API Key:
1. Visit [Kuberide](https://kuberide.com/).
1. Visit [Kuberide](https://kuberide.com/) 🌐.
2. Log in using your Google or GitHub account.
3. Navigate to **API Keys**.
4. Click to **Generate API Key**.
3. Navigate to **API Keys** ⚙️.
4. Click to **Generate API Key** ✨.
5. Copy the API key and set it as `KUBER_API_KEY`

---

Expand Down Expand Up @@ -127,15 +132,26 @@ FAUCET_ADDRESS=<your-wallet-address>

### Step 3: Fund the Wallet

Ensure the wallet address has sufficient funds for your test runs. The required balance depends on the specific tests you plan to execute (refer to the test-specific test run details below).
Ensure your wallet has enough funds for your test runs. The required balance depends on the specific tests you plan to execute (see test-specific details below).

To fund your wallet on the **Preview** or **Preprod** network:

1. Use the Cardano Testnet Faucet:
[https://docs.cardano.org/cardano-testnets/tools/faucet](https://docs.cardano.org/cardano-testnets/tools/faucet) 🌐
**Note**: There is a daily limit of **10,000 ADA** per wallet.

2. If the funded amount is insufficient, transfer additional ADA from another wallet. 💸

To check your wallet balance:

To check the wallet balance, visit:
Visit:

```
https://${network}.cardanoscan.io/address/<your-wallet-address>
```

Replace `${network}` with the appropriate Cardano network (e.g.`preprod`, or `preview`) and `<your-wallet-address>` with the generated address.
- Replace `${network}` with the appropriate network (e.g., `preprod` or `preview`).
- Replace `<your-wallet-address>` with your wallet address. 🔍

**Example**:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ShelleyWallet } from "./lib/helpers/crypto";
console.log("\n🎉 Wallet generated successfully!");
console.log("-----------------------------------");
console.log("💼 Wallet:", walletJson);
console.log(`🔑 Payment Private Key: ${walletJson.payment.private}`);
console.log(`\n🔑 Payment Private Key: ${walletJson.payment.private}`);
console.log(`🔗 Stake Public Key Hash: ${walletJson.stake.pkh}`);
console.log(`🏠 Wallet Address: ${walletJson.address}`);
console.log("-----------------------------------");
Expand Down
1 change: 1 addition & 0 deletions tests/govtool-frontend/playwright/lib/constants/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export const budgetProposal01AuthFile = ".auth/budgetProposal01.json";
export const budgetProposal02AuthFile = ".auth/budgetProposal02.json";
export const budgetProposal03AuthFile = ".auth/budgetProposal03.json";
export const budgetProposal04AuthFile = ".auth/budgetProposal04.json";
export const budgetProposal05AuthFile = ".auth/budgetProposal05.json";
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const budgetProposal01Wallet: StaticWallet = staticWallets[19];
export const budgetProposal02Wallet: StaticWallet = staticWallets[20];
export const budgetProposal03Wallet: StaticWallet = staticWallets[21];
export const budgetProposal04Wallet: StaticWallet = staticWallets[22];
export const budgetProposal05Wallet: StaticWallet = staticWallets[23];

export const adaHolderWallets = [
adaHolder01Wallet,
Expand Down
21 changes: 21 additions & 0 deletions tests/govtool-frontend/playwright/lib/forms/dRepForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const formErrors = {
],
linkDescription: "max-80-characters-error",
email: "invalid-email-address-error",
image: "invalid-image-url-error",
links: {
url: "link-reference-description-1-error",
description: "link-reference-description-1-error",
Expand Down Expand Up @@ -59,6 +60,9 @@ export default class DRepForm {
readonly motivationsInput = this.form.getByTestId("motivations-input");
readonly qualificationsInput = this.form.getByTestId("qualifications-input");
readonly paymentAddressInput = this.form.getByTestId("payment-address-input");
readonly imageInput = this.form.locator(
"div:nth-child(4) > div:nth-child(2) > input"
); // BUG missing test id
readonly doNotListCheckBox = this.form.getByRole("checkbox");

constructor(private readonly form: Page) {}
Expand Down Expand Up @@ -149,6 +153,7 @@ export default class DRepForm {
await this.motivationsInput.fill(dRepInfo.motivations);
await this.qualificationsInput.fill(dRepInfo.qualifications);
await this.paymentAddressInput.fill(dRepInfo.paymentAddress);
await this.imageInput.fill(dRepInfo.image);
await this.linkRefrenceFirstUrlInput.fill(
dRepInfo.linksReferenceLinks[0].url
);
Expand All @@ -175,6 +180,9 @@ export default class DRepForm {
const motivationsInputText = await this.motivationsInput.textContent();
const qualificationsInputText =
await this.qualificationsInput.textContent();
const isImageErrorVisible = await this.form
.getByTestId(formErrors.image)
.isVisible();
const isReferenceLinkErrorVisible = await this.form
.getByTestId(formErrors.links.url)
.isVisible();
Expand Down Expand Up @@ -202,6 +210,10 @@ export default class DRepForm {
`${dRepInfo.qualifications} is not equal to ${qualificationsInputText}`,
}).toEqual(dRepInfo.qualifications);

await expect(this.form.getByTestId(formErrors.image), {
message: isImageErrorVisible && `${dRepInfo.image} is an invalid image`,
}).toBeHidden();

await expect(this.form.getByTestId(formErrors.links.url), {
message:
isReferenceLinkErrorVisible &&
Expand Down Expand Up @@ -246,6 +258,9 @@ export default class DRepForm {
const motivationsInputText = await this.motivationsInput.textContent();
const qualificationsInputText =
await this.qualificationsInput.textContent();
const isImageErrorVisible = await this.form
.getByTestId(formErrors.image)
.isVisible();
const isReferenceLinkErrorVisible = await this.form
.getByTestId(formErrors.links.url)
.isVisible();
Expand Down Expand Up @@ -284,6 +299,12 @@ export default class DRepForm {
`${dRepInfo.qualifications} is equal to ${qualificationsInputText}`,
}).not.toEqual(dRepInfo.qualifications);

await expect(this.form.getByTestId(formErrors.image), {
message: !isImageErrorVisible && `${dRepInfo.image} is a valid image`,
}).toBeVisible({
timeout: 60_000,
});

await expect(this.form.getByTestId(formErrors.links.url), {
message:
!isReferenceLinkErrorVisible &&
Expand Down
14 changes: 10 additions & 4 deletions tests/govtool-frontend/playwright/lib/helpers/dRep.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import DRepDirectoryPage from "@pages/dRepDirectoryPage";
import { expect, Page } from "@playwright/test";
import { IDRep } from "@types";
import { IDRep, IDRepInfo } from "@types";
import { bech32 } from "bech32";
import * as crypto from "crypto";
import { functionWaitedAssert } from "./waitedLoop";
import { invalid as mockInvalid, valid as mockValid } from "@mock/index";
import {
invalid,
invalid as mockInvalid,
valid as mockValid,
} from "@mock/index";
import { faker } from "@faker-js/faker";
import { ShelleyWallet } from "./crypto";
import environments from "@constants/environments";
Expand Down Expand Up @@ -131,7 +135,7 @@ export function convertDRep(
return { cip129: cip129DRep, cip105: cip105DRep };
}

export async function generateValidDRepInfo() {
export async function generateValidDRepInfo(): Promise<IDRepInfo> {
return {
name: mockValid.name(),
objectives: faker.lorem.paragraph(2),
Expand All @@ -140,6 +144,7 @@ export async function generateValidDRepInfo() {
paymentAddress: (await ShelleyWallet.generate()).addressBech32(
environments.networkId
),
image: faker.image.avatarGitHub(),
linksReferenceLinks: [
{
url: faker.internet.url(),
Expand All @@ -155,13 +160,14 @@ export async function generateValidDRepInfo() {
};
}

export function generateInvalidDRepInfo() {
export function generateInvalidDRepInfo(): IDRepInfo {
return {
name: mockInvalid.name(),
objectives: faker.lorem.paragraph(40),
motivations: faker.lorem.paragraph(40),
qualifications: faker.lorem.paragraph(40),
paymentAddress: faker.string.alphanumeric(45),
image: invalid.url(),
linksReferenceLinks: [
{
url: mockInvalid.url(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default class BudgetDiscussionDetailsPage {
);
readonly verifyIdentityBtn = this.page.getByTestId("verify-identity-button");
readonly readMoreBtn = this.page.getByTestId("read-more-button");
readonly menuButton = this.page.getByTestId("menu-button");

// content
readonly copyLinkText = this.page.getByTestId("copy-link-text");
Expand Down Expand Up @@ -139,7 +140,7 @@ export default class BudgetDiscussionDetailsPage {
async deleteProposal() {
await this.page.waitForTimeout(2_000);

await this.page.getByTestId("menu-button").click();
await this.menuButton.click();
await this.page.getByTestId("delete-proposal").click();
await this.page.getByTestId("delete-proposal-yes-button").click();
}
Expand Down
Loading
Loading