diff --git a/.env b/.env index afa8ab9..ef52312 100644 --- a/.env +++ b/.env @@ -2,10 +2,4 @@ DB_NAME=postgres DB_PASS=postgres DB_PORT=5432 DB_USERNAME=postgres -DB_HOST=db -SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" -1_METADATA='{"id": 1, "rpcUrl": "http://evm1:8545"}' -2_METADATA='{"id": 2, "rpcUrl": "http://evm2:8545"}' -3_METADATA='{"id": 3, "rpcUrl": "ws://substrate-pallet:9944"}' - -ENV_DOMAINS='[1,2,3]' \ No newline at end of file +DB_HOST=db \ No newline at end of file diff --git a/README.md b/README.md index d8701a2..0c39cc5 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ Fetches transfers ordered by time. Results can be filtered by various query para - **component**: Component of the transfer.
*Possible values*: `deposit`, `execution`
*Default*: `deposit` - **sender**: The address of the sender to filter by. +`GET /domains` + +Fetches all active domains. + `GET /health` Health check endpoint to ensure the system is running properly diff --git a/db/migrations/1734434955039-Data.js b/db/migrations/1734706828708-Data.js similarity index 97% rename from db/migrations/1734434955039-Data.js rename to db/migrations/1734706828708-Data.js index 1f05bd9..526221d 100644 --- a/db/migrations/1734434955039-Data.js +++ b/db/migrations/1734706828708-Data.js @@ -1,5 +1,5 @@ -module.exports = class Data1734434955039 { - name = 'Data1734434955039' +module.exports = class Data1734706828708 { + name = 'Data1734706828708' async up(db) { await db.query(`CREATE TABLE "account" ("id" character varying NOT NULL, "address_status" text, CONSTRAINT "PK_54115ee388cdb6d86bb4bf5b2ea" PRIMARY KEY ("id"))`) @@ -12,7 +12,7 @@ module.exports = class Data1734434955039 { await db.query(`CREATE INDEX "IDX_863162a8edb416799e89f386f8" ON "route" ("to_domain_id") `) await db.query(`CREATE INDEX "IDX_4536bd7b96f363dedfe95dd26f" ON "route" ("resource_id") `) await db.query(`CREATE UNIQUE INDEX "IDX_7dc7af2a7a9c846759377d1450" ON "route" ("from_domain_id", "to_domain_id", "resource_id") `) - await db.query(`CREATE TABLE "domain" ("id" character varying NOT NULL, "type" text NOT NULL, "name" text NOT NULL, CONSTRAINT "PK_27e3ec3ea0ae02c8c5bceab3ba9" PRIMARY KEY ("id"))`) + await db.query(`CREATE TABLE "domain" ("id" character varying NOT NULL, "type" text NOT NULL, "name" text NOT NULL, "icon_url" text NOT NULL, "explorer_url" text NOT NULL, CONSTRAINT "PK_27e3ec3ea0ae02c8c5bceab3ba9" PRIMARY KEY ("id"))`) await db.query(`CREATE TABLE "token" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "decimals" integer NOT NULL, "token_address" text NOT NULL, "token_symbol" text NOT NULL, "resource_id" character varying, "domain_id" character varying, CONSTRAINT "PK_82fae97f905930df5d62a702fc9" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_435ef0917a04e91698042dff2b" ON "token" ("resource_id") `) await db.query(`CREATE INDEX "IDX_3d565341b16e9c03d63e05eac8" ON "token" ("domain_id") `) @@ -78,4 +78,4 @@ module.exports = class Data1734434955039 { await db.query(`ALTER TABLE "transfer" DROP CONSTRAINT "FK_f6b9e9b86a1ce51c26cd08f596a"`) await db.query(`ALTER TABLE "transfer" DROP CONSTRAINT "FK_da16210e30a21445643e6acbcd7"`) } -} \ No newline at end of file +} diff --git a/envs/.env.api b/envs/.env.api deleted file mode 100644 index a487c9b..0000000 --- a/envs/.env.api +++ /dev/null @@ -1,5 +0,0 @@ -DB_NAME=squid -DB_PASS=squid -DB_PORT=5432 -DB_USERNAME=postgres -DB_HOST=db \ No newline at end of file diff --git a/envs/.env.evm-1 b/envs/.env.evm-1 deleted file mode 100644 index 0692246..0000000 --- a/envs/.env.evm-1 +++ /dev/null @@ -1,13 +0,0 @@ -DB_NAME=squid -DB_PASS=squid -DB_PORT=5432 -DB_USERNAME=postgres -DB_HOST=db -START_SCRIPT_ENV=index - -PROCESSOR_PROMETHEUS_PORT=3000 - -SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" - -DOMAIN_ID=1 -1_METADATA='{"rpcUrl": "http://evm1:8545"}' diff --git a/envs/.env.evm-2 b/envs/.env.evm-2 deleted file mode 100644 index 1889c40..0000000 --- a/envs/.env.evm-2 +++ /dev/null @@ -1,13 +0,0 @@ -DB_NAME=squid -DB_PASS=squid -DB_PORT=5432 -DB_USERNAME=postgres -DB_HOST=db -START_SCRIPT_ENV=index - -PROCESSOR_PROMETHEUS_PORT=3002 - -SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" - -DOMAIN_ID=2 -2_METADATA='{"rpcUrl": "http://evm2:8545"}' diff --git a/envs/.env.indexer.example b/envs/.env.indexer.example index afa8ab9..3eae8a5 100644 --- a/envs/.env.indexer.example +++ b/envs/.env.indexer.example @@ -4,8 +4,8 @@ DB_PORT=5432 DB_USERNAME=postgres DB_HOST=db SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" -1_METADATA='{"id": 1, "rpcUrl": "http://evm1:8545"}' -2_METADATA='{"id": 2, "rpcUrl": "http://evm2:8545"}' -3_METADATA='{"id": 3, "rpcUrl": "ws://substrate-pallet:9944"}' +1_METADATA='{"id": 1, "rpcUrl": "http://evm1:8545", "iconUrl": "https://example.com/icon1.png", "explorerUrl": "https://explorer.com/1"}' +2_METADATA='{"id": 2, "rpcUrl": "http://evm2:8545", "iconUrl": "https://example.com/icon2.png", "explorerUrl": "https://explorer.com/2"}' +3_METADATA='{"id": 3, "rpcUrl": "ws://substrate-pallet:9944", "iconUrl": "https://example.com/icon3.png", "explorerUrl": "https://explorer.com/3"}' ENV_DOMAINS='[1,2,3]' \ No newline at end of file diff --git a/envs/.env.substrate b/envs/.env.substrate deleted file mode 100644 index 0a2a60d..0000000 --- a/envs/.env.substrate +++ /dev/null @@ -1,13 +0,0 @@ -DB_NAME=squid -DB_PASS=squid -DB_PORT=5432 -DB_USERNAME=postgres -DB_HOST=db -START_SCRIPT_ENV=index - -PROCESSOR_PROMETHEUS_PORT=3004 - -SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" - -DOMAIN_ID=3 -3_METADATA='{"rpcUrl": "ws://substrate-pallet:9944"}' diff --git a/schema.graphql b/schema.graphql index 8b8b562..bf55092 100644 --- a/schema.graphql +++ b/schema.graphql @@ -67,6 +67,8 @@ type Domain @entity { id: ID! type: String! name: String! + iconURL: String! + explorerURL: String! routesFrom: [Route!] @derivedFrom(field: "fromDomain") routesTo: [Route!] @derivedFrom(field: "toDomain") token: [Token!] @derivedFrom(field: "domain") diff --git a/src/api/controllers/DomainsController.ts b/src/api/controllers/DomainsController.ts new file mode 100644 index 0000000..6166aae --- /dev/null +++ b/src/api/controllers/DomainsController.ts @@ -0,0 +1,30 @@ +/* +The Licensed Work is (c) 2024 Sygma +SPDX-License-Identifier: LGPL-3.0-only +*/ +import type { FastifyReply, FastifyRequest } from "fastify"; +import type { DataSource } from "typeorm"; + +import { logger } from "../../utils/logger"; +import { DomainsService } from "../services/dataAccess/domains.service"; + +export class DomainsController { + private domainsService: DomainsService; + + constructor(dataSource: DataSource) { + this.domainsService = new DomainsService(dataSource); + } + + public async getDomains( + request: FastifyRequest, + reply: FastifyReply, + ): Promise { + try { + const domainsResult = await this.domainsService.findDomains({}); + await reply.status(200).send(domainsResult); + } catch (error) { + logger.error("Error occurred when fetching domains", error); + await reply.status(500).send({ error: "Internal server error" }); + } + } +} diff --git a/src/api/routes/domains.routes.ts b/src/api/routes/domains.routes.ts new file mode 100644 index 0000000..990160f --- /dev/null +++ b/src/api/routes/domains.routes.ts @@ -0,0 +1,18 @@ +/* +The Licensed Work is (c) 2024 Sygma +SPDX-License-Identifier: LGPL-3.0-only +*/ +import type { FastifyInstance } from "fastify"; + +import { DomainsController } from "../controllers/DomainsController"; +import { domainsSchema } from "../schemas/domains.schema"; + +export async function domainRoutes(server: FastifyInstance): Promise { + const domainsController = new DomainsController(server.db); + server.get( + "/domains", + { schema: domainsSchema }, + domainsController.getDomains.bind(domainsController), + ); + return Promise.resolve(); +} diff --git a/src/api/routes/index.ts b/src/api/routes/index.ts index f523559..e0bf4b7 100644 --- a/src/api/routes/index.ts +++ b/src/api/routes/index.ts @@ -4,8 +4,10 @@ SPDX-License-Identifier: LGPL-3.0-only */ import type { FastifyInstance } from "fastify"; +import { domainRoutes } from "./domains.routes"; import { transferRoutes } from "./transfers.routes"; export async function registerRoutes(server: FastifyInstance): Promise { await server.register(transferRoutes, { prefix: "/api" }); + await server.register(domainRoutes, { prefix: "/api" }); } diff --git a/src/api/schemas/domains.schema.ts b/src/api/schemas/domains.schema.ts new file mode 100644 index 0000000..485eb8a --- /dev/null +++ b/src/api/schemas/domains.schema.ts @@ -0,0 +1,25 @@ +/* +The Licensed Work is (c) 2024 Sygma +SPDX-License-Identifier: LGPL-3.0-only +*/ +import { domainMetadataSchema, domainSchema } from "."; + +export const domainsSchema = { + summary: "Get domains", + response: { + 200: { + description: "List of domains", + content: { + "application/json": { + schema: { + type: "array", + items: { + ...domainSchema.properties, + ...domainMetadataSchema.properties, + }, + }, + }, + }, + }, + }, +}; diff --git a/src/api/schemas/index.ts b/src/api/schemas/index.ts index 88d6049..32b1b2c 100644 --- a/src/api/schemas/index.ts +++ b/src/api/schemas/index.ts @@ -25,6 +25,14 @@ export const domainSchema = { }, }; +export const domainMetadataSchema = { + type: "object", + properties: { + iconURL: { type: "string", example: "https://example.com/icon1.png" }, + explorerURL: { type: "string", example: "https://explorer.com/1" }, + }, +}; + export const tokenSchema = { tpye: "object", properties: { diff --git a/src/api/services/dataAccess/domains.service.ts b/src/api/services/dataAccess/domains.service.ts new file mode 100644 index 0000000..c08f104 --- /dev/null +++ b/src/api/services/dataAccess/domains.service.ts @@ -0,0 +1,24 @@ +/* +The Licensed Work is (c) 2024 Sygma +SPDX-License-Identifier: LGPL-3.0-only +*/ +import type { DataSource, FindOptionsWhere, Repository } from "typeorm"; + +import { Domain } from "../../../model"; + +export class DomainsService { + private domainRepository: Repository; + constructor(dataSource: DataSource) { + this.domainRepository = dataSource.getRepository(Domain); + } + + public async findDomains(where: FindOptionsWhere): Promise { + const domains = await this.domainRepository.find({ + where, + order: { + id: "ASC", + }, + }); + return domains; + } +} diff --git a/src/indexer/config/envLoader.ts b/src/indexer/config/envLoader.ts index 6d5b5f2..65ae8b6 100644 --- a/src/indexer/config/envLoader.ts +++ b/src/indexer/config/envLoader.ts @@ -15,6 +15,8 @@ export type DomainMetadata = { domainId: number; rpcUrl: string; domainGateway?: string; + iconUrl: string; + explorerUrl: string; }; export type EnvVariables = { diff --git a/src/indexer/evmIndexer/evmProcessor.ts b/src/indexer/evmIndexer/evmProcessor.ts index 1e14a40..b5a32df 100644 --- a/src/indexer/evmIndexer/evmProcessor.ts +++ b/src/indexer/evmIndexer/evmProcessor.ts @@ -72,7 +72,7 @@ export class EVMProcessor implements IProcessor { for (const block of ctx.blocks) { this.logger.info( - `Processing block ${block.header.height} on networ ${domain.name}(${domain.id})`, + `Processing block ${block.header.height} on network ${domain.name}(${domain.id})`, ); for (const log of block.logs) { try { diff --git a/src/indexer/substrateIndexer/substrateParser.ts b/src/indexer/substrateIndexer/substrateParser.ts index 4a09986..8af07b7 100644 --- a/src/indexer/substrateIndexer/substrateParser.ts +++ b/src/indexer/substrateIndexer/substrateParser.ts @@ -73,7 +73,7 @@ export class SubstrateParser implements ISubstrateParser { }); if (!resource) { throw new NotFoundError( - `Unssupported resource with ID ${event.resourceId}`, + `Unsupported resource with ID ${event.resourceId}`, ); } @@ -218,7 +218,7 @@ export class SubstrateParser implements ISubstrateParser { }); if (!resource) { throw new NotFoundError( - `Unssupported resource with ID ${event.resourceId}`, + `Unsupported resource with ID ${event.resourceId}`, ); } diff --git a/src/indexer/substrateIndexer/substrateProcessor.ts b/src/indexer/substrateIndexer/substrateProcessor.ts index 5e42901..8281725 100644 --- a/src/indexer/substrateIndexer/substrateProcessor.ts +++ b/src/indexer/substrateIndexer/substrateProcessor.ts @@ -87,7 +87,7 @@ export class SubstrateProcessor implements IProcessor { const fees: FeeCollectedData[] = []; for (const block of ctx.blocks) { this.logger.info( - `processing bloc ${block.header.height} on networ ${domain.name}(${domain.id})`, + `Processing block ${block.header.height} on network ${domain.name}(${domain.id})`, ); for (const event of block.events) { try { diff --git a/src/main_init.ts b/src/main_init.ts index bb2a090..44d991d 100644 --- a/src/main_init.ts +++ b/src/main_init.ts @@ -7,7 +7,7 @@ import type { EntityManager } from "typeorm"; import type { Domain as DomainConfig } from "./indexer/config"; import { fetchSharedConfig } from "./indexer/config"; -import { getEnv } from "./indexer/config/envLoader"; +import { getDomainMetadata, getEnv } from "./indexer/config/envLoader"; import { Domain, Resource, Token } from "./model"; import { initDatabase } from "./utils"; @@ -18,21 +18,33 @@ export async function init(): Promise { const dataSource = await initDatabase(envVars.dbConfig); const sharedConfig = await fetchSharedConfig(envVars.sharedConfigURL); - await insertDomains(sharedConfig.domains, dataSource.manager); + await insertDomains( + sharedConfig.domains, + dataSource.manager, + envVars.envDomains, + ); await dataSource.destroy(); } async function insertDomains( domains: Array, manager: EntityManager, + supportedDomainsIDs: number[], ): Promise { - for (const domain of domains) { + for (const domainID of supportedDomainsIDs) { + const domain = domains.find((domain) => domain.id == domainID); + if (!domain) { + throw new Error(`domain with id ${domainID} not found in shared-config`); + } + const domainMetadata = getDomainMetadata(domain.id.toString()); await manager.upsert( Domain, { id: domain.id.toString(), type: domain.type, name: domain.name, + iconURL: domainMetadata.iconUrl ?? "", + explorerURL: domainMetadata.explorerUrl ?? "", }, ["id"], ); diff --git a/src/model/generated/domain.model.ts b/src/model/generated/domain.model.ts index 3e72997..c5281c8 100644 --- a/src/model/generated/domain.model.ts +++ b/src/model/generated/domain.model.ts @@ -21,6 +21,12 @@ export class Domain { @StringColumn_({nullable: false}) name!: string + @StringColumn_({nullable: false}) + iconURL!: string + + @StringColumn_({nullable: false}) + explorerURL!: string + @OneToMany_(() => Route, e => e.fromDomain) routesFrom!: Route[] diff --git a/tests/e2e/domains.spec.ts b/tests/e2e/domains.spec.ts new file mode 100644 index 0000000..ddd30d7 --- /dev/null +++ b/tests/e2e/domains.spec.ts @@ -0,0 +1,24 @@ +/* +The Licensed Work is (c) 2024 Sygma +SPDX-License-Identifier: LGPL-3.0-only +*/ + +import { expect } from "chai"; +import { Domain } from "../../src/model"; + +const NUMBER_OF_DOMAINS = 3; + +describe("Domains tests", function () { + it("should succesfully fetch all domains", async () => { + const response = await fetch("http://localhost:8000/api/domains"); + const domains: Array = await response.json(); + expect(domains.length).to.be.deep.equal(NUMBER_OF_DOMAINS); + domains.map((domain) => { + expect(domain.id).to.be.not.null; + expect(domain.name).to.be.not.null; + expect(domain.type).to.be.not.null; + expect(domain.iconURL).to.be.not.null; + expect(domain.explorerURL).to.be.not.null; + }); + }); +});