Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support organisation start block #83

Merged
merged 3 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
# Backup file
.env.backup

# Exclude add-organisation updates on pushes
add-organisation.js
# Ogranisation config
org-config.jsonc
99 changes: 11 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,97 +1,20 @@
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/subsquid/squid-evm-template)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Giveth/DeVouch-BE)

# Minimal EVM squid
# Introduction

This is a starter template of a squid indexer for EVM networks (Ethereum, Polygon, BSC, etc.). See [Squid SDK docs](https://docs.subsquid.io/) for a complete reference.
Devouch is a decentralized application that allows users to attest to projects credibility by their vouches or flags. The backend is built on Subsquid and uses a Postgres database.

To extract EVM logs and transactions by a topic or a contract address, use [`.addLog()`](https://docs.subsquid.io/evm-indexing/configuration/evm-logs/), [`.addTransaction()`](https://docs.subsquid.io/evm-indexing/configuration/transactions/), [`.addTrace()`](https://docs.subsquid.io/evm-indexing/configuration/traces/) or [`.addStateDiff()`](https://docs.subsquid.io/evm-indexing/configuration/state-diffs/) methods of the `EvmBatchProcessor` instance defined in `src/processor.ts`. Select data fields with [`.setFields()`](https://docs.subsquid.io/evm-indexing/configuration/data-selection/).
# Getting Started

The requested data is transformed in batches by a single handler provided to the `processor.run()` method.

For a full list of supported networks and config options,
check the [`EvmBatchProcessor` overview](https://docs.subsquid.io/evm-indexing/evm-processor/) and the [setup section](https://docs.subsquid.io/evm-indexing/configuration/).

For a step-by-step migration guide from TheGraph, see [the dedicated docs page](https://docs.subsquid.io/migrate/migrate-subgraph/).

Dependencies: Node.js v16 or newer, Git, Docker.

## Quickstart

```bash
# 0. Install @subsquid/cli a.k.a. the sqd command globally
npm i -g @subsquid/cli

# 1. Retrieve the template
sqd init my_squid_name -t evm
cd my_squid_name

# 2. Install dependencies
npm ci

# 3. Start a Postgres database container and detach
sqd up

# 4. Build the squid
sqd build

# 5. Start both the squid processor and the GraphQL server
sqd run .
```
A GraphiQL playground will be available at [localhost:4350/graphql](http://localhost:4350/graphql).

You can also start squid services one by one:
```bash
sqd process
sqd serve
```

## Dev flow

### 1. Define database schema

Start development by defining the schema of the target database via `schema.graphql`.
Schema definition consists of regular graphql type declarations annotated with custom directives.
Full description of `schema.graphql` dialect is available [here](https://docs.subsquid.io/store/postgres/schema-file/).

### 2. Generate TypeORM classes

Mapping developers use TypeORM [EntityManager](https://typeorm.io/#/working-with-entity-manager)
to interact with target database during data processing. All necessary entity classes are
generated by the squid framework from `schema.graphql`. This is done by running `sqd codegen`
command.

### 3. Generate database migrations

All database changes are applied through migration files located at `db/migrations`.
`squid-typeorm-migration(1)` tool provides several commands to drive the process.
## Add new organisation

```bash
## drop create the database
sqd down
sqd up
# 0. Copy the org-config.template.json to org-config.json
cp org-config.template.json org-config.json

## replace any old schemas with a new one made from the entities
sqd migration:generate
# 1. Fill the config with your organisation data
# 2. Run the script to add your organisation data to migrations
npm run add-organisation
```
See [docs on database migrations](https://docs.subsquid.io/store/postgres/db-migrations/) for more details.

### 4. Import ABI contract and generate interfaces to decode events

It is necessary to import the respective ABI definition to decode EVM logs. One way to generate a type-safe facade class to decode EVM logs is by placing the relevant JSON ABIs to `./abi`, then using `squid-evm-typegen(1)` via an `sqd` script:

```bash
sqd typegen
```

See more details on the [`squid-evm-typegen` doc page](https://docs.subsquid.io/evm-indexing/squid-evm-typegen).

## Project conventions

Squid tools assume a certain [project layout](https://docs.subsquid.io/basics/squid-structure):

* All compiled js files must reside in `lib` and all TypeScript sources in `src`.
The layout of `lib` must reflect `src`.
* All TypeORM classes must be exported by `src/model/index.ts` (`lib/model` module).
* Database schema must be defined in `schema.graphql`.
* Database migrations must reside in `db/migrations` and must be plain js files.
* `sqd(1)` and `squid-*(1)` executables consult `.env` file for environment variables.
Then create a PR to the main branch to be reviewed and merged.
8 changes: 5 additions & 3 deletions db/create-organisation-add-migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ exports.default = function createOrganisationAddMigration(
schemaId,
authorizedAttestor,
color = null,
network = "eth-sepolia"
network = "eth-sepolia",
startBlock = null
) {
const timestamp = new Date().getTime() + ADD_ORG_MIGRATION_OFFSET;
const fileName = `${timestamp}-Add${organisationName}.js`;
Expand All @@ -25,12 +26,13 @@ exports.default = function createOrganisationAddMigration(
if (SQUID_NETWORK !== "${network}") return;
// add organisation with name "${organisationName}" and schema id "${schemaId}"
await db.query(
\`INSERT INTO "organisation" ("id", "name", "issuer", "color")
\`INSERT INTO "organisation" ("id", "name", "issuer", "color", "start_block")
VALUES (
'${schemaId.toLocaleLowerCase()}',
'${organisationName}',
'${authorizedAttestor.toLocaleLowerCase()}',
${color ? "'" + color.toLocaleLowerCase() + "'" : null}
${color ? "'" + color.toLocaleLowerCase() + "'" : null},
${startBlock}
)\`
);
}
Expand Down
11 changes: 11 additions & 0 deletions db/migrations/1718184531403-Data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = class Data1718184531403 {
name = 'Data1718184531403'

async up(db) {
await db.query(`ALTER TABLE "organisation" ADD "start_block" integer`)
}

async down(db) {
await db.query(`ALTER TABLE "organisation" DROP COLUMN "start_block"`)
}
}
45 changes: 45 additions & 0 deletions org-config.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Organization Configuration",
"description": "Schema for the organization configuration file.",
"type": "object",

"properties": {
"$schema": {
"description": "The schema version.",
"type": "string"
},
"name": {
"description": "The name of the organization.",
"type": "string"
},
"schemaId": {
"description": "The UID for the attestation schema.",
"type": "string",
"pattern": "^0x[A-Fa-f0-9]{64}$"
},
"authorizedAttestor": {
"description": "The address of the authorized attestor.",
"type": "string",
"pattern": "^0x[A-Fa-f0-9]{40}$"
},

"network": {
"description": "The network for the organization.",
"type": "string",
"enum": ["eth-sepolia", "optimism-mainnet"]
},

"color": {
"description": "The organisaiton color in UI.",
"type": "string",
"pattern": "^#[A-Fa-f0-9]{6}$"
},
"startBlock": {
"description": "The block number at which the organization was created.",
"type": "integer"
}
},
"required": ["name", "schemaId", "authorizedAttestor", "network"],
"additionalProperties": false
}
13 changes: 13 additions & 0 deletions org-config.template.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "./org-config.schema.json",

"name": "Giveth Verifier",
"schemaId": "0xf63f2a7159ee674aa6fce42196a8bb0605eafcf20c19e91a7eafba8d39fa0404",
"authorizedAttestor": "0x93E79499b00a2fdAAC38e6005B0ad8E88b177346",

"network": "optimism-mainnet",

// Optional
"color": "#FFFFFF",
"startBlock": 999999999999,
}
6 changes: 6 additions & 0 deletions package-lock.json

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

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"test": "npm run test:run-fresh-db; dotenvx run --env-file=.env.test -- jest --runInBand",
"clear:generate:migration:run:locally": "sqd codegen ; docker compose -f docker-compose-potgres.yml down -v;docker compose -f docker-compose-potgres.yml up -d;sqd migration:generate; sqd run",
"clear:run:locally": "sqd build ; docker compose -f docker-compose-potgres.yml down -v;docker compose -f docker-compose-potgres.yml up -d; sqd run",
"run:locally": "docker compose -f docker-compose-potgres.yml up -d; sqd build; sqd run"
"run:locally": "docker compose -f docker-compose-potgres.yml up -d; sqd build; sqd run",
"add-organization": "node add-organisation.js"
},
"dependencies": {
"@ethereum-attestation-service/eas-sdk": "^1.5.0",
Expand All @@ -21,6 +22,7 @@
"dotenv": "^16.4.4",
"ethers": "^6.12.1",
"html-to-text": "^9.0.5",
"jsonc-parser": "^3.2.1",
"node-cron": "^3.0.3",
"pg": "^8.11.5",
"showdown": "^2.1.0",
Expand Down
2 changes: 2 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type Organisation @entity {
issuer: String!
"Color of the organization"
color: String
"The first attestation block number"
startBlock: Int
"Organization Attestors"
attestors: [AttestorOrganisation!]! @derivedFrom(field: "organisation")
attestedProjects: [OrganisationProject!]! @derivedFrom(field: "organisation")
Expand Down
16 changes: 14 additions & 2 deletions src/controllers/utils/databaseHelper.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { DataHandlerContext } from "@subsquid/evm-processor";
import { createOrmConfig } from "@subsquid/typeorm-config";
import { Store } from "@subsquid/typeorm-store";
import { assert } from "console";
import { EntityManager } from "typeorm/entity-manager/EntityManager";
import { DataSource, EntityManager } from "typeorm";

export const getEntityManger = (
let connection: DataSource | undefined;
export async function getEntityManagerByConnection(): Promise<EntityManager> {
if (!connection) {
let cfg = createOrmConfig({ projectDir: __dirname + "/../../.." });
(cfg.entities as string[]).push(__dirname + "/../../model/generated/*.ts");

connection = await new DataSource(cfg).initialize();
}
return connection.createEntityManager();
}

export const getEntityMangerByContext = (
ctx: DataHandlerContext<Store>
): EntityManager => {
const em = (ctx.store as unknown as { em: () => EntityManager }).em();
Expand Down
6 changes: 3 additions & 3 deletions src/controllers/utils/modelHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
OrganisationProject,
Project,
} from "../../model";
import { getEntityManger } from "./databaseHelper";
import { getEntityMangerByContext } from "./databaseHelper";
import { ProjectStats } from "./types";

export const upsertOrganisatoinProject = async (
Expand All @@ -32,7 +32,7 @@ export const updateProjectAttestationCounts = async (
ctx: DataHandlerContext<Store>,
project: Project
): Promise<void> => {
const em = getEntityManger(ctx);
const em = getEntityMangerByContext(ctx);
const projectStats = await getProjectStats(ctx, project);

project.totalVouches = projectStats.pr_total_vouches;
Expand Down Expand Up @@ -96,7 +96,7 @@ export const getProjectStats = async (
ctx: DataHandlerContext<Store>,
project: Project
): Promise<ProjectStats> => {
const em = getEntityManger(ctx);
const em = getEntityMangerByContext(ctx);

const result = await em.query(
`
Expand Down
2 changes: 2 additions & 0 deletions src/features/import-projects/gitcoin/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SourceConfig } from "../types";

export const GITCOIN_API_URL =
process.env.GITCOIN_API_URL ||
"https://grants-stack-indexer-v2.gitcoin.co/graphql";
Expand Down
2 changes: 2 additions & 0 deletions src/features/import-projects/giveth/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SourceConfig } from "../types";

export const GIVETH_API_URL =
process.env.GIVETH_API_URL || "https://mainnet.serve.giveth.io/graphql";

Expand Down
1 change: 1 addition & 0 deletions src/features/import-projects/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Project } from "../../model";
import { getDataSource } from "../../helpers/db";
import { DESCRIPTION_SUMMARY_LENGTH } from "../../constants";
import { convert } from "html-to-text";
import { SourceConfig } from "./types";

export const updateOrCreateProject = async (
project: any,
Expand Down
2 changes: 2 additions & 0 deletions src/features/import-projects/rf4/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SourceConfig } from "../types";

export const RF4_API_URL =
process.env.RF4_API_URL || "https://round4-api-eas.retrolist.app/projects";

Expand Down
2 changes: 2 additions & 0 deletions src/features/import-projects/rpgf/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SourceConfig } from "../types";

export const RPGF3_API_URL =
process.env.RPGF3_API_URL || "https://backend.pairwise.vote/mock/projects";

Expand Down
2 changes: 1 addition & 1 deletion src/features/import-projects/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface SourceConfig {
export interface SourceConfig {
source: string;
idField: string;
titleField: string;
Expand Down
28 changes: 16 additions & 12 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { TypeormDatabase } from "@subsquid/typeorm-store";
import { processor } from "./processor";
import { Processor } from "./processor";
import * as EASContract from "./abi/EAS";
import { processAttest } from "./mappings/attest";
import { processRevokeLog } from "./mappings/revoke";
import { importProjects } from "./features/import-projects/index";

processor.run(new TypeormDatabase({ supportHotBlocks: false }), async (ctx) => {
for (let _block of ctx.blocks) {
for (let _log of _block.logs) {
switch (_log.topics[0]) {
case EASContract.events.Attested.topic:
await processAttest(ctx, _log);
break;
Processor.getInstance().then(async (processor) => {
processor.run(
new TypeormDatabase({ supportHotBlocks: false }),
async (ctx) => {
for (let _block of ctx.blocks) {
for (let _log of _block.logs) {
switch (_log.topics[0]) {
case EASContract.events.Attested.topic:
await processAttest(ctx, _log);
break;

case EASContract.events.Revoked.topic:
await processRevokeLog(ctx, _log);
case EASContract.events.Revoked.topic:
await processRevokeLog(ctx, _log);
}
}
}
}
}
);
});

importProjects();
Loading