Skip to content

Commit e090e1e

Browse files
authored
feat: custom rpc support, some fixes, validate duplicate storage resources (#1111)
* feat: add custom rpc * add no duplicate storage resource validator * use base64 for secrets * fix circular deps * Apply automatic changes * use password input * improve message * add url validator * fix incorrect config updates * add some debug statements * use application/merge-patch+json --------- Co-authored-by: shamsartem <[email protected]>
1 parent fe9de38 commit e090e1e

File tree

15 files changed

+525
-102
lines changed

15 files changed

+525
-102
lines changed

packages/cli/package/docs/configs/env.md

Lines changed: 114 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,124 @@ Defines project user's preferences
44

55
## Properties
66

7-
| Property | Type | Required | Description |
8-
|-----------------|-----------------------|----------|------------------------------------------------------------------------------------------------|
9-
| `version` | integer | **Yes** | Config version |
10-
| `blockScoutUrl` | string | No | BlockScout URL to use |
11-
| `chainId` | number | No | Chain ID to use |
12-
| `deployment` | [object](#deployment) | No | Deployed contract address overrides |
13-
| `fluenceEnv` | string | No | Fluence environment to connect to Possible values are: `testnet`, `mainnet`, `stage`, `local`. |
14-
| `ipfsGateway` | string | No | IPFS gateway URL to use |
15-
| `relays` | string[] | No | List of custom relay multiaddresses to use when connecting to Fluence network |
16-
| `rpcUrl` | string | No | RPC URL to use |
17-
| `subgraphUrl` | string | No | Subgraph URL to use |
18-
19-
## deployment
7+
| Property | Type | Required | Description |
8+
|----------------|-------------------------|----------|------------------------------------------------------------------------------------------------|
9+
| `version` | integer | **Yes** | Config version |
10+
| `fluenceEnv` | string | No | Fluence environment to connect to Possible values are: `testnet`, `mainnet`, `stage`, `local`. |
11+
| `perEnvConfig` | [object](#perenvconfig) | No | |
2012

21-
Deployed contract address overrides
13+
## perEnvConfig
2214

2315
### Properties
2416

17+
| Property | Type | Required | Description |
18+
|-----------|--------------------|----------|-------------|
19+
| `local` | [object](#local) | No | |
20+
| `mainnet` | [object](#mainnet) | No | |
21+
| `stage` | [object](#stage) | No | |
22+
| `testnet` | [object](#testnet) | No | |
23+
24+
### local
25+
26+
#### Properties
27+
28+
| Property | Type | Required | Description |
29+
|-----------------|-----------------------|----------|-------------------------------------|
30+
| `blockScoutUrl` | string | No | BlockScout URL to use |
31+
| `chainId` | number | No | Chain ID to use |
32+
| `deployment` | [object](#deployment) | No | Deployed contract address overrides |
33+
| `ipfsGateway` | string | No | IPFS gateway URL to use |
34+
| `rpcHttpUrl` | string | No | RPC HTTP URL to use |
35+
| `rpcWsUrl` | string | No | RPC WS URL to use |
36+
| `subgraphUrl` | string | No | Subgraph URL to use |
37+
38+
#### deployment
39+
40+
Deployed contract address overrides
41+
42+
##### Properties
43+
44+
| Property | Type | Required | Description |
45+
|-----------------|--------|----------|-------------|
46+
| `balanceKeeper` | string | No | |
47+
| `diamond` | string | No | |
48+
| `multicall3` | string | No | |
49+
| `usdc` | string | No | |
50+
51+
### mainnet
52+
53+
#### Properties
54+
55+
| Property | Type | Required | Description |
56+
|-----------------|-----------------------|----------|-------------------------------------|
57+
| `blockScoutUrl` | string | No | BlockScout URL to use |
58+
| `chainId` | number | No | Chain ID to use |
59+
| `deployment` | [object](#deployment) | No | Deployed contract address overrides |
60+
| `ipfsGateway` | string | No | IPFS gateway URL to use |
61+
| `rpcHttpUrl` | string | No | RPC HTTP URL to use |
62+
| `rpcWsUrl` | string | No | RPC WS URL to use |
63+
| `subgraphUrl` | string | No | Subgraph URL to use |
64+
65+
#### deployment
66+
67+
Deployed contract address overrides
68+
69+
##### Properties
70+
71+
| Property | Type | Required | Description |
72+
|-----------------|--------|----------|-------------|
73+
| `balanceKeeper` | string | No | |
74+
| `diamond` | string | No | |
75+
| `multicall3` | string | No | |
76+
| `usdc` | string | No | |
77+
78+
### stage
79+
80+
#### Properties
81+
82+
| Property | Type | Required | Description |
83+
|-----------------|-----------------------|----------|-------------------------------------|
84+
| `blockScoutUrl` | string | No | BlockScout URL to use |
85+
| `chainId` | number | No | Chain ID to use |
86+
| `deployment` | [object](#deployment) | No | Deployed contract address overrides |
87+
| `ipfsGateway` | string | No | IPFS gateway URL to use |
88+
| `rpcHttpUrl` | string | No | RPC HTTP URL to use |
89+
| `rpcWsUrl` | string | No | RPC WS URL to use |
90+
| `subgraphUrl` | string | No | Subgraph URL to use |
91+
92+
#### deployment
93+
94+
Deployed contract address overrides
95+
96+
##### Properties
97+
98+
| Property | Type | Required | Description |
99+
|-----------------|--------|----------|-------------|
100+
| `balanceKeeper` | string | No | |
101+
| `diamond` | string | No | |
102+
| `multicall3` | string | No | |
103+
| `usdc` | string | No | |
104+
105+
### testnet
106+
107+
#### Properties
108+
109+
| Property | Type | Required | Description |
110+
|-----------------|-----------------------|----------|-------------------------------------|
111+
| `blockScoutUrl` | string | No | BlockScout URL to use |
112+
| `chainId` | number | No | Chain ID to use |
113+
| `deployment` | [object](#deployment) | No | Deployed contract address overrides |
114+
| `ipfsGateway` | string | No | IPFS gateway URL to use |
115+
| `rpcHttpUrl` | string | No | RPC HTTP URL to use |
116+
| `rpcWsUrl` | string | No | RPC WS URL to use |
117+
| `subgraphUrl` | string | No | Subgraph URL to use |
118+
119+
#### deployment
120+
121+
Deployed contract address overrides
122+
123+
##### Properties
124+
25125
| Property | Type | Required | Description |
26126
|-----------------|--------|----------|-------------|
27127
| `balanceKeeper` | string | No | |

packages/cli/package/src/lib/chain/chainConfig.ts

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@ import capitalize from "lodash-es/capitalize.js";
1919

2020
import type { ChainENV } from "../../common.js";
2121
import { commandObj } from "../commandObj.js";
22-
import { initEnvConfig } from "../configs/project/env/env.js";
22+
import {
23+
CHAIN_RPC_CONTAINER_NAME,
24+
CHAIN_RPC_PORT,
25+
} from "../configs/project/chainContainers.js";
26+
import { initEnvConfig, initNewEnvConfig } from "../configs/project/env/env.js";
2327
import { dbg } from "../dbg.js";
2428
import { ensureChainEnv } from "../ensureChainNetwork.js";
2529
import { numToStr } from "../helpers/typesafeStringify.js";
30+
import { urlValidator } from "../helpers/validations.js";
31+
import { password } from "../prompt.js";
2632

2733
let chainIdPromise: Promise<number> | undefined;
2834

@@ -34,12 +40,14 @@ export async function getChainId() {
3440
let chainId: number = CHAIN_IDS[chainEnv];
3541
const envConfig = await initEnvConfig();
3642

37-
if (envConfig?.chainId !== undefined) {
43+
if (envConfig?.perEnvConfig?.[chainEnv]?.chainId !== undefined) {
44+
const customChainId = envConfig.perEnvConfig[chainEnv].chainId;
45+
3846
commandObj.logToStderr(
39-
`Using custom chain ID: ${numToStr(envConfig.chainId)} from ${envConfig.$getPath()}`,
47+
`Using custom chain ID: ${numToStr(customChainId)} from ${envConfig.$getPath()}`,
4048
);
4149

42-
chainId = envConfig.chainId;
50+
chainId = customChainId;
4351
}
4452

4553
dbg(`chainId: ${numToStr(chainId)}`);
@@ -56,16 +64,28 @@ export async function getRpcUrl() {
5664
if (rpcUrlPromise === undefined) {
5765
rpcUrlPromise = (async () => {
5866
const chainEnv = await ensureChainEnv();
59-
const { RPC_URLS } = await import("@fluencelabs/deal-ts-clients");
60-
let rpcUrl: string = RPC_URLS[chainEnv];
61-
const envConfig = await initEnvConfig();
62-
63-
if (envConfig?.rpcUrl !== undefined) {
64-
commandObj.logToStderr(
65-
`Using custom RPC URL: ${envConfig.rpcUrl} from ${envConfig.$getPath()}`,
66-
);
67-
68-
rpcUrl = envConfig.rpcUrl;
67+
let rpcUrl: string | undefined;
68+
let envConfig = await initEnvConfig();
69+
70+
if (envConfig?.perEnvConfig?.[chainEnv]?.rpcHttpUrl !== undefined) {
71+
const customRpcUrl = envConfig.perEnvConfig[chainEnv].rpcHttpUrl;
72+
rpcUrl = customRpcUrl;
73+
} else if (chainEnv === "local") {
74+
rpcUrl = "http://localhost:8545";
75+
} else {
76+
rpcUrl = await password({
77+
message: `Enter private HTTP RPC URL to use with ${chainEnv} env`,
78+
validate(rpcUrl: string) {
79+
return urlValidator(rpcUrl);
80+
},
81+
});
82+
83+
envConfig = await initNewEnvConfig();
84+
const perEnvConfig = envConfig.perEnvConfig ?? {};
85+
perEnvConfig[chainEnv] = perEnvConfig[chainEnv] ?? {};
86+
perEnvConfig[chainEnv].rpcHttpUrl = rpcUrl;
87+
envConfig.perEnvConfig = perEnvConfig;
88+
await envConfig.$commit();
6989
}
7090

7191
dbg(`rpcUrl: ${rpcUrl}`);
@@ -76,6 +96,44 @@ export async function getRpcUrl() {
7696
return rpcUrlPromise;
7797
}
7898

99+
let wsUrlPromise: Promise<string> | undefined;
100+
101+
export async function getWsUrl() {
102+
if (wsUrlPromise === undefined) {
103+
wsUrlPromise = (async () => {
104+
const chainEnv = await ensureChainEnv();
105+
let rpcUrl: string | undefined;
106+
let envConfig = await initEnvConfig();
107+
108+
if (envConfig?.perEnvConfig?.[chainEnv]?.rpcWsUrl !== undefined) {
109+
const customRpcUrl = envConfig.perEnvConfig[chainEnv].rpcWsUrl;
110+
rpcUrl = customRpcUrl;
111+
} else if (chainEnv === "local") {
112+
rpcUrl = `wss://${CHAIN_RPC_CONTAINER_NAME}:${CHAIN_RPC_PORT}`;
113+
} else {
114+
rpcUrl = await password({
115+
message: `Enter private Websocket RPC URL to use with ${chainEnv} env`,
116+
validate(rpcUrl: string) {
117+
return urlValidator(rpcUrl);
118+
},
119+
});
120+
121+
envConfig = await initNewEnvConfig();
122+
const perEnvConfig = envConfig.perEnvConfig ?? {};
123+
perEnvConfig[chainEnv] = perEnvConfig[chainEnv] ?? {};
124+
perEnvConfig[chainEnv].rpcWsUrl = rpcUrl;
125+
envConfig.perEnvConfig = perEnvConfig;
126+
await envConfig.$commit();
127+
}
128+
129+
dbg(`wsUrl: ${rpcUrl}`);
130+
return rpcUrl;
131+
})();
132+
}
133+
134+
return wsUrlPromise;
135+
}
136+
79137
let ipfsGatewayPromise: Promise<string> | undefined;
80138

81139
const IPFS_GATEWAYS: Record<ChainENV, string> = {
@@ -92,12 +150,14 @@ export async function getIpfsGateway() {
92150
let ipfsGateway = IPFS_GATEWAYS[chainEnv];
93151
const envConfig = await initEnvConfig();
94152

95-
if (envConfig?.ipfsGateway !== undefined) {
153+
if (envConfig?.perEnvConfig?.[chainEnv]?.ipfsGateway !== undefined) {
154+
const customIpfsGateway = envConfig.perEnvConfig[chainEnv].ipfsGateway;
155+
96156
commandObj.logToStderr(
97-
`Using custom IPFS Gateway: ${envConfig.ipfsGateway} from ${envConfig.$getPath()}`,
157+
`Using custom IPFS Gateway: ${customIpfsGateway} from ${envConfig.$getPath()}`,
98158
);
99159

100-
ipfsGateway = envConfig.ipfsGateway;
160+
ipfsGateway = customIpfsGateway;
101161
}
102162

103163
dbg(`ipfsGateway: ${ipfsGateway}`);
@@ -136,12 +196,15 @@ export async function getBlockScoutUrl() {
136196
let blockScoutUrl: string = BLOCK_SCOUT_URLS[chainEnv];
137197
const envConfig = await initEnvConfig();
138198

139-
if (envConfig?.blockScoutUrl !== undefined) {
199+
if (envConfig?.perEnvConfig?.[chainEnv]?.blockScoutUrl !== undefined) {
200+
const customBlockScoutUrl =
201+
envConfig.perEnvConfig[chainEnv].blockScoutUrl;
202+
140203
commandObj.logToStderr(
141-
`Using custom BlockScout URL: ${envConfig.blockScoutUrl} from ${envConfig.$getPath()}`,
204+
`Using custom BlockScout URL: ${customBlockScoutUrl} from ${envConfig.$getPath()}`,
142205
);
143206

144-
blockScoutUrl = envConfig.blockScoutUrl;
207+
blockScoutUrl = customBlockScoutUrl;
145208
}
146209

147210
dbg(`blockScoutUrl: ${blockScoutUrl}`);
@@ -170,12 +233,14 @@ export async function getSubgraphUrl() {
170233
let subgraphUrl: string = SUBGRAPH_URLS[chainEnv];
171234
const envConfig = await initEnvConfig();
172235

173-
if (envConfig?.subgraphUrl !== undefined) {
236+
if (envConfig?.perEnvConfig?.[chainEnv]?.subgraphUrl !== undefined) {
237+
const customSubgraphUrl = envConfig.perEnvConfig[chainEnv].subgraphUrl;
238+
174239
commandObj.logToStderr(
175-
`Using custom Subgraph URL: ${envConfig.subgraphUrl} from ${envConfig.$getPath()}`,
240+
`Using custom Subgraph URL: ${customSubgraphUrl} from ${envConfig.$getPath()}`,
176241
);
177242

178-
subgraphUrl = envConfig.subgraphUrl;
243+
subgraphUrl = customSubgraphUrl;
179244
}
180245

181246
dbg(`subgraphUrl: ${subgraphUrl}`);

packages/cli/package/src/lib/chain/conversions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,16 @@ export async function cidHexStringToBase32(cidHex: string): Promise<string> {
7979
return base32.encode(new Uint8Array(Buffer.from(cidHex, "hex")));
8080
}
8181

82+
export function utf8ToBase64String(utf8String: string): string {
83+
return bufferToBase64(Buffer.from(utf8String, "utf-8"));
84+
}
85+
8286
export function hexStringToUTF8ToBase64String(hexString: string): string {
8387
const cleanHexString = hexString.startsWith("0x")
8488
? hexString.slice(2)
8589
: hexString;
8690

87-
return bufferToBase64(Buffer.from(cleanHexString, "utf-8"));
91+
return utf8ToBase64String(cleanHexString);
8892
}
8993

9094
export async function resourceSupplyFromConfigToChain(

packages/cli/package/src/lib/configs/initConfigNew.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ export function getConfigInitFunction<
200200
);
201201

202202
await validateLatestConfig(config);
203-
prevConfig = config;
203+
prevConfig = cloneDeep(config);
204204
},
205205
};
206206

@@ -428,12 +428,13 @@ async function getLatestConfig<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>({
428428
// No need to migrate for the current config version
429429
if (index !== 0) {
430430
prevConfig = currentConfig;
431-
const migrated = await configOptions.migrate(cloneDeep(prevConfig));
431+
const configToMigrate = cloneDeep(prevConfig);
432432

433-
if ("version" in migrated) {
434-
delete migrated.version;
433+
if ("version" in configToMigrate) {
434+
delete configToMigrate.version;
435435
}
436436

437+
const migrated = await configOptions.migrate(configToMigrate);
437438
currentConfig = { version: initialVersion + index, ...migrated };
438439
}
439440

packages/cli/package/src/lib/configs/project/env/env.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ import { type InitConfigOptions } from "../../initConfigNewTypes.js";
2222

2323
import configOptions0, { type Config as Config0 } from "./env0.js";
2424
import configOptions1, { type Config as Config1 } from "./env1.js";
25+
import configOptions2, { type Config as Config2 } from "./env2.js";
2526

26-
export const options: InitConfigOptions<Config0, Config1> = {
27+
export const options: InitConfigOptions<Config0, Config1, Config2> = {
2728
description: "Defines project user's preferences",
28-
options: [configOptions0, configOptions1],
29+
options: [configOptions0, configOptions1, configOptions2],
2930
getConfigPath: getEnvConfigPath,
3031
};
3132

0 commit comments

Comments
 (0)