DeVouch is a decentralized application that allows users to attest to a project's credibility through vouches or flags. It serves as a trust layer on top of the attestation service, allowing users to vouch for projects they believe in. DeVouch is a part of Giveth's ecosystem and is used to vouch for projects in other programs like Gitcoin Grants and RetroFund.
- Decentralized attestation system using EAS (Ethereum Attestation Service)
- Project vouching and flagging functionality
- Multi-organization support
- GraphQL API for data querying
- Integration with multiple data sources (Giveth, Gitcoin, RetroFund 4 (RF4), RetroFund 5 (RF5))
- Documentation: Giveth docs website
- GraphQL API: Available at the configured
GQL_URLendpoint (see configuration section)
- Backend Framework: Subsquid
- Database: PostgreSQL
- Query Language: GraphQL
- Runtime: Node.js with TypeScript
- Smart Contract Integration: Ethereum Attestation Service (EAS), ethers.js
- Testing: Jest
The system has three main flows:
- DeVouch periodically fetches target project data from various sources. It requests different sources for different programs. At the time of writing, it fetches data from Giveth, Gitcoin, RetroFund, and RetroList.
- Data is processed, and if there are new projects, they are stored in the PostgreSQL database along with other projects that had been imported before.
DeVouch does not count attestations from just any attestor. An account's attestation is valid only if a recognized organization has granted this role to the account by attesting to it.
This step is done manually by the organization's admin. They will make a pull request (PR) to add the organization to the database.
- Fill the
org-config.jsoncfile with the organization's data. - Run
npm run add-organizationto add the organization to the database. - Make a PR to add the organization to the database.
- We will review the PR and merge it if it's valid.
After the PR is merged from the previous step, the organization's admin will attest to the account by making an attestation using the account address and the organization's schema ID.
- The organization's admin will attest to the account.
- That attestation will be indexed by DeVouch.
- Now the account is part of the organization and can attest to projects on its behalf.
After projects and valid attestors are imported, attestors can start attesting to projects.
- The user attests to a project by creating an attestation using the DeVouch schema. This attestation includes the project source (e.g., Giveth), project ID, a vouch or flag indicator, an optional comment, and the UID of the attestation that grants them the right to attest on behalf of an organization. The UID is crucial for identifying which organization the user represents at this attestation, since a single account can belong to multiple organizations.
- DeVouch processes the attestation and records it in the database.
- Node.js (v22 or higher)
- Docker and Docker Compose
- PostgreSQL
- Git
- Clone the repository
- Install dependencies:
npm install
- Copy the environment template:
cp .env.template .env
- Set up the organization config:
cp org-config.template.jsonc org-config.jsonc
Below are the required environment variables. Please refer to .env.template for the full list and descriptions:
DB_HOST,DB_NAME,DB_PORT: PostgreSQL configurationGQL_PORT,GQL_URL: GraphQL server settingsRPC_ENDPOINT: Ethereum node endpointSQUID_NETWORK: Network to use for Squid (e.g.,eth-sepolia,optimism-mainnet)IMPORT_PROJECT_CRON_SCHEDULE: Cron schedule for project importSQD_API_KEY: Subsquid Network Gateway API key (https://portal.sqd.dev)SQD_RPC_ONLY: set to"true"to skip the Subsquid Network Gateway and index fromRPC_ENDPOINTonly. Unset (the default) uses the gateway.GIVETH_API_URL: Giveth V6 core GraphQL endpoint. Giveth projects are imported only from its keyset-paginateddevouchProjectCatalogquery; there is no V5 fallback and no default. Unset, together with the credentials, the Giveth source is skipped.GIVETH_API_USERNAME/GIVETH_API_PASSWORD: HTTP Basic credentials for thedevouchProjectCatalogquery. Required together withGIVETH_API_URL.GIVETH_IMAGE_BASE_URL: Giveth frontend origin prepended to relative image paths returned by the catalog (default project images such as/images/defaultProjectImages/3.png). Defaults tohttps://qf.giveth.iowhenSQUID_NETWORKisoptimism-mainnetandhttps://v6-staging.giveth.iootherwise; must be an absolute http(s) URL. Absolute image URLs are stored unchanged.GIVETH_MAX_DEACTIVATIONS_PER_RUN: floor on how many Giveth projects one import may hide (default250). It is the whole ceiling for a walk whose completeness could not be confirmed against the catalog'stotal; a walk that WAS confirmed complete may hide up to half the currently listed Giveth projects, so an accumulated backlog clears itself without an operator sizing it first. Either way a larger stale set aborts the run without hiding anything, and raising this value raises both ceilings. Any value that is not a positive integer is logged and ignored in favour of the default.- Various API endpoints for other integrations (RPGF3_API_URL, etc.)
- IPFS gateway configuration
npm run run:locallynpm run clear:run:locallynpm run test# 1. Configure org-config.jsonc
# 2. Run the script
npm run add-organization- Development: Local environment
- Production: Cloud deployment
- Build the application:
npm run build
- To deploy using Docker:
docker-compose up -d
The project uses GitHub Actions for continuous integration. Pull requests are automatically reviewed by CodeRabbit.
- Database connection issues: Check PostgreSQL container status and credentials.
- RPC endpoint errors: Verify RPC endpoint availability and API keys.
- GraphQL endpoint not responding: Check port configuration and server logs.
- Giveth import reports
Giveth V6 catalog is not configured: missing .... Giveth projects come only from V6'sdevouchProjectCatalogquery (#189), soGIVETH_API_URL,GIVETH_API_USERNAMEandGIVETH_API_PASSWORDare set together or not at all: with none set the source is skipped (IMPORT_SUMMARYshowsgivethok: truewith anote), with some set the import aborts. The import never falls back to the legacy V5allProjectsquery, and aGIVETH_API_URLstill pointing at the old V5 default (mainnet.serve.giveth.io) is refused before any request so the credentials are never sent there. The credentials are theDEVOUCH_USER/DEVOUCH_PASSvalues configured in that V6 instance's AdminJS global configuration. An unauthenticated or wrongly authenticated request returns HTTP 200 with anUNAUTHENTICATEDGraphQL error rather than a 401, so the failure surfaces from the response body and not the status code. Not every V6 deployment exposesdevouchProjectCatalogyet; against one that does not, the import aborts with "Cannot query field" on the first page. Incompose.local.yamlthe source is opt-in: setLOCAL_GIVETH_API_URL(for a V6 core on the host,http://host.docker.internal:4000/graphql) together with the two credentials in.env; with nothing set the source is skipped rather than aborting every cron cycle. The catalog serves every ACTIVE project, publicly listed or not, keyset paginated by ascending id;idis serialized as a string and is the same public numeric id the project had on V5, sogiveth-<id>keeps pointing at the same DeVouch row and its attestations.nextCatalogCursorverifies the ascending contract on every page and aborts the import rather than skipping rows if it is violated (src/test/givethCursor.test.ts), andsrc/test/givethCatalog.test.tspins the fetcher's error handling against a mocked request. The walk requests pages attake: 100, the maximumdevouchProjectCatalogaccepts. Because the catalog lists only ACTIVE projects, a stored Giveth project that is absent from it has been deactivated or cancelled upstream (#190). After a COMPLETE walk - and only then - the import clearsimportedon those rows in one transaction, which drops them from the listings without deleting the project or any attestation, vouch, flag or counter; a catalog that lists the project again flipsimportedback on the next run. Any failed page, malformed response or pagination violation aborts before reconciliation, and a walk that completes with zero projects is refused rather than acted on. A catalog that comes back SHORT rather than empty walks to completion with real projects in it, so two independent guards cover it, both evaluated before any row is written. The catalog's owntotal(selected on the first request only - upstream resolves it lazily with a COUNT off the read replica, so it is an estimate, never snapshot-consistent with the pages it arrives beside) refuses a walk that collected fewer projects thantotalminus a tolerance of 25 or 5%, whichever is larger; walking MORE thantotalis normal and never refused, since a project deactivated mid-walk leaves the count but not the page that already served it, and a missing or unusabletotalskips the check rather than failing the run. Independently, a stale set over the per-run ceiling aborts the run. The two are connected: a fixed ceiling is a proxy for "the catalog may have come back short", so oncetotalhas ruled that out directly the ceiling scales to half the listed Giveth projects - which is what lets the first run clear a backlog of long-cancelled projects (AC4) instead of refusing it every day until someone raises the number by hand. Without that confirmation the ceiling stays atGIVETH_MAX_DEACTIVATIONS_PER_RUN(default 250), and in neither regime may a run blank most of the listings. Between them no outage can mass-hide projects.IMPORT_SUMMARYreports the count asdeactivatedon thegivethsource;src/test/givethReconcile.test.tscovers the cases, the ceiling and the tolerance arithmetic. Nothing in CI exercises the live query, so after pointingGIVETH_API_URLat a new instance, check one run'sIMPORT_SUMMARYline forgivethok: true. sqd typegenreintroduces a type error insrc/abi/abi.support.ts: the generateddecodeResultneeds anas any as Resultcast on its return to compile under TypeScript 5.9+. Reapply it after regenerating the ABI bindings.sqd codegenrewritessrc/model/generated/against the newer@subsquid/typeorm-codegen, which names indexes explicitly. The live database uses TypeORM's auto-generated index names, so regenerating will make the nextsqd migration:generateemit index renames. Treat that as a deliberate, separate migration rather than a side effect of codegen.
- Enable debug mode by setting
SQD_DEBUG=*in the environment. - To check Docker container logs:
docker-compose logs -f
- Database logs are available in the PostgreSQL container.
For more detailed information, visit the Giveth docs website.