Skip to content

Commit 533ded9

Browse files
authored
feat(config): allow multiple RPC addresses (#201)
* feat: replace rpc{Http,Ws}Url variables with RpcEndpoint[] * refactor: Separate JSON interfaces from implementation - Rename: loadConfig(env) to Chains.load(env) - Rename: use capital case for acronym classes - RpcEndpoint -> RPCEndpoint - RpcProtocol -> RPCProtocol - RpcEndpoint -> RPCEndpointJSON - Add Chains.getRPCEndpointsByProtocol(RPCProtocol): RPCEndpoint[] - Network -> NetworkJSON - Chain -> ChainJSON - Add classes: - Chain - Network - ChainsFactory - RPCEndpoint - Use concrete classes that implement JSON interfaces in public API - Use interfaces for parsing JSON file
1 parent 82d319a commit 533ded9

File tree

5 files changed

+152
-39
lines changed

5 files changed

+152
-39
lines changed

packages/config/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ test: ## Run tests
2626
lint: ## Run lint
2727
$(call npm, run lint)
2828

29-
build: NODE_ENV = "production"
3029
.PHONY: build
30+
build: NODE_ENV = "production"
3131
build: ## Run build
3232
$(call npm, run build)
3333

packages/config/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ npm install --save @streamr/config
1313
## Examples
1414
Import DATA token production Ethereum address as a variable in a Typescript project:
1515
```typescript
16-
import { Chains, loadConfig } from "index"
16+
import { Chains } from "index"
1717

18-
const config: Chains = loadConfig("production")
18+
const config: Chains = Chains.load("production")
1919
const contractAddress: string = config.ethereum.contracts["DATA-token"]
2020
const chainId: number = config.ethereum.id
2121
const rpcHttpUrl: string = config.ethereum.rpcHttpUrl

packages/config/src/index.ts

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,104 @@
11
import networksAsJSON from "./networks.json"
22

3-
export interface Contracts {
4-
[name: string]: string
3+
interface ContractsJSON {
4+
readonly [name: string]: string
5+
}
6+
export class Contracts implements ContractsJSON {
7+
[name: string]: string
8+
}
9+
10+
export enum RPCProtocol {
11+
HTTP,
12+
WEBSOCKET
513
}
614

7-
export interface Chain {
8-
id: number
9-
rpcHttpUrl: string
10-
rpcWsUrl: string
11-
contracts: Contracts
15+
interface RPCEndpointJSON {
16+
readonly url: string
1217
}
1318

14-
export interface Chains {
15-
[name: string]: Chain
19+
export class RPCEndpoint implements RPCEndpointJSON {
20+
constructor(
21+
readonly url: string,
22+
//readonly readTimeoutSecond: int,
23+
//readonly writeTimeoutSecond: int,
24+
) {}
25+
}
26+
27+
interface ChainJSON {
28+
readonly id: number
29+
readonly rpcEndpoints: RPCEndpointJSON[]
30+
readonly contracts: ContractsJSON
31+
}
32+
33+
export class Chain implements ChainJSON {
34+
constructor(
35+
public readonly id: number,
36+
public rpcEndpoints: RPCEndpoint[],
37+
public contracts: Contracts,
38+
) {
39+
this.id = id
40+
this.rpcEndpoints = new Array<RPCEndpoint>()
41+
for (const rpcEndpoint of rpcEndpoints) {
42+
this.rpcEndpoints.push(new RPCEndpoint(rpcEndpoint.url))
43+
}
44+
this.contracts = new Contracts()
45+
for (const key of Object.keys(contracts)) {
46+
this.contracts[key] = contracts[key]
47+
}
48+
}
49+
getRPCEndpointsByProtocol(protocol: RPCProtocol): RPCEndpoint[] {
50+
const endpoints = new Array<RPCEndpoint>()
51+
for (const rpcEndpoint of this.rpcEndpoints) {
52+
if (protocol === RPCProtocol.HTTP) {
53+
if (rpcEndpoint.url.startsWith("https://") || rpcEndpoint.url.startsWith("http://")) {
54+
endpoints.push(new RPCEndpoint(rpcEndpoint.url))
55+
}
56+
} else if (protocol === RPCProtocol.WEBSOCKET) {
57+
if (rpcEndpoint.url.startsWith("wss://") || rpcEndpoint.url.startsWith("ws://")) {
58+
endpoints.push(new RPCEndpoint(rpcEndpoint.url))
59+
}
60+
}
61+
}
62+
return endpoints
63+
}
1664
}
1765

1866
export type Environment = "development" | "production"
1967

20-
export type Networks = {
21-
[env in Environment]: Chains
68+
type NetworksJSON = {
69+
readonly [env in Environment]: ChainsJSON
70+
}
71+
72+
interface ChainsJSON {
73+
readonly [name: string]: ChainJSON
74+
}
75+
76+
export class Chains implements ChainsJSON {
77+
[name: string]: Chain
78+
public static load(env: Environment): Chains {
79+
const networks: NetworksJSON = networksAsJSON
80+
const chainsJson: ChainsJSON = networks[env]
81+
const chains: Chains = ChainsFactory.create(chainsJson)
82+
return chains
83+
}
2284
}
2385

24-
export const loadConfig = (env: Environment): Chains => {
25-
const networks: Networks = networksAsJSON
26-
const chain: Chains = networks[env]
27-
return chain
86+
class ChainsFactory {
87+
private constructor() {}
88+
static create(chainsJson: ChainsJSON): Chains {
89+
const chains = new Chains()
90+
for (const key in chainsJson) {
91+
const chainJson: ChainJSON = chainsJson[key]
92+
const rpcEndpoints = new Array<RPCEndpoint>()
93+
for (const rpcEndpoint of chainJson.rpcEndpoints) {
94+
rpcEndpoints.push(new RPCEndpoint(rpcEndpoint.url))
95+
}
96+
const contracts = new Contracts()
97+
for (const key of Object.keys(chainJson.contracts)) {
98+
contracts[key] = chainJson.contracts[key]
99+
}
100+
chains[key] = new Chain(chainJson.id, rpcEndpoints, contracts)
101+
}
102+
return chains
103+
}
28104
}

packages/config/src/networks.json

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
"development": {
33
"ethereum": {
44
"id": 8995,
5-
"rpcHttpUrl": "http://10.200.10.1:8545",
6-
"rpcWsUrl": "ws://10.200.10.1:8545",
5+
"rpcEndpoints": [
6+
{
7+
"url": "http://10.200.10.1:8545"
8+
},
9+
{
10+
"url": "ws://10.200.10.1:8545"
11+
}
12+
],
713
"contracts": {
814
"DATA-token": "0xbAA81A0179015bE47Ad439566374F2Bae098686F",
915
"XDATA-token": "0x6d0F3bF9aD2455b4F62f22fFD21317e1E3eEFE5C",
@@ -24,8 +30,14 @@
2430
},
2531
"streamr": {
2632
"id": 8997,
27-
"rpcHttpUrl": "http://10.200.10.1:8546",
28-
"rpcWsUrl": "ws://10.200.10.1:8546",
33+
"rpcEndpoints": [
34+
{
35+
"url": "http://10.200.10.1:8546"
36+
},
37+
{
38+
"url": "ws://10.200.10.1:8546"
39+
}
40+
],
2941
"contracts": {
3042
"Token": "0x73Be21733CC5D08e1a14Ea9a399fb27DB3BEf8fF",
3143
"Mediator": "0xedD2aa644a6843F2e5133Fe3d6BD3F4080d97D9F",
@@ -42,8 +54,7 @@
4254
"production": {
4355
"ethereum": {
4456
"id": 1,
45-
"rpcHttpUrl": "",
46-
"rpcWsUrl": "",
57+
"rpcEndpoints": [],
4758
"contracts": {
4859
"DATA-token": "0x8f693ca8D21b157107184d29D398A8D082b38b76",
4960
"XDATA-token": "0x0cf0ee63788a0849fe5297f3407f701e122cc023",
@@ -62,8 +73,14 @@
6273
},
6374
"gnosis": {
6475
"id": 100,
65-
"rpcHttpUrl": "https://rpc.gnosischain.com",
66-
"rpcWsUrl": "wss://rpc.gnosischain.com/wss",
76+
"rpcEndpoints": [
77+
{
78+
"url": "https://rpc.gnosischain.com"
79+
},
80+
{
81+
"url": "wss://rpc.gnosischain.com/wss"
82+
}
83+
],
6784
"contracts": {
6885
"XDATA-token": "0xE4a2620edE1058D61BEe5F45F6414314fdf10548",
6986
"DATA-token": "0x256eb8a51f382650B2A1e946b8811953640ee47D",
@@ -80,17 +97,31 @@
8097
},
8198
"binance": {
8299
"id": 56,
83-
"rpcHttpUrl": "https://bsc-dataseed.binance.org",
84-
"rpcWsUrl": "wss://bsc-dataseed.binance.org",
100+
"rpcHttpUrl": "",
101+
"rpcWsUrl": "",
102+
"rpcEndpoints": [
103+
{
104+
"url": "https://bsc-dataseed.binance.org"
105+
},
106+
{
107+
"url": "wss://bsc-dataseed.binance.org"
108+
}
109+
],
85110
"contracts": {
86111
"DATA-token": "0x0864c156b3c5f69824564dec60c629ae6401bf2a",
87112
"xdaiBridge": "0xa93ee7b4a7215f7e725437a6b6d7a4e7fe1dd8f0"
88113
}
89114
},
90115
"polygon": {
91116
"id": 137,
92-
"rpcHttpUrl": "https://polygon-rpc.com",
93-
"rpcWsUrl": "wss://polygon-rpc.com",
117+
"rpcEndpoints": [
118+
{
119+
"url": "https://polygon-rpc.com"
120+
},
121+
{
122+
"url": "wss://polygon-rpc.com"
123+
}
124+
],
94125
"contracts": {
95126
"DATA-token": "0x3a9A81d576d83FF21f26f325066054540720fC34",
96127
"StorageNodeRegistry": "0x080F34fec2bc33928999Ea9e39ADc798bEF3E0d6",

packages/config/test/index.test.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,36 @@
11
import { describe, it } from "mocha"
22
import { assert } from "chai"
3-
import { Chains, loadConfig } from "../src/index"
3+
import { Chains, RPCProtocol } from "../src/index"
44

55
describe("Load configuration from JSON file", () => {
66
it("ethereum chain id is 1", () => {
7-
const config: Chains = loadConfig("production")
8-
const chainId: number = config.ethereum.id
7+
const chains: Chains = Chains.load("production")
8+
const chainId: number = chains.ethereum.id
99
const expected = 1
1010
assert.equal(chainId, expected, `Expecting ethereum prod chain id to equal ${expected}, got '${chainId}'`)
1111
})
1212
it("development chain id is 8995", () => {
13-
const config: Chains = loadConfig("development")
14-
const chainId: number = config.ethereum.id
13+
const chains: Chains = Chains.load("development")
14+
const chainId: number = chains.ethereum.id
1515
const expected = 8995
1616
assert.equal(chainId, expected, `Expecting ethereum dev chain id to equal ${expected}, got '${chainId}'`)
1717
})
1818
it("reads DATA token dev address from JSON", () => {
19-
const config: Chains = loadConfig("development")
20-
const address = config.ethereum.contracts["DATA-token"]
19+
const chains: Chains = Chains.load("development")
20+
const address = chains.ethereum.contracts["DATA-token"]
2121
const expected = "0xbAA81A0179015bE47Ad439566374F2Bae098686F"
2222
assert.equal(address, expected, `Expecting ethereum DATA token to equal ${expected}, got '${address}'`)
2323
})
2424
it("reads prod Polygon RPC URL", () => {
25-
const config: Chains = loadConfig("production")
26-
const rpcHttpUrl = config.polygon.rpcHttpUrl
25+
const chains: Chains = Chains.load("production")
26+
const rpcHttpUrl = chains.polygon.rpcEndpoints[0].url
2727
const expected = "https://polygon-rpc.com"
2828
assert.equal(rpcHttpUrl, expected, `Expecting prod polygon RPC URL to equal ${expected}, got '${rpcHttpUrl}'`)
2929
})
30+
it("finds RPC endpoints by protocol", () => {
31+
const chains: Chains = Chains.load("production")
32+
const endpoints = chains.binance.getRPCEndpointsByProtocol(RPCProtocol.HTTP)
33+
assert.equal(endpoints.length, 1)
34+
assert.equal(endpoints[0].url, "https://bsc-dataseed.binance.org")
35+
})
3036
})

0 commit comments

Comments
 (0)