diff --git a/packages/config/Makefile b/packages/config/Makefile index 0ff3ab666..52e3c2099 100644 --- a/packages/config/Makefile +++ b/packages/config/Makefile @@ -26,8 +26,8 @@ test: ## Run tests lint: ## Run lint $(call npm, run lint) -build: NODE_ENV = "production" .PHONY: build +build: NODE_ENV = "production" build: ## Run build $(call npm, run build) diff --git a/packages/config/README.md b/packages/config/README.md index 712c73265..a0e4de3d5 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -13,9 +13,9 @@ npm install --save @streamr/config ## Examples Import DATA token production Ethereum address as a variable in a Typescript project: ```typescript -import { Chains, loadConfig } from "index" +import { Chains } from "index" -const config: Chains = loadConfig("production") +const config: Chains = Chains.load("production") const contractAddress: string = config.ethereum.contracts["DATA-token"] const chainId: number = config.ethereum.id const rpcHttpUrl: string = config.ethereum.rpcHttpUrl diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 88a632390..6d0a5bc9d 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,28 +1,104 @@ import networksAsJSON from "./networks.json" -export interface Contracts { - [name: string]: string +interface ContractsJSON { + readonly [name: string]: string +} +export class Contracts implements ContractsJSON { + [name: string]: string +} + +export enum RPCProtocol { + HTTP, + WEBSOCKET } -export interface Chain { - id: number - rpcHttpUrl: string - rpcWsUrl: string - contracts: Contracts +interface RPCEndpointJSON { + readonly url: string } -export interface Chains { - [name: string]: Chain +export class RPCEndpoint implements RPCEndpointJSON { + constructor( + readonly url: string, + //readonly readTimeoutSecond: int, + //readonly writeTimeoutSecond: int, + ) {} +} + +interface ChainJSON { + readonly id: number + readonly rpcEndpoints: RPCEndpointJSON[] + readonly contracts: ContractsJSON +} + +export class Chain implements ChainJSON { + constructor( + public readonly id: number, + public rpcEndpoints: RPCEndpoint[], + public contracts: Contracts, + ) { + this.id = id + this.rpcEndpoints = new Array() + for (const rpcEndpoint of rpcEndpoints) { + this.rpcEndpoints.push(new RPCEndpoint(rpcEndpoint.url)) + } + this.contracts = new Contracts() + for (const key of Object.keys(contracts)) { + this.contracts[key] = contracts[key] + } + } + getRPCEndpointsByProtocol(protocol: RPCProtocol): RPCEndpoint[] { + const endpoints = new Array() + for (const rpcEndpoint of this.rpcEndpoints) { + if (protocol === RPCProtocol.HTTP) { + if (rpcEndpoint.url.startsWith("https://") || rpcEndpoint.url.startsWith("http://")) { + endpoints.push(new RPCEndpoint(rpcEndpoint.url)) + } + } else if (protocol === RPCProtocol.WEBSOCKET) { + if (rpcEndpoint.url.startsWith("wss://") || rpcEndpoint.url.startsWith("ws://")) { + endpoints.push(new RPCEndpoint(rpcEndpoint.url)) + } + } + } + return endpoints + } } export type Environment = "development" | "production" -export type Networks = { - [env in Environment]: Chains +type NetworksJSON = { + readonly [env in Environment]: ChainsJSON +} + +interface ChainsJSON { + readonly [name: string]: ChainJSON +} + +export class Chains implements ChainsJSON { + [name: string]: Chain + public static load(env: Environment): Chains { + const networks: NetworksJSON = networksAsJSON + const chainsJson: ChainsJSON = networks[env] + const chains: Chains = ChainsFactory.create(chainsJson) + return chains + } } -export const loadConfig = (env: Environment): Chains => { - const networks: Networks = networksAsJSON - const chain: Chains = networks[env] - return chain +class ChainsFactory { + private constructor() {} + static create(chainsJson: ChainsJSON): Chains { + const chains = new Chains() + for (const key in chainsJson) { + const chainJson: ChainJSON = chainsJson[key] + const rpcEndpoints = new Array() + for (const rpcEndpoint of chainJson.rpcEndpoints) { + rpcEndpoints.push(new RPCEndpoint(rpcEndpoint.url)) + } + const contracts = new Contracts() + for (const key of Object.keys(chainJson.contracts)) { + contracts[key] = chainJson.contracts[key] + } + chains[key] = new Chain(chainJson.id, rpcEndpoints, contracts) + } + return chains + } } diff --git a/packages/config/src/networks.json b/packages/config/src/networks.json index 2745f0f7d..86ecf7f5d 100644 --- a/packages/config/src/networks.json +++ b/packages/config/src/networks.json @@ -2,8 +2,14 @@ "development": { "ethereum": { "id": 8995, - "rpcHttpUrl": "http://10.200.10.1:8545", - "rpcWsUrl": "ws://10.200.10.1:8545", + "rpcEndpoints": [ + { + "url": "http://10.200.10.1:8545" + }, + { + "url": "ws://10.200.10.1:8545" + } + ], "contracts": { "DATA-token": "0xbAA81A0179015bE47Ad439566374F2Bae098686F", "XDATA-token": "0x6d0F3bF9aD2455b4F62f22fFD21317e1E3eEFE5C", @@ -24,8 +30,14 @@ }, "streamr": { "id": 8997, - "rpcHttpUrl": "http://10.200.10.1:8546", - "rpcWsUrl": "ws://10.200.10.1:8546", + "rpcEndpoints": [ + { + "url": "http://10.200.10.1:8546" + }, + { + "url": "ws://10.200.10.1:8546" + } + ], "contracts": { "Token": "0x73Be21733CC5D08e1a14Ea9a399fb27DB3BEf8fF", "Mediator": "0xedD2aa644a6843F2e5133Fe3d6BD3F4080d97D9F", @@ -42,8 +54,7 @@ "production": { "ethereum": { "id": 1, - "rpcHttpUrl": "", - "rpcWsUrl": "", + "rpcEndpoints": [], "contracts": { "DATA-token": "0x8f693ca8D21b157107184d29D398A8D082b38b76", "XDATA-token": "0x0cf0ee63788a0849fe5297f3407f701e122cc023", @@ -62,8 +73,14 @@ }, "gnosis": { "id": 100, - "rpcHttpUrl": "https://rpc.gnosischain.com", - "rpcWsUrl": "wss://rpc.gnosischain.com/wss", + "rpcEndpoints": [ + { + "url": "https://rpc.gnosischain.com" + }, + { + "url": "wss://rpc.gnosischain.com/wss" + } + ], "contracts": { "XDATA-token": "0xE4a2620edE1058D61BEe5F45F6414314fdf10548", "DATA-token": "0x256eb8a51f382650B2A1e946b8811953640ee47D", @@ -80,8 +97,16 @@ }, "binance": { "id": 56, - "rpcHttpUrl": "https://bsc-dataseed.binance.org", - "rpcWsUrl": "wss://bsc-dataseed.binance.org", + "rpcHttpUrl": "", + "rpcWsUrl": "", + "rpcEndpoints": [ + { + "url": "https://bsc-dataseed.binance.org" + }, + { + "url": "wss://bsc-dataseed.binance.org" + } + ], "contracts": { "DATA-token": "0x0864c156b3c5f69824564dec60c629ae6401bf2a", "xdaiBridge": "0xa93ee7b4a7215f7e725437a6b6d7a4e7fe1dd8f0" @@ -89,8 +114,14 @@ }, "polygon": { "id": 137, - "rpcHttpUrl": "https://polygon-rpc.com", - "rpcWsUrl": "wss://polygon-rpc.com", + "rpcEndpoints": [ + { + "url": "https://polygon-rpc.com" + }, + { + "url": "wss://polygon-rpc.com" + } + ], "contracts": { "DATA-token": "0x3a9A81d576d83FF21f26f325066054540720fC34", "StorageNodeRegistry": "0x080F34fec2bc33928999Ea9e39ADc798bEF3E0d6", diff --git a/packages/config/test/index.test.ts b/packages/config/test/index.test.ts index 708f3a9df..e788f07aa 100644 --- a/packages/config/test/index.test.ts +++ b/packages/config/test/index.test.ts @@ -1,30 +1,36 @@ import { describe, it } from "mocha" import { assert } from "chai" -import { Chains, loadConfig } from "../src/index" +import { Chains, RPCProtocol } from "../src/index" describe("Load configuration from JSON file", () => { it("ethereum chain id is 1", () => { - const config: Chains = loadConfig("production") - const chainId: number = config.ethereum.id + const chains: Chains = Chains.load("production") + const chainId: number = chains.ethereum.id const expected = 1 assert.equal(chainId, expected, `Expecting ethereum prod chain id to equal ${expected}, got '${chainId}'`) }) it("development chain id is 8995", () => { - const config: Chains = loadConfig("development") - const chainId: number = config.ethereum.id + const chains: Chains = Chains.load("development") + const chainId: number = chains.ethereum.id const expected = 8995 assert.equal(chainId, expected, `Expecting ethereum dev chain id to equal ${expected}, got '${chainId}'`) }) it("reads DATA token dev address from JSON", () => { - const config: Chains = loadConfig("development") - const address = config.ethereum.contracts["DATA-token"] + const chains: Chains = Chains.load("development") + const address = chains.ethereum.contracts["DATA-token"] const expected = "0xbAA81A0179015bE47Ad439566374F2Bae098686F" assert.equal(address, expected, `Expecting ethereum DATA token to equal ${expected}, got '${address}'`) }) it("reads prod Polygon RPC URL", () => { - const config: Chains = loadConfig("production") - const rpcHttpUrl = config.polygon.rpcHttpUrl + const chains: Chains = Chains.load("production") + const rpcHttpUrl = chains.polygon.rpcEndpoints[0].url const expected = "https://polygon-rpc.com" assert.equal(rpcHttpUrl, expected, `Expecting prod polygon RPC URL to equal ${expected}, got '${rpcHttpUrl}'`) }) + it("finds RPC endpoints by protocol", () => { + const chains: Chains = Chains.load("production") + const endpoints = chains.binance.getRPCEndpointsByProtocol(RPCProtocol.HTTP) + assert.equal(endpoints.length, 1) + assert.equal(endpoints[0].url, "https://bsc-dataseed.binance.org") + }) }) \ No newline at end of file