Skip to content

Commit

Permalink
feat: github metadata provider (#54)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes ZKS-205

## Description

Implement GithubMetadataProvider
  • Loading branch information
0xnigir1 authored Aug 23, 2024
1 parent 66359db commit 41731c6
Show file tree
Hide file tree
Showing 19 changed files with 375 additions and 24 deletions.
2 changes: 1 addition & 1 deletion apps/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inspect } from "util";
import { caching } from "cache-manager";

import { EvmProvider } from "@zkchainhub/chain-providers/dist/src/index.js";
import { EvmProvider } from "@zkchainhub/chain-providers";
import { L1MetricsService } from "@zkchainhub/metrics";
import { CoingeckoProvider } from "@zkchainhub/pricing";
import { Logger } from "@zkchainhub/shared";
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/metrics/dto/response/metadata.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class ZkChainMetadata {
* @type {string}
* @memberof Metadata
*/
iconUrl: string;
iconUrl?: string;

/**
* The name of the chain.
Expand All @@ -28,7 +28,7 @@ export class ZkChainMetadata {
* @type {string}
* @memberof Metadata
*/
explorerUrl: string;
explorerUrl?: string;

/**
* The launch date of the chain (timestamp).
Expand Down
7 changes: 6 additions & 1 deletion packages/metadata/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
"test:cov": "vitest run --config vitest.config.ts --coverage"
},
"dependencies": {
"@zkchainhub/shared": "workspace:*"
"@zkchainhub/shared": "workspace:*",
"axios": "1.7.4",
"zod": "3.23.8"
},
"devDependencies": {
"axios-mock-adapter": "2.0.0"
}
}
6 changes: 6 additions & 0 deletions packages/metadata/src/exceptions/fetchError.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class FetchError extends Error {
constructor(message: string) {
super(message);
this.name = "FetchError";
}
}
2 changes: 2 additions & 0 deletions packages/metadata/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./invalidSchema.exception.js";
export * from "./fetchError.exception.js";
6 changes: 6 additions & 0 deletions packages/metadata/src/exceptions/invalidSchema.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class InvalidSchema extends Error {
constructor(message: string) {
super(message);
this.name = "InvalidSchema";
}
}
2 changes: 2 additions & 0 deletions packages/metadata/src/external.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type { IMetadataProvider } from "./internal.js";

export { InvalidSchema, FetchError } from "./internal.js";

export { StaticMetadataProvider, GithubMetadataProvider } from "./internal.js";
1 change: 1 addition & 0 deletions packages/metadata/src/internal.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./interfaces/index.js";
export * from "./providers/index.js";
export * from "./exceptions/index.js";
68 changes: 63 additions & 5 deletions packages/metadata/src/providers/githubMetadata.provider.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,73 @@
import { Token, TokenType, ZKChainMetadata } from "@zkchainhub/shared";
import axios, { AxiosInstance } from "axios";
import { z } from "zod";

import {
ILogger,
Token,
TokenType,
ZKChainMetadata,
ZKChainMetadataItem,
} from "@zkchainhub/shared";

import { IMetadataProvider } from "../interfaces/index.js";
import { FetchError, InvalidSchema } from "../internal.js";
import { ChainSchema, TokenSchema } from "../schemas/index.js";

/**
* Represents a provider for retrieving metadata from GitHub.
*/
export class GithubMetadataProvider implements IMetadataProvider {
private readonly axios: AxiosInstance;
constructor(
private readonly tokenJsonUrl: string,
private readonly chainJsonUrl: string,
private readonly logger: ILogger,
) {
this.axios = axios.create({
headers: {
Accept: "application/json",
},
});
}

async getChainsMetadata(): Promise<ZKChainMetadata> {
//TODO: Implement this method
throw new Error("Method not implemented.");
const { data } = await this.axios.get(this.chainJsonUrl).catch((e) => {
this.logger.error(
`Failed to fetch chains metadata from ${this.chainJsonUrl}: ${e.message}`,
);
throw new FetchError(`Failed to fetch chains metadata: ${e.message}`);
});

const validatedData = z.array(ChainSchema).safeParse(data);

if (!validatedData.success) {
this.logger.error(`Invalid ZKChain metadata: ${validatedData.error.errors}`);
throw new InvalidSchema("Invalid ZKChain metadata");
}

return validatedData.data.reduce((acc, chain) => {
const { chainId, ...rest } = chain;
const chainIdBn = BigInt(chainId);
acc.set(chainIdBn, { ...rest, chainId: chainIdBn });
return acc;
}, new Map<bigint, ZKChainMetadataItem>());
}

async getTokensMetadata(): Promise<Token<TokenType>[]> {
//TODO: Implement this method
throw new Error("Method not implemented.");
const { data } = await this.axios.get(this.tokenJsonUrl).catch((e) => {
this.logger.error(
`Failed to fetch chains metadata from ${this.chainJsonUrl}: ${e.message}`,
);
throw new FetchError(`Failed to fetch chains metadata: ${e.message}`);
});

const validatedData = z.array(TokenSchema).safeParse(data);

if (!validatedData.success) {
this.logger.error(`Invalid Token metadata: ${validatedData.error.errors}`);
throw new InvalidSchema("Invalid Token metadata");
}

return validatedData.data;
}
}
28 changes: 28 additions & 0 deletions packages/metadata/src/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { z } from "zod";

import { Address } from "@zkchainhub/shared";

export const TokenSchema = z.object({
name: z.string(),
symbol: z.string(),
coingeckoId: z.string(), // FIXME: on pricing refactor, this should not be part of the token metadata
type: z.union([z.literal("erc20"), z.literal("native")]),
contractAddress: z
.custom<Address>((val) => {
return typeof val === "string" && /^0x[a-fA-F0-9]{40}$/.test(val);
}, "Invalid Ethereum address")
.nullable(),
decimals: z.number(),
imageUrl: z.string().optional(),
});

export const ChainSchema = z.object({
chainId: z.number().positive(),
name: z.string(),
iconUrl: z.string().url().optional(),
publicRpcs: z.array(z.string().url()).default([]),
explorerUrl: z.string().url().optional(),
launchDate: z.number().positive(),
chainType: z.union([z.literal("Rollup"), z.literal("Validium")]),
baseToken: TokenSchema,
});
65 changes: 65 additions & 0 deletions packages/metadata/test/fixtures/metadata.fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export const tokenJsonUrl = "https://example.com/tokens.json";
export const chainJsonUrl = "https://example.com/chains.json";
export const mockTokenData = [
{
name: "Ethereum",
symbol: "ETH",
contractAddress: null,
coingeckoId: "ethereum",
type: "native",
imageUrl:
"https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628",
decimals: 18,
},
{
name: "Wrapped Ether",
symbol: "WETH",
contractAddress: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
coingeckoId: "weth",
imageUrl: "https://coin-images.coingecko.com/coins/images/2518/large/weth.png?1696503332",
type: "erc20",
decimals: 18,
},
];
export const mockChainData = [
{
chainId: 324,
name: "ZKsyncERA",
iconUrl: "https://s2.coinmarketcap.com/static/img/coins/64x64/24091.png",
publicRpcs: [
"https://mainnet.era.zksync.io",
"https://zksync.drpc.org",
"https://zksync.meowrpc.com",
],
explorerUrl: "https://explorer.zksync.io/",
launchDate: 1679626800,
chainType: "Rollup",
baseToken: {
name: "Ethereum",
symbol: "ETH",
contractAddress: null,
coingeckoId: "ethereum",
type: "native",
imageUrl:
"https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628",
decimals: 18,
},
},
{
chainId: 388,
name: "Cronos",
chainType: "Validium",
publicRpcs: ["https://mainnet.zkevm.cronos.org"],
explorerUrl: "https://explorer.zkevm.cronos.org/",
baseToken: {
symbol: "zkCRO",
name: "zkCRO",
contractAddress: "0x28Ff2E4dD1B58efEB0fC138602A28D5aE81e44e2",
coingeckoId: "unknown",
type: "erc20",
imageUrl: "https://zkevm.cronos.org/images/chains/zkevm.svg",
decimals: 18,
},
launchDate: 1679626800,
},
];
Loading

0 comments on commit 41731c6

Please sign in to comment.