-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathconfig.ts
123 lines (105 loc) · 4.25 KB
/
config.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { config as airnodeConfig } from '@api3/airnode-validator';
import { GoResult, goSync } from '@api3/promise-utils';
import { ethers } from 'ethers';
import fs from 'fs';
import { cloneDeepWith, isString, reduce, template } from 'lodash';
import { z } from 'zod';
import { Secrets } from './types';
export const evmAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
export const evmHashSchema = z.string().regex(/^0x[a-fA-F\d]{64}$/);
export const namedUnits = z.union([
z.literal('wei'),
z.literal('kwei'),
z.literal('mwei'),
z.literal('gwei'),
z.literal('szabo'),
z.literal('finney'),
z.literal('ether'),
]);
export const thresholdSchema = z.object({
value: z.number().nonnegative(),
unit: namedUnits,
});
export const valueSchema = z.object({
recipient: evmAddressSchema,
lowThreshold: thresholdSchema,
highThreshold: thresholdSchema,
});
export const valuesSchema = z.array(valueSchema).refine(
(values) => {
const recipients = values.map((value) => value.recipient);
const uniqueRecipients = [...new Set(recipients)];
return uniqueRecipients.length === recipients.length;
},
{ message: 'All recipients must be unique' }
);
export const merkleFunderDepositoriesSchema = z.array(
z.object({
owner: evmAddressSchema,
values: valuesSchema,
})
);
export const chainConfigSchema = z.object({
funderMnemonic: z.string().refine((mnemonic) => ethers.utils.isValidMnemonic(mnemonic), {
message: 'Invalid mnemonic',
}),
providers: airnodeConfig.providersSchema,
options: airnodeConfig.chainOptionsSchema,
merkleFunderDepositories: merkleFunderDepositoriesSchema,
});
export const configSchema = z.record(z.coerce.number().int().positive(), chainConfigSchema);
// Regular expression that does not match anything, ensuring no escaping or interpolation happens
// https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L199
const NO_MATCH_REGEXP = /($^)/;
// Regular expression matching ES template literal delimiter (${}) with escaping
// https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L175
const ES_MATCH_REGEXP = /(?<!\\)\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
// Regular expression matching the escaped ES template literal delimiter (${}). We need to use "\\\\" (four backslashes)
// because "\\" becomes "\\\\" when converted to string
const ESCAPED_ES_MATCH_REGEXP = /\\\\(\$\{([^\\}]*(?:\\.[^\\}]*)*)\})/g;
function interpolateSecrets(config: unknown, secrets: Secrets): GoResult<unknown> {
const stringifiedSecrets = reduce(
secrets,
(acc: object, value: unknown, key: string) => {
return {
...acc,
// Convert to value to JSON to encode new lines as "\n". The resulting value will be a JSON string with quotes
// which are sliced off.
[key]: JSON.stringify(value).slice(1, -1),
};
},
{} as Secrets
);
const interpolationRes = goSync(() =>
JSON.parse(
template(JSON.stringify(config), {
escape: NO_MATCH_REGEXP,
evaluate: NO_MATCH_REGEXP,
interpolate: ES_MATCH_REGEXP,
})(stringifiedSecrets)
)
);
if (!interpolationRes.success) return interpolationRes;
const interpolatedConfig = JSON.stringify(interpolationRes.data);
// Un-escape the escaped config interpolations (e.g. to enable interpolation in processing snippets)
return goSync(() => JSON.parse(interpolatedConfig.replace(ESCAPED_ES_MATCH_REGEXP, '$1')));
}
const replaceInterpolationStrings = (obj: any): any =>
cloneDeepWith(obj, (value) => {
if (isString(value)) {
return value
.replace(/\${MNEMONIC}/g, 'test test test test test test test test test test test junk')
.replace(/\${FUNDER_RPC_URL_(.*?)}/g, 'https://rpc.example.com');
}
});
export const validateConfig = (config: unknown, ignoreInterpolationStrings = false) => {
return configSchema.parse(!ignoreInterpolationStrings ? config : replaceInterpolationStrings(config));
};
export const loadConfig = (configPath = './config/config.json') => {
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
const interpolateConfigRes = interpolateSecrets(config, process.env);
if (!interpolateConfigRes.success) {
throw new Error(`Secrets interpolation failed: ${interpolateConfigRes.error.message}`);
}
return validateConfig(interpolateConfigRes.data);
};