Skip to content

Commit

Permalink
feat: ui integration (#48)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes ZKS-165 ZKS-170 ZKS-162 ZKS-168

## Description
- Implemented Metrics controller
- refactor on API dto's
- unit tests for controller 
- zkchains registry updated

---------

Co-authored-by: nigiri <[email protected]>
  • Loading branch information
0xkenj1 and 0xnigir1 authored Aug 20, 2024
1 parent 0395086 commit 01eba7d
Show file tree
Hide file tree
Showing 42 changed files with 1,725 additions and 365 deletions.
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
PORT=3000

L1_RPC_URLS="" #CSV list of L1 RPC URLs
L2_RPC_URLS="" #CSV list of L2 RPC URLs

COINGECKO_API_KEY='' # CoinGecko API key
COINGECKO_BASE_URL='' # CoinGecko API base URL for the API version you are using
COINGECKO_API_TYPE='' # CoinGecko API Type: 'demo' or 'pro'
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged
npx lint-staged && pnpm check-types
37 changes: 33 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# zkChainHub
# ZKchainHub - Backend

## Description

Expand Down Expand Up @@ -28,6 +28,17 @@ graph LR
$ pnpm install
```

## ⚙️ Setting up env variables

- Create `.env` file in the `root` folder and copy paste `.env.example` content in there.
```
$ cp .env.example .env
```
- Set up `L1_RPC_URLS` as CSV list of RPC URLs. For example, `https://eth.llamarpc.com,https://rpc.flashbots.net/fast`. You can check [Chainlist](https://chainlist.org/) for a list of public RPCs
- Set up `L2_RPC_URLS` as CSV list of RPC URLs. For example, `https://mainnet.era.zksync.io`. You can check [Chainlist](https://chainlist.org/) for a list of public RPCs
- Set `COINGECKO_API_KEY`, `COINGECKO_BASE_URL` and `COINGECKO_API_KEY` depending on your API plan. You can get an API Key creating an account on [Coingecko's site](https://www.coingecko.com/en/api)
- (Optionally) Set `PORT` on which API is made available. By default is port 3000

## Running the app

```bash
Expand All @@ -44,6 +55,8 @@ $ pnpm run start:prod
$ pnpm run start my-app
```

Verify that ZKchainHub API is running on http://localhost:3000 (or the port specified)

## Test

```bash
Expand All @@ -57,15 +70,31 @@ $ pnpm run test:e2e
$ pnpm run test:cov
```

## Creating a new app
## Docs
Locally Swagger docs are available at http://localhost:3000/docs

## Development

### Linter
Run `pnpm lint` to make sure the code base follows configured linter rules.

### Creating a new app
```bash
$ pnpm nest g app <app-name>
```

## Creating a new library
### Creating a new library
```bash
$ pnpm create-lib <lib-name>
```

## 💻 Conventional Commits
### 💻 Conventional Commits
We follow the Conventional Commits [specification](https://www.conventionalcommits.org/en/v1.0.0/#specification).

## Contributing

ZKchainHub was built with ❤️ by [Wonderland](https://defi.sucks).

Wonderland is a team of top Web3 researchers, developers, and operators who believe that the future needs to be open-source, permissionless, and decentralized.

[DeFi sucks](https://defi.sucks), but Wonderland is here to make it better.
92 changes: 89 additions & 3 deletions apps/api/src/api.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,104 @@
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { CacheModule } from "@nestjs/cache-manager";
import { Logger, MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";

import { MetricsModule } from "@zkchainhub/metrics";
import { PricingModule } from "@zkchainhub/pricing";
import { ProvidersModule } from "@zkchainhub/providers";
import { LoggerModule } from "@zkchainhub/shared";

import { RequestLoggerMiddleware } from "./common/middleware/request.middleware";
import { config, ConfigType, validationSchema } from "./config";
import { MetricsController } from "./metrics/metrics.controller";

/**
* The main API module of the application.
* Here we import all required modules and register the controllers for the ZKchainHub API.
*/
@Module({
imports: [LoggerModule],
imports: [
LoggerModule,
ConfigModule.forRoot({
load: [config],
validationSchema: validationSchema,
validationOptions: {
abortEarly: true,
},
}),
MetricsModule.registerAsync({
imports: [
ConfigModule,
ProvidersModule.registerAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService<Pick<ConfigType, "l1" | "l2">, true>) => {
return {
l1: {
rpcUrls: config.get("l1.rpcUrls", { infer: true }),
chain: config.get("l1.chain", { infer: true }),
},
};
},
extraProviders: [Logger],
}),
PricingModule.registerAsync({
imports: [
ConfigModule,
CacheModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (
config: ConfigService<Pick<ConfigType, "pricing">, true>,
) => ({
cacheOptions: {
store: "memory",
ttl: config.get("pricing.cacheOptions.ttl", { infer: true }),
},
}),
}),
],
useFactory: (config: ConfigService<Pick<ConfigType, "pricing">, true>) => {
return {
pricingOptions: {
provider: "coingecko",
apiKey: config.get("pricing.pricingOptions.apiKey", {
infer: true,
}),
apiBaseUrl: config.get("pricing.pricingOptions.apiBaseUrl", {
infer: true,
}),
apiType: config.get("pricing.pricingOptions.apiType", {
infer: true,
}),
},
};
},
extraProviders: [Logger],
}),
],
useFactory: (
config: ConfigService<
Pick<
ConfigType,
| "bridgeHubAddress"
| "sharedBridgeAddress"
| "stateTransitionManagerAddresses"
>,
true
>,
) => {
return {
contracts: {
bridgeHub: config.get("bridgeHubAddress")!,
sharedBridge: config.get("sharedBridgeAddress")!,
stateTransitionManager: config.get("stateTransitionManagerAddresses")!,
},
};
},
inject: [ConfigService],
}),
],
controllers: [MetricsController],
providers: [],
providers: [Logger],
})
export class ApiModule implements NestModule {
/**
Expand Down
60 changes: 60 additions & 0 deletions apps/api/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Address } from "abitype";
import Joi from "joi";
import { mainnet, zkSync } from "viem/chains";

export const config = () => ({
l1: {
rpcUrls: process.env.L1_RPC_URLS!.split(","),
chain: mainnet,
},
l2: {
rpcUrls: process.env.L2_RPC_URLS!.split(","),
chain: zkSync,
},
bridgeHubAddress: "0x303a465B659cBB0ab36eE643eA362c509EEb5213" as Address,
sharedBridgeAddress: "0xD7f9f54194C633F36CCD5F3da84ad4a1c38cB2cB" as Address,
stateTransitionManagerAddresses: ["0xc2eE6b6af7d616f6e27ce7F4A451Aedc2b0F5f5C"] as Address[],
pricing: {
cacheOptions: {
ttl: process.env.CACHE_TTL ? parseInt(process.env.CACHE_TTL) : 60,
},
pricingOptions: {
apiKey: process.env.COINGECKO_API_KEY,
apiBaseUrl: process.env.COINGECKO_BASE_URL || "https://api.coingecko.com/api/v3/",
apiType: process.env.COINGECKO_API_TYPE || "demo",
},
},
});

export type ConfigType = ReturnType<typeof config>;

export const validationSchema = Joi.object({
L1_RPC_URLS: Joi.string()
.uri()
.required()
.custom((value, helpers) => {
// Ensure it's a comma-separated list of valid URLs
const urls = value.split(",");
for (const url of urls) {
if (!Joi.string().uri().validate(url).error) continue;
return helpers.message({ custom: `"${url}" is not a valid URL` });
}
return value;
}),
L2_RPC_URLS: Joi.string()
.uri()
.required()
.custom((value, helpers) => {
// Ensure it's a comma-separated list of valid URLs
const urls = value.split(",");
for (const url of urls) {
if (!Joi.string().uri().validate(url).error) continue;
return helpers.message({ custom: `"${url}" is not a valid URL` });
}
return value;
}),
COINGECKO_API_KEY: Joi.string().required(),
COINGECKO_BASE_URL: Joi.string().uri().default("https://api.coingecko.com/api/v3/"),
COINGECKO_API_TYPE: Joi.string().valid("demo", "pro").default("demo"),
CACHE_TTL: Joi.number().positive().default(60),
});
11 changes: 10 additions & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@ import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston";
import { ApiModule } from "./api.module";

async function bootstrap() {
const PORT = process.env.PORT || 3000;
const app = await NestFactory.create(ApiModule);
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
app.enableCors({
origin: "*", // Replace with the origin you want to allow
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
credentials: true,
});

setupOpenApiConfiguration(app);

await app.listen(3000);
const logger = app.get(WINSTON_MODULE_NEST_PROVIDER);

logger.log(`Starting API server on port ${PORT}`);
await app.listen(PORT);
}
bootstrap();
34 changes: 16 additions & 18 deletions apps/api/src/metrics/dto/response/chain.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { ApiPropertyOptional } from "@nestjs/swagger";

import { Chains, ChainType } from "@zkchainhub/shared";
import { AssetTvl } from "@zkchainhub/metrics/types";
import { ChainType, Token } from "@zkchainhub/shared";

import { AssetDistribution, BatchesInfo, FeeParams, L2ChainInfo, Metadata } from ".";
import { BatchesInfo, FeeParams, L2ChainInfo, ZkChainMetadata } from ".";

/**
* ZKChainInfo class representing the ZK chain information.
Expand All @@ -13,30 +14,26 @@ export class ZKChainInfo {
* @type {ChainType}
* @memberof ZKChainInfo
*/
@ApiProperty({ enum: Chains, enumName: "ChainType" })
chainType: ChainType;

/**
* The native token of the chain (optional).
* @type {Token<"erc20" | "native">}
* @memberof ZKChainSummary
*/
baseToken?: Token<"erc20" | "native">;
/**
* A map of asset names to their respective amounts.
* @type {AssetDistribution}
* @type {AssetTvl}
* @memberof ZKChainInfo
* @example { ETH: 1000000, ZK: 500000 }
*/
@ApiProperty({
example: { ETH: 1000000, ZK: 500000 },
description: "A map of asset names to their respective amounts",
additionalProperties: {
type: "number",
},
})
tvl: AssetDistribution;
tvl: AssetTvl[];

/**
* Optional batches information.
* @type {BatchesInfo}
* @memberof ZKChainInfo
*/
@ApiPropertyOptional()
batchesInfo?: BatchesInfo;

/**
Expand All @@ -48,11 +45,11 @@ export class ZKChainInfo {

/**
* Optional metadata.
* @type {Metadata}
* @type {ZkChainMetadata}
* @memberof ZKChainInfo
*/
@ApiPropertyOptional({ type: Metadata })
metadata?: Metadata;
@ApiPropertyOptional({ type: ZkChainMetadata })
metadata?: ZkChainMetadata;

/**
* Optional Layer 2 chain information.
Expand All @@ -65,6 +62,7 @@ export class ZKChainInfo {
constructor(data: ZKChainInfo) {
this.chainType = data.chainType;
this.tvl = data.tvl;
this.baseToken = data.baseToken;
this.batchesInfo = data.batchesInfo;
this.feeParams = data.feeParams;
this.metadata = data.metadata;
Expand Down
Loading

0 comments on commit 01eba7d

Please sign in to comment.