@lidofinance/next-pages 0.46.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -36
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +201 -107
- package/dist/api/index.js.map +1 -1
- package/dist/api/rpcFactory/errors.d.ts +17 -0
- package/dist/api/rpcFactory/errors.d.ts.map +1 -0
- package/dist/api/rpcFactory/index.d.ts +3 -0
- package/dist/api/rpcFactory/index.d.ts.map +1 -0
- package/dist/api/rpcFactory/rpc-factory.d.ts +4 -0
- package/dist/api/rpcFactory/rpc-factory.d.ts.map +1 -0
- package/dist/api/rpcFactory/types.d.ts +34 -0
- package/dist/api/rpcFactory/types.d.ts.map +1 -0
- package/dist/api/rpcFactory/validation.d.ts +14 -0
- package/dist/api/rpcFactory/validation.d.ts.map +1 -0
- package/package.json +14 -10
- package/dist/api/rpcFactory.d.ts +0 -37
- package/dist/api/rpcFactory.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Common API and UI next pages.
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- React 17 || 18
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
yarn add @lidofinance/next-pages
|
|
@@ -20,47 +20,45 @@ yarn add next@^12.3.0 prom-client@^14.0.0 @lidofinance/api-logger@^0.36.0 @lidof
|
|
|
20
20
|
#### Health
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
|
-
import { health } from '@lidofinance/next-pages'
|
|
23
|
+
import { health } from '@lidofinance/next-pages'
|
|
24
24
|
// or import { health } from '@lidofinance/next-pages/api';
|
|
25
25
|
|
|
26
|
-
export default health
|
|
26
|
+
export default health
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
#### Metrics
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
|
-
import { registry } from 'utilsApi/metrics'
|
|
33
|
-
import { metricsFactory } from '@lidofinance/next-pages'
|
|
32
|
+
import { registry } from 'utilsApi/metrics'
|
|
33
|
+
import { metricsFactory } from '@lidofinance/next-pages'
|
|
34
34
|
// or import { metricsFactory } from '@lidofinance/next-pages/api';
|
|
35
35
|
|
|
36
36
|
const metrics = metricsFactory({
|
|
37
37
|
registry,
|
|
38
|
-
})
|
|
38
|
+
})
|
|
39
39
|
|
|
40
|
-
export default metrics
|
|
40
|
+
export default metrics
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
#### RPC
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
|
-
import getConfig from 'next/config'
|
|
47
|
-
import { rpcFactory } from '@lidofinance/api-pages'
|
|
46
|
+
import getConfig from 'next/config'
|
|
47
|
+
import { rpcFactory } from '@lidofinance/api-pages'
|
|
48
48
|
// or import { rpcFactory } from '@lidofinance/next-pages/api';
|
|
49
|
-
import { fetchRPC, serverLogger } from 'utilsApi'
|
|
50
|
-
import { registry } from 'utilsApi/metrics'
|
|
51
|
-
import { METRICS_PREFIX } from '../../config'
|
|
49
|
+
import { fetchRPC, serverLogger } from 'utilsApi'
|
|
50
|
+
import { registry } from 'utilsApi/metrics'
|
|
51
|
+
import { METRICS_PREFIX } from '../../config'
|
|
52
52
|
|
|
53
|
-
const { publicRuntimeConfig, serverRuntimeConfig } = getConfig()
|
|
54
|
-
const { defaultChain } = publicRuntimeConfig
|
|
55
|
-
const { infuraApiKey, alchemyApiKey } = serverRuntimeConfig
|
|
53
|
+
const { publicRuntimeConfig, serverRuntimeConfig } = getConfig()
|
|
54
|
+
const { defaultChain } = publicRuntimeConfig
|
|
55
|
+
const { infuraApiKey, alchemyApiKey } = serverRuntimeConfig
|
|
56
56
|
|
|
57
|
-
// Should be a separate file
|
|
58
57
|
export const enum CHAINS {
|
|
59
58
|
Mainnet = 1,
|
|
60
59
|
Goerli = 5,
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
// Should be a separate file
|
|
64
62
|
export const providers: Record<CHAINS, [string, ...string[]]> = {
|
|
65
63
|
[CHAINS.Mainnet]: [
|
|
66
64
|
`https://mainnet.infura.io/v3/${infuraApiKey}`,
|
|
@@ -70,24 +68,87 @@ export const providers: Record<CHAINS, [string, ...string[]]> = {
|
|
|
70
68
|
`https://goerli.infura.io/v3/${infuraApiKey}`,
|
|
71
69
|
`https://eth-goerli.alchemyapi.io/v2/${alchemyApiKey}`,
|
|
72
70
|
],
|
|
73
|
-
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const allowedCallAddresses: Record<string, string[]> = {
|
|
74
|
+
/* Mapping of chainIds to array of addresses permitted for eth_getLogs */
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const allowedLogsAddresses: Record<string, string[]> = {
|
|
78
|
+
/* Mapping of chainIds to array of addresses permitted for eth_call */
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// example
|
|
82
|
+
const allowedRPCMethods = [
|
|
83
|
+
'test',
|
|
84
|
+
'eth_call',
|
|
85
|
+
'eth_gasPrice',
|
|
86
|
+
'eth_getCode',
|
|
87
|
+
'eth_estimateGas',
|
|
88
|
+
'eth_getBlockByNumber',
|
|
89
|
+
'eth_feeHistory',
|
|
90
|
+
'eth_maxPriorityFeePerGas',
|
|
91
|
+
'eth_getBalance',
|
|
92
|
+
'eth_blockNumber',
|
|
93
|
+
'eth_getTransactionByHash',
|
|
94
|
+
'eth_getTransactionReceipt',
|
|
95
|
+
'eth_getTransactionCount',
|
|
96
|
+
'eth_sendRawTransaction',
|
|
97
|
+
'eth_getLogs',
|
|
98
|
+
'eth_chainId',
|
|
99
|
+
'net_version',
|
|
100
|
+
]
|
|
74
101
|
|
|
75
102
|
const rpc = rpcFactory({
|
|
76
|
-
fetchRPC
|
|
103
|
+
fetchRPC: trackedFetchRpcFactory({
|
|
104
|
+
registry: Metrics.registry,
|
|
105
|
+
prefix: METRICS_PREFIX,
|
|
106
|
+
}),
|
|
77
107
|
metrics: {
|
|
78
108
|
prefix: METRICS_PREFIX,
|
|
79
|
-
registry,
|
|
109
|
+
registry: Metrics.registry,
|
|
80
110
|
},
|
|
81
|
-
allowedRPCMethods: [
|
|
82
|
-
'eth_call',
|
|
83
|
-
'eth_gasPrice',
|
|
84
|
-
// ...
|
|
85
|
-
],
|
|
86
111
|
defaultChain,
|
|
87
|
-
providers
|
|
88
|
-
|
|
112
|
+
providers: {
|
|
113
|
+
[CHAINS.Mainnet]: secretConfig.rpcUrls_1,
|
|
114
|
+
[CHAINS.Holesky]: secretConfig.rpcUrls_17000,
|
|
115
|
+
},
|
|
116
|
+
// optional validation options to protect from misuse of public endpoints
|
|
117
|
+
// Strongly recommended to use all of them
|
|
118
|
+
validation: {
|
|
119
|
+
// !!! IMPORTANT, MUST HAVE !!!
|
|
120
|
+
// allowed JSON RPC methods
|
|
121
|
+
allowedRPCMethods,
|
|
122
|
+
|
|
123
|
+
// !!! IMPORTANT, MUST HAVE !!!
|
|
124
|
+
// mapping chainId - addresses fro eth_call, put only contracts you dapp interacts with
|
|
125
|
+
// no entry for chainId or empty array will prevent method for this chainId
|
|
126
|
+
allowedCallAddresses,
|
|
127
|
+
|
|
128
|
+
// !!! IMPORTANT, MUST HAVE !!!
|
|
129
|
+
// mapping chainId - addresses fro eth_getLogs put only contracts here if you need to fetch events from them
|
|
130
|
+
// no entry for chainId or empty array will prevent method for this chainId
|
|
131
|
+
allowedLogsAddresses,
|
|
132
|
+
|
|
133
|
+
// max JSON-RPC batch size
|
|
134
|
+
maxBatchCount: 20, // DEFAULT
|
|
135
|
+
|
|
136
|
+
// prevents empty `address` param for eth_getLogs, that can results in massive queries
|
|
137
|
+
blockEmptyAddressGetLogs: true, // DEFAULT
|
|
138
|
+
|
|
139
|
+
// only 20k blocks size historical queries for eth_getLogs
|
|
140
|
+
// fetches and caches current block to compare with ambiguous blockTags(e.g. "latest")
|
|
141
|
+
maxGetLogsRange: 20_000, // DEFAULT
|
|
142
|
+
|
|
143
|
+
/// cache TTL for maxGetLogsRange
|
|
144
|
+
currentBlockTTLms: 60_000; // DEFAULT
|
|
145
|
+
|
|
146
|
+
// 1mb max response after which data stream is interrupted and 413 is returned
|
|
147
|
+
maxResponseSize: 1_000_000, // DEFAULT
|
|
148
|
+
},
|
|
149
|
+
})
|
|
89
150
|
|
|
90
|
-
export default rpc
|
|
151
|
+
export default rpc
|
|
91
152
|
```
|
|
92
153
|
|
|
93
154
|
### UI pages
|
|
@@ -98,17 +159,19 @@ export default rpc;
|
|
|
98
159
|
- **Page500**
|
|
99
160
|
|
|
100
161
|
Example for `pages/404.tsx`
|
|
162
|
+
|
|
101
163
|
```tsx
|
|
102
|
-
import { Page404 } from '@lidofinance/next-ui-pages/ui'
|
|
164
|
+
import { Page404 } from '@lidofinance/next-ui-pages/ui'
|
|
103
165
|
|
|
104
|
-
export default Page404
|
|
166
|
+
export default Page404
|
|
105
167
|
```
|
|
106
168
|
|
|
107
169
|
Example for `pages/500.tsx`
|
|
170
|
+
|
|
108
171
|
```tsx
|
|
109
|
-
import { Page500 } from '@lidofinance/next-ui-pages/ui'
|
|
172
|
+
import { Page500 } from '@lidofinance/next-ui-pages/ui'
|
|
110
173
|
|
|
111
|
-
export default Page500
|
|
174
|
+
export default Page500
|
|
112
175
|
```
|
|
113
176
|
|
|
114
177
|
#### Error page as generic component
|
|
@@ -116,10 +179,10 @@ export default Page500;
|
|
|
116
179
|
Example for `pages/404.tsx`
|
|
117
180
|
|
|
118
181
|
```tsx
|
|
119
|
-
import { FC } from 'react'
|
|
120
|
-
import { PageError } from '@lidofinance/next-ui-pages/ui'
|
|
182
|
+
import { FC } from 'react'
|
|
183
|
+
import { PageError } from '@lidofinance/next-ui-pages/ui'
|
|
121
184
|
|
|
122
|
-
const Page404: FC = () => <PageError title="404" content="Page Not Found"
|
|
185
|
+
const Page404: FC = () => <PageError title="404" content="Page Not Found" />
|
|
123
186
|
|
|
124
|
-
export default Page404
|
|
187
|
+
export default Page404
|
|
125
188
|
```
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { health } from './health';
|
|
2
2
|
export { type MetricsFactoryParameters, metricsFactory } from './metricsFactory';
|
|
3
|
-
export { type
|
|
3
|
+
export { type RPCFactoryParams, type RPCFactoryValidationParams, type RpcProviders, rpcFactory } from './rpcFactory';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,KAAK,wBAAwB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAChF,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,KAAK,wBAAwB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAChF,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,0BAA0B,EAAE,KAAK,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/api/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Readable as $FgOCV$Readable, Transform as $FgOCV$Transform} from "node:stream";
|
|
2
|
+
import {pipeline as $FgOCV$pipeline} from "node:stream/promises";
|
|
2
3
|
import {Counter as $FgOCV$Counter} from "prom-client";
|
|
3
4
|
import {iterateUrls as $FgOCV$iterateUrls} from "@lidofinance/rpc";
|
|
5
|
+
import {Cache as $FgOCV$Cache} from "memory-cache";
|
|
4
6
|
|
|
5
7
|
const $6ad1ea13f08e3d24$export$f0784a54fb0af903 = (_req, res)=>{
|
|
6
8
|
res.status(200).send({
|
|
@@ -18,140 +20,230 @@ const $29c7c52f2735c037$export$fc20b1372f40c2c5 = ({ registry: registry })=>asyn
|
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
const $
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
const $09f9daeda4842558$export$c982278a0ddc4dbe = "Something went wrong. Sorry, try again later :(";
|
|
25
|
+
const $09f9daeda4842558$export$56a189db5508cdcb = "Healthy RPC services are over!";
|
|
26
|
+
class $09f9daeda4842558$export$bb8c263ad32caa8 extends Error {
|
|
24
27
|
}
|
|
25
|
-
class $
|
|
28
|
+
class $09f9daeda4842558$export$fc97cac2ba066ec7 extends $09f9daeda4842558$export$bb8c263ad32caa8 {
|
|
26
29
|
constructor(message){
|
|
27
30
|
super(message || "Unsupported chainId");
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
|
-
class $
|
|
33
|
+
class $09f9daeda4842558$export$2dfa2d8949b43fdc extends $09f9daeda4842558$export$bb8c263ad32caa8 {
|
|
31
34
|
constructor(message){
|
|
32
35
|
super(message || "Unsupported HTTP method");
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
|
-
class $
|
|
38
|
+
class $09f9daeda4842558$export$2adbfa76b71ba748 extends $09f9daeda4842558$export$bb8c263ad32caa8 {
|
|
36
39
|
constructor(message){
|
|
37
40
|
super(message || "Invalid Request");
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
|
-
class $
|
|
43
|
+
class $09f9daeda4842558$export$e224c1080d950012 extends $09f9daeda4842558$export$bb8c263ad32caa8 {
|
|
41
44
|
constructor(message){
|
|
42
|
-
super(message || "
|
|
45
|
+
super(message || "Response size too large");
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
// validation factories
|
|
54
|
+
const $5a404da009e373d8$var$zeroBN = BigInt(0);
|
|
55
|
+
const $5a404da009e373d8$export$564dd6c0fb12e680 = (defaultChain, providers, maxBatchSize)=>{
|
|
56
|
+
return (req)=>{
|
|
57
|
+
if (req.method !== "POST") // We don't care about tracking blocked requests here
|
|
58
|
+
throw new (0, $09f9daeda4842558$export$2dfa2d8949b43fdc)();
|
|
59
|
+
const chainId = Number(req.query.chainId || defaultChain);
|
|
60
|
+
// Allow only chainId of specified chains
|
|
61
|
+
if (providers[chainId] == null) // We don't care about tracking blocked requests here
|
|
62
|
+
throw new (0, $09f9daeda4842558$export$fc97cac2ba066ec7)();
|
|
63
|
+
const requests = Array.isArray(req.body) ? req.body : [
|
|
64
|
+
req.body
|
|
65
|
+
];
|
|
66
|
+
if (typeof maxBatchSize === "number" && requests.length > maxBatchSize) throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`Too many batched requests`);
|
|
67
|
+
return {
|
|
68
|
+
chainId: chainId,
|
|
69
|
+
requests: requests
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const $5a404da009e373d8$export$bd57dbd3bc9a1cc6 = (allowedMethods)=>{
|
|
74
|
+
const methodsMap = new Set(allowedMethods ?? []);
|
|
75
|
+
return (request, context)=>{
|
|
76
|
+
const method = request.method;
|
|
77
|
+
if (typeof method !== "string") throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method isn't string`);
|
|
78
|
+
if (allowedMethods && !methodsMap.has(request.method)) {
|
|
79
|
+
context.rpcRequestBlocked.inc({
|
|
80
|
+
reason: "method not allowed"
|
|
81
|
+
});
|
|
82
|
+
throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method ${method} not allowed`);
|
|
57
83
|
}
|
|
58
|
-
}
|
|
59
|
-
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const $5a404da009e373d8$export$b724143801441d66 = (allowedAddress)=>{
|
|
87
|
+
const allowedCallAddressMap = $5a404da009e373d8$var$createAllowedAddressMap(allowedAddress);
|
|
88
|
+
return (request, { chainId: chainId, rpcRequestBlocked: rpcRequestBlocked })=>{
|
|
89
|
+
const { params: params } = request;
|
|
90
|
+
if (Array.isArray(params) && params[0] && typeof params[0] === "object" && "to" in params[0] && typeof params[0].to === "string") {
|
|
91
|
+
if (!allowedCallAddressMap[chainId]?.has(params[0].to.toLowerCase())) {
|
|
92
|
+
rpcRequestBlocked.inc({
|
|
93
|
+
reason: "address not allowed for eth_call"
|
|
94
|
+
});
|
|
95
|
+
throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`Address not allowed for eth_call`);
|
|
96
|
+
}
|
|
97
|
+
} else throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_call is invalid`);
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const $5a404da009e373d8$export$5d8ee2232a9cb21a = (allowedAddress, blockEmptyAddressGetLogs, maxBlockRange, currentBlockTTL = 60000)=>{
|
|
101
|
+
const allowedAddressMap = $5a404da009e373d8$var$createAllowedAddressMap(allowedAddress ?? {});
|
|
102
|
+
const maxBlockRangeBN = maxBlockRange ? BigInt(maxBlockRange) : $5a404da009e373d8$var$zeroBN;
|
|
103
|
+
const currentBlock = new (0, $FgOCV$Cache)();
|
|
104
|
+
return async (request, { chainId: chainId, rpcRequestBlocked: rpcRequestBlocked, requestRPC: requestRPC })=>{
|
|
105
|
+
try {
|
|
106
|
+
const params = request.params[0];
|
|
107
|
+
const fromBlock = params.fromBlock;
|
|
108
|
+
const toBlock = params.toBlock ?? "latest";
|
|
109
|
+
if (blockEmptyAddressGetLogs || allowedAddress) {
|
|
110
|
+
// address validation
|
|
111
|
+
const address = params.address;
|
|
112
|
+
if (blockEmptyAddressGetLogs && !address) throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs address is invalid`);
|
|
113
|
+
const addresses = Array.isArray(address) ? address : [
|
|
114
|
+
address
|
|
115
|
+
];
|
|
116
|
+
if (blockEmptyAddressGetLogs && addresses.length === 0) throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs address is invalid`);
|
|
117
|
+
if (allowedAddress && addresses.some((eventAddress)=>// needs this check before toLowerCase
|
|
118
|
+
typeof eventAddress !== "string" || !allowedAddressMap[chainId]?.has(eventAddress.toLowerCase()))) {
|
|
119
|
+
rpcRequestBlocked.inc({
|
|
120
|
+
reason: "address not allowed for eth_getLogs"
|
|
121
|
+
});
|
|
122
|
+
throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`Address not allowed for eth_getLogs`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// block range validation
|
|
126
|
+
if (maxBlockRange) {
|
|
127
|
+
if (fromBlock === "earliest") throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs fromBlock is invalid`);
|
|
128
|
+
const shouldValidateBlockDistance = fromBlock.startsWith("0x");
|
|
129
|
+
if (shouldValidateBlockDistance) {
|
|
130
|
+
const normalizedFromBlock = BigInt(fromBlock);
|
|
131
|
+
let normalizedToBlock;
|
|
132
|
+
if (toBlock.startsWith("0x")) normalizedToBlock = BigInt(toBlock);
|
|
133
|
+
else {
|
|
134
|
+
const cached = currentBlock.get(chainId);
|
|
135
|
+
if (cached) normalizedToBlock = cached;
|
|
136
|
+
else {
|
|
137
|
+
normalizedToBlock = await requestRPC(chainId, {
|
|
138
|
+
id: 1,
|
|
139
|
+
jsonrpc: "2.0",
|
|
140
|
+
method: "eth_blockNumber"
|
|
141
|
+
}).then((res)=>res.json()).then((res)=>BigInt(res.result));
|
|
142
|
+
currentBlock.put(chainId, normalizedToBlock, currentBlockTTL);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const range = normalizedToBlock - normalizedFromBlock;
|
|
146
|
+
if (range < $5a404da009e373d8$var$zeroBN || range > maxBlockRangeBN) {
|
|
147
|
+
rpcRequestBlocked.inc({
|
|
148
|
+
reason: "eth_getLogs range is invalid"
|
|
149
|
+
});
|
|
150
|
+
throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs range is invalid`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof (0, $09f9daeda4842558$export$bb8c263ad32caa8)) throw error;
|
|
156
|
+
throw new (0, $09f9daeda4842558$export$2adbfa76b71ba748)(`RPC method eth_getLogs is invalid`);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
60
159
|
};
|
|
61
|
-
const $
|
|
160
|
+
const $5a404da009e373d8$export$7c5a2292697deb4b = (MAX_SIZE)=>(context)=>{
|
|
161
|
+
let bytesWritten = 0;
|
|
162
|
+
return new (0, $FgOCV$Transform)({
|
|
163
|
+
transform (chunk, _encoding, callback) {
|
|
164
|
+
bytesWritten += chunk.length;
|
|
165
|
+
if (bytesWritten > MAX_SIZE) {
|
|
166
|
+
// Emit an error if size exceeds MAX_SIZE
|
|
167
|
+
context.rpcRequestBlocked.inc({
|
|
168
|
+
reason: "response too large"
|
|
169
|
+
});
|
|
170
|
+
return callback(new (0, $09f9daeda4842558$export$e224c1080d950012)(`Stream size exceeds the maximum limit of ${MAX_SIZE} bytes`));
|
|
171
|
+
}
|
|
172
|
+
return callback(null, chunk) // Pass the chunk through
|
|
173
|
+
;
|
|
174
|
+
},
|
|
175
|
+
flush (callback) {
|
|
176
|
+
callback();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
const $5a404da009e373d8$export$e67f820dcb2fefda = (method, allowedRPCMethods)=>!allowedRPCMethods || allowedRPCMethods.includes(method);
|
|
181
|
+
const $5a404da009e373d8$var$createAllowedAddressMap = (allowedAddress)=>{
|
|
182
|
+
return Object.entries(allowedAddress).reduce((acc, [chainId, addresses])=>{
|
|
183
|
+
acc[chainId] = new Set(addresses.map((a)=>a.toLowerCase()));
|
|
184
|
+
return acc;
|
|
185
|
+
}, {});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
const $3f76f1f6d2274759$export$9bfcc39f106ad871 = ({ metrics: metrics, providers: providers, fetchRPC: fetchRPC, defaultChain: defaultChain, validation: validation })=>{
|
|
190
|
+
const { allowedRPCMethods: allowedRPCMethods, allowedCallAddresses: allowedCallAddresses, allowedLogsAddresses: allowedLogsAddresses, maxResponseSize: maxResponseSize = 1000000, maxBatchCount: maxBatchCount = 20, currentBlockTTLms: currentBlockTTLms = 60000, maxGetLogsRange: maxGetLogsRange = 20000, blockEmptyAddressGetLogs: blockEmptyAddressGetLogs = true } = validation;
|
|
191
|
+
// optional metrics
|
|
192
|
+
const { registry: registry, prefix: prefix = "" } = metrics ?? {};
|
|
62
193
|
const rpcRequestBlocked = new (0, $FgOCV$Counter)({
|
|
63
194
|
name: prefix + "rpc_service_request_blocked",
|
|
64
195
|
help: "RPC service request blocked",
|
|
65
|
-
labelNames: [
|
|
196
|
+
labelNames: [
|
|
197
|
+
"reason"
|
|
198
|
+
],
|
|
66
199
|
registers: []
|
|
67
200
|
});
|
|
68
|
-
registry
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
201
|
+
registry?.registerMetric(rpcRequestBlocked);
|
|
202
|
+
const validateBaseRequest = (0, $5a404da009e373d8$export$564dd6c0fb12e680)(defaultChain, providers, maxBatchCount);
|
|
203
|
+
const validateRpcMethod = (0, $5a404da009e373d8$export$bd57dbd3bc9a1cc6)(allowedRPCMethods);
|
|
204
|
+
const validateEthCall = allowedCallAddresses && (0, $5a404da009e373d8$export$e67f820dcb2fefda)("eth_call", allowedRPCMethods) ? (0, $5a404da009e373d8$export$b724143801441d66)(allowedCallAddresses) : undefined;
|
|
205
|
+
const validateEthGetLogs = (allowedLogsAddresses || blockEmptyAddressGetLogs) && (0, $5a404da009e373d8$export$e67f820dcb2fefda)("eth_getLogs", allowedRPCMethods) ? (0, $5a404da009e373d8$export$5d8ee2232a9cb21a)(allowedLogsAddresses, blockEmptyAddressGetLogs, maxGetLogsRange, currentBlockTTLms) : undefined;
|
|
206
|
+
const validateMaxSteamSize = (0, $5a404da009e373d8$export$7c5a2292697deb4b)(maxResponseSize);
|
|
207
|
+
const requestRPC = (chainId, body)=>(0, $FgOCV$iterateUrls)(providers[chainId], // TODO: consider adding verification that body is actually matches FetchRpcInitBody
|
|
208
|
+
(url)=>fetchRPC(url, {
|
|
209
|
+
body: body
|
|
210
|
+
}, {
|
|
211
|
+
chainId: chainId
|
|
212
|
+
}), // eslint-disable-next-line @typescript-eslint/unbound-method
|
|
213
|
+
console.error);
|
|
77
214
|
return async (req, res)=>{
|
|
215
|
+
const abortController = new AbortController();
|
|
78
216
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
throw new $7d34ac0315ec4c05$export$fc97cac2ba066ec7();
|
|
86
|
-
const requests = Array.isArray(req.body) ? req.body : [
|
|
87
|
-
req.body
|
|
88
|
-
];
|
|
89
|
-
if (typeof maxBatchCount === "number" && requests.length > maxBatchCount) throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`Too many batched requests`);
|
|
90
|
-
// Check if provided methods are allowed
|
|
217
|
+
const { chainId: chainId, requests: requests } = validateBaseRequest(req);
|
|
218
|
+
const validationContext = {
|
|
219
|
+
chainId: chainId,
|
|
220
|
+
rpcRequestBlocked: rpcRequestBlocked,
|
|
221
|
+
requestRPC: requestRPC
|
|
222
|
+
};
|
|
91
223
|
// We throw HTTP error for ANY invalid RPC request out of batch
|
|
92
|
-
// because we assume that frontend must not send invalid requests
|
|
93
|
-
for (const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
if (method === "eth_call" && allowedCallAddressMap[chainId]) {
|
|
100
|
-
if (Array.isArray(params) && typeof params[0] === "object" && typeof params[0].to === "string") {
|
|
101
|
-
if (!allowedCallAddressMap[chainId].has(params[0].to.toLowerCase())) {
|
|
102
|
-
rpcRequestBlocked.inc();
|
|
103
|
-
throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`Address not allowed for eth_call`);
|
|
104
|
-
}
|
|
105
|
-
} else throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`RPC method eth_call is invalid`);
|
|
106
|
-
}
|
|
107
|
-
if (method === "eth_getLogs" && (disallowEmptyAddressGetLogs || allowedLogsAddressMap[chainId])) {
|
|
108
|
-
if (Array.isArray(params) && typeof params[0] === "object") {
|
|
109
|
-
const address = params[0].address;
|
|
110
|
-
if (disallowEmptyAddressGetLogs && (!address || Array.isArray(address) && address.length === 0)) {
|
|
111
|
-
rpcRequestBlocked.inc();
|
|
112
|
-
throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`No empty address on eth_getLogs`);
|
|
113
|
-
}
|
|
114
|
-
const addresses = Array.isArray(address) ? address : [
|
|
115
|
-
address
|
|
116
|
-
];
|
|
117
|
-
if (addresses.some((eventAddress)=>// needs this check before toLowerCase
|
|
118
|
-
typeof eventAddress !== "string" || !allowedLogsAddressMap[chainId].has(eventAddress.toLowerCase()))) {
|
|
119
|
-
rpcRequestBlocked.inc();
|
|
120
|
-
throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`Address not allowed for eth_getLogs`);
|
|
121
|
-
}
|
|
122
|
-
} else throw new $7d34ac0315ec4c05$export$2adbfa76b71ba748(`Invalid eth_getLogs`);
|
|
123
|
-
}
|
|
224
|
+
// because we assume that frontend must not send invalid requests at all
|
|
225
|
+
for (const request of requests){
|
|
226
|
+
validateRpcMethod(request, validationContext);
|
|
227
|
+
const method = request.method;
|
|
228
|
+
if (method === "eth_call" && validateEthCall) validateEthCall(request, validationContext);
|
|
229
|
+
if (method === "eth_getLogs" && validateEthGetLogs) await validateEthGetLogs(request, validationContext);
|
|
124
230
|
}
|
|
125
|
-
const
|
|
126
|
-
(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
res.setHeader("Content-Type", requested.headers.get("Content-Type") ?? "application/json");
|
|
133
|
-
if (requested.body) {
|
|
134
|
-
const sizeLimit = $7d34ac0315ec4c05$var$createSizeLogger(maxResponseSize);
|
|
135
|
-
const readableStream = (0, $FgOCV$Readable).fromWeb(requested.body);
|
|
136
|
-
readableStream.pipe(sizeLimit).on("error", (error)=>{
|
|
137
|
-
if (error instanceof $7d34ac0315ec4c05$export$e224c1080d950012) {
|
|
138
|
-
console.warn(`[rpcFactory] RPC response too large: ${JSON.stringify(requests)}`);
|
|
139
|
-
res.status(413).end() // Payload Too Large
|
|
140
|
-
;
|
|
141
|
-
} else {
|
|
142
|
-
res.statusCode = 500;
|
|
143
|
-
res.end($7d34ac0315ec4c05$export$c982278a0ddc4dbe);
|
|
144
|
-
}
|
|
145
|
-
readableStream.destroy();
|
|
146
|
-
}).pipe(res);
|
|
147
|
-
} else res.status(requested.status).json("There are a problems with RPC provider");
|
|
231
|
+
const proxyedRPC = await requestRPC(chainId, req.body);
|
|
232
|
+
res.setHeader("Content-Type", proxyedRPC.headers.get("Content-Type") ?? "application/json");
|
|
233
|
+
if (!proxyedRPC.body) throw new Error("There are a problems with RPC provider");
|
|
234
|
+
// auto closes both Readable.fromWeb() and underlying proxyedRPC streams on error
|
|
235
|
+
await (0, $FgOCV$pipeline)((0, $FgOCV$Readable).fromWeb(proxyedRPC.body), validateMaxSteamSize(validationContext), res, {
|
|
236
|
+
signal: abortController.signal
|
|
237
|
+
});
|
|
148
238
|
} catch (error) {
|
|
149
|
-
if (error instanceof $
|
|
239
|
+
if (error instanceof (0, $09f9daeda4842558$export$bb8c263ad32caa8)) res.status(400).json(error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe));
|
|
150
240
|
else if (error instanceof Error) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
241
|
+
console.error("[rpcFactory]" + (error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe)));
|
|
242
|
+
if (!res.headersSent) res.status(500).json(error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe));
|
|
243
|
+
} else res.status(500).json((0, $09f9daeda4842558$export$56a189db5508cdcb));
|
|
244
|
+
} finally{
|
|
245
|
+
// forces pipeline closure in case of external error/abort
|
|
246
|
+
abortController.abort();
|
|
155
247
|
}
|
|
156
248
|
};
|
|
157
249
|
};
|
|
@@ -159,5 +251,7 @@ const $7d34ac0315ec4c05$export$9bfcc39f106ad871 = ({ metrics: { prefix: prefix,
|
|
|
159
251
|
|
|
160
252
|
|
|
161
253
|
|
|
162
|
-
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
export {$6ad1ea13f08e3d24$export$f0784a54fb0af903 as health, $29c7c52f2735c037$export$fc20b1372f40c2c5 as metricsFactory, $3f76f1f6d2274759$export$9bfcc39f106ad871 as rpcFactory};
|
|
163
257
|
//# sourceMappingURL=index.js.map
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"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","sources":["packages/next/pages/src/api/index.ts","packages/next/pages/src/api/health.ts","packages/next/pages/src/api/metricsFactory.ts","packages/next/pages/src/api/rpcFactory.ts"],"sourcesContent":["export { health } from './health'\nexport { type MetricsFactoryParameters, metricsFactory } from './metricsFactory'\nexport {\n type RpcProviders,\n DEFAULT_API_ERROR_MESSAGE,\n HEALTHY_RPC_SERVICES_ARE_OVER,\n UnsupportedChainIdError,\n UnsupportedHTTPMethodError,\n type RPCFactoryParams,\n rpcFactory,\n} from './rpcFactory'\n","import { NextApiRequest, NextApiResponse } from 'next'\n\nexport const health = (_req: NextApiRequest, res: NextApiResponse): void => {\n res.status(200).send({ status: 'ok' })\n}\n","import { NextApiRequest, NextApiResponse } from 'next'\nimport { Registry } from 'prom-client'\n\nexport type MetricsFactoryParameters = {\n registry: Registry\n}\n\nexport const metricsFactory =\n ({ registry }: MetricsFactoryParameters) =>\n async (_req: NextApiRequest, res: NextApiResponse) => {\n const collectedMetrics = await registry.metrics()\n res.send(collectedMetrics)\n }\n","import { Readable, Transform } from 'node:stream'\nimport { ReadableStream } from 'node:stream/web'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport { Counter, Registry } from 'prom-client'\nimport type { TrackedFetchRPC } from '@lidofinance/api-rpc'\nimport type { FetchRpcInitBody } from '@lidofinance/rpc'\nimport { iterateUrls } from '@lidofinance/rpc'\n\nexport type RpcProviders = Record<string | number, [string, ...string[]]>\n\nexport const DEFAULT_API_ERROR_MESSAGE = 'Something went wrong. Sorry, try again later :('\n\nexport const HEALTHY_RPC_SERVICES_ARE_OVER = 'Healthy RPC services are over!'\n\nexport class ClientError extends Error {}\nexport class UnsupportedChainIdError extends ClientError {\n constructor(message?: string) {\n super(message || 'Unsupported chainId')\n }\n}\n\nexport class UnsupportedHTTPMethodError extends ClientError {\n constructor(message?: string) {\n super(message || 'Unsupported HTTP method')\n }\n}\n\nexport class InvalidRequestError extends ClientError {\n constructor(message?: string) {\n super(message || 'Invalid Request')\n }\n}\n\nexport class SizeTooLargeError extends ClientError {\n constructor(message?: string) {\n super(message || 'Invalid Request')\n }\n}\n\nconst createSizeLogger = (MAX_SIZE: number) => {\n let bytesWritten = 0\n const logSizeStream = new Transform({\n transform(chunk, _encoding, callback) {\n bytesWritten += chunk.length\n if (bytesWritten > MAX_SIZE) {\n // Emit an error if size exceeds MAX_SIZE\n return callback(new SizeTooLargeError(`Stream size exceeds the maximum limit of ${MAX_SIZE} bytes`))\n }\n return callback(null, chunk) // Pass the chunk through\n },\n flush(callback) {\n callback()\n },\n })\n return logSizeStream\n}\n\nexport type RPCFactoryParams = {\n metrics: {\n prefix: string\n registry: Registry\n }\n providers: RpcProviders\n fetchRPC: TrackedFetchRPC\n defaultChain: string | number\n // If we don't specify allowed RPC methods, then we can't use\n // fetchRPC with prometheus, otherwise it will blow up, if someone will send arbitrary\n // methods\n allowedRPCMethods: string[]\n // filtration by eth_call to addresses\n allowedCallAddresses?: Record<number, string[]>\n allowedLogsAddresses?: Record<number, string[]>\n disallowEmptyAddressGetLogs?: boolean\n maxBatchCount?: number\n maxResponseSize?: number\n}\n\nexport const rpcFactory = ({\n metrics: { prefix, registry },\n providers,\n fetchRPC,\n defaultChain,\n allowedRPCMethods,\n allowedCallAddresses = {},\n allowedLogsAddresses = {},\n maxBatchCount,\n maxResponseSize = 1_000_000, // ~1MB,\n disallowEmptyAddressGetLogs = false,\n}: RPCFactoryParams) => {\n const rpcRequestBlocked = new Counter({\n name: prefix + 'rpc_service_request_blocked',\n help: 'RPC service request blocked',\n labelNames: [],\n registers: [],\n })\n registry.registerMetric(rpcRequestBlocked)\n\n const allowedCallAddressMap = Object.entries(allowedCallAddresses).reduce((acc, [chainId, addresses]) => {\n acc[chainId] = new Set(addresses.map((a) => a.toLowerCase()))\n return acc\n }, {} as Record<string, Set<string>>)\n\n const allowedLogsAddressMap = Object.entries(allowedLogsAddresses).reduce((acc, [chainId, addresses]) => {\n acc[chainId] = new Set(addresses.map((a) => a.toLowerCase()))\n return acc\n }, {} as Record<string, Set<string>>)\n\n return async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {\n try {\n // Accept only POST requests\n if (req.method !== 'POST') {\n // We don't care about tracking blocked requests here\n throw new UnsupportedHTTPMethodError()\n }\n\n const chainId = Number(req.query.chainId || defaultChain)\n\n // Allow only chainId of specified chains\n if (providers[chainId] == null) {\n // We don't care about tracking blocked requests here\n throw new UnsupportedChainIdError()\n }\n\n const requests = Array.isArray(req.body) ? req.body : [req.body]\n\n if (typeof maxBatchCount === 'number' && requests.length > maxBatchCount) {\n throw new InvalidRequestError(`Too many batched requests`)\n }\n\n // Check if provided methods are allowed\n // We throw HTTP error for ANY invalid RPC request out of batch\n // because we assume that frontend must not send invalid requests\n for (const { method, params } of requests) {\n if (typeof method !== 'string') {\n throw new InvalidRequestError(`RPC method isn't string`)\n }\n if (!allowedRPCMethods.includes(method)) {\n rpcRequestBlocked.inc()\n throw new InvalidRequestError(`RPC method ${method} isn't allowed`)\n }\n if (method === 'eth_call' && allowedCallAddressMap[chainId]) {\n if (Array.isArray(params) && typeof params[0] === 'object' && typeof params[0].to === 'string') {\n if (!allowedCallAddressMap[chainId].has(params[0].to.toLowerCase())) {\n rpcRequestBlocked.inc()\n throw new InvalidRequestError(`Address not allowed for eth_call`)\n }\n } else throw new InvalidRequestError(`RPC method eth_call is invalid`)\n }\n if (method === 'eth_getLogs' && (disallowEmptyAddressGetLogs || allowedLogsAddressMap[chainId])) {\n if (Array.isArray(params) && typeof params[0] === 'object') {\n const address = params[0].address\n if (disallowEmptyAddressGetLogs && (!address || (Array.isArray(address) && address.length === 0))) {\n rpcRequestBlocked.inc()\n throw new InvalidRequestError(`No empty address on eth_getLogs`)\n }\n const addresses = Array.isArray(address) ? address : [address]\n if (\n addresses.some(\n (eventAddress) =>\n // needs this check before toLowerCase\n typeof eventAddress !== 'string' || !allowedLogsAddressMap[chainId].has(eventAddress.toLowerCase()),\n )\n ) {\n rpcRequestBlocked.inc()\n throw new InvalidRequestError(`Address not allowed for eth_getLogs`)\n }\n } else throw new InvalidRequestError(`Invalid eth_getLogs`)\n }\n }\n\n const requested = await iterateUrls(\n providers[chainId],\n // TODO: consider adding verification that body is actually matches FetchRpcInitBody\n (url) => fetchRPC(url, { body: req.body as FetchRpcInitBody }, { chainId }),\n // eslint-disable-next-line @typescript-eslint/unbound-method\n console.error,\n )\n\n res.setHeader('Content-Type', requested.headers.get('Content-Type') ?? 'application/json')\n if (requested.body) {\n const sizeLimit = createSizeLogger(maxResponseSize)\n const readableStream = Readable.fromWeb(requested.body as ReadableStream)\n readableStream\n .pipe(sizeLimit)\n .on('error', (error) => {\n if (error instanceof SizeTooLargeError) {\n console.warn(`[rpcFactory] RPC response too large: ${JSON.stringify(requests)}`)\n res.status(413).end() // Payload Too Large\n } else {\n res.statusCode = 500\n res.end(DEFAULT_API_ERROR_MESSAGE)\n }\n readableStream.destroy()\n })\n .pipe(res)\n } else {\n res.status(requested.status).json('There are a problems with RPC provider')\n }\n } catch (error) {\n if (error instanceof ClientError) {\n res.status(400).json(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n } else if (error instanceof Error) {\n // TODO: check if there are errors duplication with iterateUrls\n console.error(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n res.status(500).json(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n } else {\n res.status(500).json(HEALTHY_RPC_SERVICES_ARE_OVER)\n }\n }\n }\n}\n"],"names":[],"version":3,"file":"index.js.map","sourceRoot":"../../../../../"}
|
|
1
|
+
{"mappings":"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","sources":["packages/next/pages/src/api/index.ts","packages/next/pages/src/api/health.ts","packages/next/pages/src/api/metricsFactory.ts","packages/next/pages/src/api/rpcFactory/index.ts","packages/next/pages/src/api/rpcFactory/rpc-factory.ts","packages/next/pages/src/api/rpcFactory/errors.ts","packages/next/pages/src/api/rpcFactory/validation.ts"],"sourcesContent":["export { health } from './health'\nexport { type MetricsFactoryParameters, metricsFactory } from './metricsFactory'\nexport { type RPCFactoryParams, type RPCFactoryValidationParams, type RpcProviders, rpcFactory } from './rpcFactory'\n","import { NextApiRequest, NextApiResponse } from 'next'\n\nexport const health = (_req: NextApiRequest, res: NextApiResponse): void => {\n res.status(200).send({ status: 'ok' })\n}\n","import { NextApiRequest, NextApiResponse } from 'next'\nimport { Registry } from 'prom-client'\n\nexport type MetricsFactoryParameters = {\n registry: Registry\n}\n\nexport const metricsFactory =\n ({ registry }: MetricsFactoryParameters) =>\n async (_req: NextApiRequest, res: NextApiResponse) => {\n const collectedMetrics = await registry.metrics()\n res.send(collectedMetrics)\n }\n","export { rpcFactory } from './rpc-factory'\nexport type { RPCFactoryParams, RPCFactoryValidationParams, RpcProviders } from './types'\n","import { Readable } from 'node:stream'\nimport { ReadableStream } from 'node:stream/web'\nimport { pipeline } from 'node:stream/promises'\n\nimport { Counter } from 'prom-client'\nimport { iterateUrls } from '@lidofinance/rpc'\n\nimport { DEFAULT_API_ERROR_MESSAGE, HEALTHY_RPC_SERVICES_ARE_OVER, ClientError } from './errors'\nimport {\n baseRequestValidationFactory,\n ethCallValidationFactory,\n ethGetLogsValidationFactory,\n rpcMethodsValidationFactory,\n shouldValidateRpcMethod,\n streamMaxSizeValidationFactory,\n} from './validation'\n\nimport type { RPCFactoryParams } from './types'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport type { FetchRpcInitBody } from '@lidofinance/rpc'\n\nexport const rpcFactory = ({ metrics, providers, fetchRPC, defaultChain, validation }: RPCFactoryParams) => {\n const {\n allowedRPCMethods,\n allowedCallAddresses,\n allowedLogsAddresses,\n maxResponseSize = 1_000_000,\n maxBatchCount = 20,\n currentBlockTTLms = 60_000,\n maxGetLogsRange = 20_000,\n blockEmptyAddressGetLogs = true,\n } = validation\n\n // optional metrics\n const { registry, prefix = '' } = metrics ?? {}\n const rpcRequestBlocked = new Counter({\n name: prefix + 'rpc_service_request_blocked',\n help: 'RPC service request blocked',\n labelNames: ['reason'],\n registers: [],\n })\n registry?.registerMetric(rpcRequestBlocked)\n\n const validateBaseRequest = baseRequestValidationFactory(defaultChain, providers, maxBatchCount)\n\n const validateRpcMethod = rpcMethodsValidationFactory(allowedRPCMethods)\n\n const validateEthCall =\n allowedCallAddresses && shouldValidateRpcMethod('eth_call', allowedRPCMethods)\n ? ethCallValidationFactory(allowedCallAddresses)\n : undefined\n\n const validateEthGetLogs =\n (allowedLogsAddresses || blockEmptyAddressGetLogs) && shouldValidateRpcMethod('eth_getLogs', allowedRPCMethods)\n ? ethGetLogsValidationFactory(allowedLogsAddresses, blockEmptyAddressGetLogs, maxGetLogsRange, currentBlockTTLms)\n : undefined\n\n const validateMaxSteamSize = streamMaxSizeValidationFactory(maxResponseSize)\n\n const requestRPC = (chainId: number, body: FetchRpcInitBody) =>\n iterateUrls(\n providers[chainId],\n // TODO: consider adding verification that body is actually matches FetchRpcInitBody\n (url) => fetchRPC(url, { body }, { chainId }),\n // eslint-disable-next-line @typescript-eslint/unbound-method\n console.error,\n )\n\n return async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {\n const abortController = new AbortController()\n try {\n const { chainId, requests } = validateBaseRequest(req)\n\n const validationContext = {\n chainId,\n rpcRequestBlocked,\n requestRPC,\n }\n\n // We throw HTTP error for ANY invalid RPC request out of batch\n // because we assume that frontend must not send invalid requests at all\n for (const request of requests) {\n validateRpcMethod(request, validationContext)\n\n const method = request.method\n\n if (method === 'eth_call' && validateEthCall) {\n validateEthCall(request, validationContext)\n }\n if (method === 'eth_getLogs' && validateEthGetLogs) {\n await validateEthGetLogs(request, validationContext)\n }\n }\n\n const proxyedRPC = await requestRPC(chainId, req.body as FetchRpcInitBody)\n\n res.setHeader('Content-Type', proxyedRPC.headers.get('Content-Type') ?? 'application/json')\n if (!proxyedRPC.body) {\n throw new Error('There are a problems with RPC provider')\n }\n\n // auto closes both Readable.fromWeb() and underlying proxyedRPC streams on error\n await pipeline(\n Readable.fromWeb(proxyedRPC.body as ReadableStream),\n validateMaxSteamSize(validationContext),\n res,\n {\n signal: abortController.signal,\n },\n )\n } catch (error) {\n if (error instanceof ClientError) {\n res.status(400).json(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n } else if (error instanceof Error) {\n console.error('[rpcFactory]' + (error.message ?? DEFAULT_API_ERROR_MESSAGE))\n if (!res.headersSent) {\n res.status(500).json(error.message ?? DEFAULT_API_ERROR_MESSAGE)\n }\n } else {\n res.status(500).json(HEALTHY_RPC_SERVICES_ARE_OVER)\n }\n } finally {\n // forces pipeline closure in case of external error/abort\n abortController.abort()\n }\n }\n}\n","export const DEFAULT_API_ERROR_MESSAGE = 'Something went wrong. Sorry, try again later :('\n\nexport const HEALTHY_RPC_SERVICES_ARE_OVER = 'Healthy RPC services are over!'\n\nexport class ClientError extends Error {}\n\nexport class UnsupportedChainIdError extends ClientError {\n constructor(message?: string) {\n super(message || 'Unsupported chainId')\n }\n}\n\nexport class UnsupportedHTTPMethodError extends ClientError {\n constructor(message?: string) {\n super(message || 'Unsupported HTTP method')\n }\n}\n\nexport class InvalidRequestError extends ClientError {\n constructor(message?: string) {\n super(message || 'Invalid Request')\n }\n}\n\nexport class SizeTooLargeError extends ClientError {\n constructor(message?: string) {\n super(message || 'Response size too large')\n }\n}\n","import { Transform } from 'node:stream'\n\nimport { Cache } from 'memory-cache'\n\nimport {\n ClientError,\n InvalidRequestError,\n SizeTooLargeError,\n UnsupportedChainIdError,\n UnsupportedHTTPMethodError,\n} from './errors'\n\nimport type { NextApiRequest } from 'next'\nimport type { RpcRequest, ValidationContext } from './types'\n\n// validation factories\n\nconst zeroBN = BigInt(0)\n\nexport const baseRequestValidationFactory = (\n defaultChain: number | string,\n providers: Record<string, unknown>,\n maxBatchSize?: number,\n) => {\n return (req: NextApiRequest) => {\n if (req.method !== 'POST') {\n // We don't care about tracking blocked requests here\n throw new UnsupportedHTTPMethodError()\n }\n\n const chainId = Number(req.query.chainId || defaultChain)\n\n // Allow only chainId of specified chains\n if (providers[chainId] == null) {\n // We don't care about tracking blocked requests here\n throw new UnsupportedChainIdError()\n }\n\n const requests = Array.isArray(req.body) ? req.body : [req.body]\n\n if (typeof maxBatchSize === 'number' && requests.length > maxBatchSize) {\n throw new InvalidRequestError(`Too many batched requests`)\n }\n return { chainId, requests }\n }\n}\n\nexport const rpcMethodsValidationFactory = (allowedMethods?: string[]) => {\n const methodsMap = new Set(allowedMethods ?? [])\n\n return (request: RpcRequest, context: ValidationContext) => {\n const method = request.method\n if (typeof method !== 'string') {\n throw new InvalidRequestError(`RPC method isn't string`)\n }\n if (allowedMethods && !methodsMap.has(request.method)) {\n context.rpcRequestBlocked.inc({ reason: 'method not allowed' })\n throw new InvalidRequestError(`RPC method ${method} not allowed`)\n }\n }\n}\n\nexport const ethCallValidationFactory = (allowedAddress: Record<number, string[]>) => {\n const allowedCallAddressMap = createAllowedAddressMap(allowedAddress)\n\n return (request: RpcRequest, { chainId, rpcRequestBlocked }: ValidationContext) => {\n const { params } = request\n if (\n Array.isArray(params) &&\n params[0] &&\n typeof params[0] === 'object' &&\n 'to' in params[0] &&\n typeof params[0].to === 'string'\n ) {\n if (!allowedCallAddressMap[chainId]?.has(params[0].to.toLowerCase())) {\n rpcRequestBlocked.inc({ reason: 'address not allowed for eth_call' })\n throw new InvalidRequestError(`Address not allowed for eth_call`)\n }\n } else throw new InvalidRequestError(`RPC method eth_call is invalid`)\n }\n}\n\nexport const ethGetLogsValidationFactory = (\n allowedAddress: Record<number, string[]> | undefined,\n blockEmptyAddressGetLogs: boolean | undefined,\n maxBlockRange: number | undefined,\n currentBlockTTL = 60_000,\n) => {\n const allowedAddressMap = createAllowedAddressMap(allowedAddress ?? {})\n\n const maxBlockRangeBN = maxBlockRange ? BigInt(maxBlockRange) : zeroBN\n\n const currentBlock = new Cache<number, bigint>()\n\n return async (request: RpcRequest, { chainId, rpcRequestBlocked, requestRPC }: ValidationContext) => {\n try {\n const params = request.params[0] as any\n const fromBlock: string = params.fromBlock\n const toBlock: string = params.toBlock ?? 'latest'\n\n if (blockEmptyAddressGetLogs || allowedAddress) {\n // address validation\n const address: string | string[] = params.address\n\n if (blockEmptyAddressGetLogs && !address)\n throw new InvalidRequestError(`RPC method eth_getLogs address is invalid`)\n\n const addresses = Array.isArray(address) ? address : [address]\n\n if (blockEmptyAddressGetLogs && addresses.length === 0) {\n throw new InvalidRequestError(`RPC method eth_getLogs address is invalid`)\n }\n if (\n allowedAddress &&\n addresses.some(\n (eventAddress) =>\n // needs this check before toLowerCase\n typeof eventAddress !== 'string' || !allowedAddressMap[chainId]?.has(eventAddress.toLowerCase()),\n )\n ) {\n rpcRequestBlocked.inc({\n reason: 'address not allowed for eth_getLogs',\n })\n throw new InvalidRequestError(`Address not allowed for eth_getLogs`)\n }\n }\n\n // block range validation\n if (maxBlockRange) {\n if (fromBlock === 'earliest') throw new InvalidRequestError(`RPC method eth_getLogs fromBlock is invalid`)\n\n const shouldValidateBlockDistance = fromBlock.startsWith('0x')\n\n if (shouldValidateBlockDistance) {\n const normalizedFromBlock = BigInt(fromBlock)\n let normalizedToBlock: bigint\n if (toBlock.startsWith('0x')) normalizedToBlock = BigInt(toBlock)\n else {\n const cached = currentBlock.get(chainId)\n if (cached) {\n normalizedToBlock = cached\n } else {\n normalizedToBlock = await requestRPC(chainId, {\n id: 1,\n jsonrpc: '2.0',\n method: 'eth_blockNumber',\n })\n .then((res) => res.json())\n .then((res) => BigInt(res.result))\n currentBlock.put(chainId, normalizedToBlock, currentBlockTTL)\n }\n }\n const range = normalizedToBlock - normalizedFromBlock\n\n if (range < zeroBN || range > maxBlockRangeBN) {\n rpcRequestBlocked.inc({\n reason: 'eth_getLogs range is invalid',\n })\n throw new InvalidRequestError(`RPC method eth_getLogs range is invalid`)\n }\n }\n }\n } catch (error) {\n if (error instanceof ClientError) throw error\n throw new InvalidRequestError(`RPC method eth_getLogs is invalid`)\n }\n }\n}\n\nexport const streamMaxSizeValidationFactory = (MAX_SIZE: number) => (context: ValidationContext) => {\n let bytesWritten = 0\n return new Transform({\n transform(chunk, _encoding, callback) {\n bytesWritten += chunk.length\n if (bytesWritten > MAX_SIZE) {\n // Emit an error if size exceeds MAX_SIZE\n context.rpcRequestBlocked.inc({ reason: 'response too large' })\n return callback(new SizeTooLargeError(`Stream size exceeds the maximum limit of ${MAX_SIZE} bytes`))\n }\n return callback(null, chunk) // Pass the chunk through\n },\n flush(callback) {\n callback()\n },\n })\n}\n\n// utils\n\nexport const shouldValidateRpcMethod = (method: string, allowedRPCMethods?: string[]) =>\n !allowedRPCMethods || allowedRPCMethods.includes(method)\n\nconst createAllowedAddressMap = (allowedAddress: Record<number, string[]>) => {\n return Object.entries(allowedAddress).reduce((acc, [chainId, addresses]) => {\n acc[chainId] = new Set(addresses.map((a) => a.toLowerCase()))\n return acc\n }, {} as Record<string, Set<string>>)\n}\n"],"names":[],"version":3,"file":"index.js.map","sourceRoot":"../../../../../"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const DEFAULT_API_ERROR_MESSAGE = "Something went wrong. Sorry, try again later :(";
|
|
2
|
+
export declare const HEALTHY_RPC_SERVICES_ARE_OVER = "Healthy RPC services are over!";
|
|
3
|
+
export declare class ClientError extends Error {
|
|
4
|
+
}
|
|
5
|
+
export declare class UnsupportedChainIdError extends ClientError {
|
|
6
|
+
constructor(message?: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class UnsupportedHTTPMethodError extends ClientError {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class InvalidRequestError extends ClientError {
|
|
12
|
+
constructor(message?: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class SizeTooLargeError extends ClientError {
|
|
15
|
+
constructor(message?: string);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/errors.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,yBAAyB,oDAAoD,CAAA;AAE1F,eAAO,MAAM,6BAA6B,mCAAmC,CAAA;AAE7E,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAEzC,qBAAa,uBAAwB,SAAQ,WAAW;gBAC1C,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,0BAA2B,SAAQ,WAAW;gBAC7C,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,mBAAoB,SAAQ,WAAW;gBACtC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,iBAAkB,SAAQ,WAAW;gBACpC,OAAO,CAAC,EAAE,MAAM;CAG7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,YAAY,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RPCFactoryParams } from './types';
|
|
2
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
3
|
+
export declare const rpcFactory: ({ metrics, providers, fetchRPC, defaultChain, validation }: RPCFactoryParams) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
4
|
+
//# sourceMappingURL=rpc-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-factory.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/rpc-factory.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAG3D,eAAO,MAAM,UAAU,+DAAgE,gBAAgB,WA+ClF,cAAc,OAAO,eAAe,KAAG,QAAQ,IAAI,CA0DvE,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TrackedFetchRPC } from '@lidofinance/api-rpc';
|
|
2
|
+
import type { FetchRpcInitBody } from '@lidofinance/rpc';
|
|
3
|
+
import type { Counter, Registry } from 'prom-client';
|
|
4
|
+
export type RpcProviders = Record<string | number, [string, ...string[]]>;
|
|
5
|
+
export type RPCFactoryValidationParams = {
|
|
6
|
+
maxBatchCount?: number;
|
|
7
|
+
maxResponseSize?: number;
|
|
8
|
+
allowedRPCMethods?: string[];
|
|
9
|
+
allowedCallAddresses?: Record<number, string[]>;
|
|
10
|
+
allowedLogsAddresses?: Record<number, string[]>;
|
|
11
|
+
blockEmptyAddressGetLogs?: boolean;
|
|
12
|
+
maxGetLogsRange?: number;
|
|
13
|
+
currentBlockTTLms?: number;
|
|
14
|
+
};
|
|
15
|
+
export type RPCFactoryParams = {
|
|
16
|
+
metrics?: {
|
|
17
|
+
prefix: string;
|
|
18
|
+
registry: Registry;
|
|
19
|
+
};
|
|
20
|
+
providers: RpcProviders;
|
|
21
|
+
fetchRPC: TrackedFetchRPC;
|
|
22
|
+
defaultChain: string | number;
|
|
23
|
+
validation: RPCFactoryValidationParams;
|
|
24
|
+
};
|
|
25
|
+
export type RpcRequest = {
|
|
26
|
+
method: string;
|
|
27
|
+
params: unknown[];
|
|
28
|
+
};
|
|
29
|
+
export type ValidationContext = {
|
|
30
|
+
chainId: number;
|
|
31
|
+
rpcRequestBlocked: Counter<'reason'>;
|
|
32
|
+
requestRPC: (chainId: number, body: FetchRpcInitBody) => Promise<Response>;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEpD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAEzE,MAAM,MAAM,0BAA0B,GAAG;IACvC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/C,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/C,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE;QACR,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,QAAQ,CAAA;KACnB,CAAA;IACD,SAAS,EAAE,YAAY,CAAA;IACvB,QAAQ,EAAE,eAAe,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;IAC7B,UAAU,EAAE,0BAA0B,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,EAAE,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IACpC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3E,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Transform } from 'node:stream';
|
|
3
|
+
import type { NextApiRequest } from 'next';
|
|
4
|
+
import type { RpcRequest, ValidationContext } from './types';
|
|
5
|
+
export declare const baseRequestValidationFactory: (defaultChain: number | string, providers: Record<string, unknown>, maxBatchSize?: number) => (req: NextApiRequest) => {
|
|
6
|
+
chainId: number;
|
|
7
|
+
requests: any[];
|
|
8
|
+
};
|
|
9
|
+
export declare const rpcMethodsValidationFactory: (allowedMethods?: string[]) => (request: RpcRequest, context: ValidationContext) => void;
|
|
10
|
+
export declare const ethCallValidationFactory: (allowedAddress: Record<number, string[]>) => (request: RpcRequest, { chainId, rpcRequestBlocked }: ValidationContext) => void;
|
|
11
|
+
export declare const ethGetLogsValidationFactory: (allowedAddress: Record<number, string[]> | undefined, blockEmptyAddressGetLogs: boolean | undefined, maxBlockRange: number | undefined, currentBlockTTL?: number) => (request: RpcRequest, { chainId, rpcRequestBlocked, requestRPC }: ValidationContext) => Promise<void>;
|
|
12
|
+
export declare const streamMaxSizeValidationFactory: (MAX_SIZE: number) => (context: ValidationContext) => Transform;
|
|
13
|
+
export declare const shouldValidateRpcMethod: (method: string, allowedRPCMethods?: string[]) => boolean;
|
|
14
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/validation.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAYvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAM5D,eAAO,MAAM,4BAA4B,iBACzB,MAAM,GAAG,MAAM,aAClB,OAAO,MAAM,EAAE,OAAO,CAAC,iBACnB,MAAM,WAER,cAAc;;;CAqB5B,CAAA;AAED,eAAO,MAAM,2BAA2B,oBAAqB,MAAM,EAAE,eAGlD,UAAU,WAAW,iBAAiB,SAUxD,CAAA;AAED,eAAO,MAAM,wBAAwB,mBAAoB,OAAO,MAAM,EAAE,MAAM,EAAE,CAAC,eAG9D,UAAU,kCAAkC,iBAAiB,SAe/E,CAAA;AAED,eAAO,MAAM,2BAA2B,mBACtB,OAAO,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,SAAS,4BAC1B,OAAO,GAAG,SAAS,iBAC9B,MAAM,GAAG,SAAS,yCASV,UAAU,8CAA8C,iBAAiB,kBAyEjG,CAAA;AAED,eAAO,MAAM,8BAA8B,aAAc,MAAM,eAAe,iBAAiB,cAgB9F,CAAA;AAID,eAAO,MAAM,uBAAuB,WAAY,MAAM,sBAAsB,MAAM,EAAE,YAC1B,CAAA"}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"url": "https://github.com/lidofinance/warehouse/issues"
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.48.0",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|
|
@@ -58,22 +58,26 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@lidofinance/api-logger": "~0.
|
|
62
|
-
"@lidofinance/api-rpc": "~0.
|
|
63
|
-
"@lidofinance/rpc": "~0.
|
|
64
|
-
"@lidofinance/ui-pages": "~0.
|
|
61
|
+
"@lidofinance/api-logger": "~0.48.0",
|
|
62
|
+
"@lidofinance/api-rpc": "~0.48.0",
|
|
63
|
+
"@lidofinance/rpc": "~0.48.0",
|
|
64
|
+
"@lidofinance/ui-pages": "~0.48.0",
|
|
65
65
|
"next": "12 || 13",
|
|
66
66
|
"prom-client": "^14.1.0",
|
|
67
67
|
"react": "17 || 18"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@lidofinance/api-logger": "~0.
|
|
71
|
-
"@lidofinance/api-rpc": "~0.
|
|
72
|
-
"@lidofinance/config-prettier": "~0.
|
|
73
|
-
"@lidofinance/rpc": "~0.
|
|
74
|
-
"@lidofinance/ui-pages": "~0.
|
|
70
|
+
"@lidofinance/api-logger": "~0.48.0",
|
|
71
|
+
"@lidofinance/api-rpc": "~0.48.0",
|
|
72
|
+
"@lidofinance/config-prettier": "~0.48.0",
|
|
73
|
+
"@lidofinance/rpc": "~0.48.0",
|
|
74
|
+
"@lidofinance/ui-pages": "~0.48.0",
|
|
75
|
+
"@types/memory-cache": "^0.2.6",
|
|
75
76
|
"@types/react": "^18.0.25",
|
|
76
77
|
"next": "^12.3.1",
|
|
77
78
|
"prom-client": "^14.1.0"
|
|
79
|
+
},
|
|
80
|
+
"dependencies": {
|
|
81
|
+
"memory-cache": "^0.2.0"
|
|
78
82
|
}
|
|
79
83
|
}
|
package/dist/api/rpcFactory.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
-
import { Registry } from 'prom-client';
|
|
3
|
-
import type { TrackedFetchRPC } from '@lidofinance/api-rpc';
|
|
4
|
-
export type RpcProviders = Record<string | number, [string, ...string[]]>;
|
|
5
|
-
export declare const DEFAULT_API_ERROR_MESSAGE = "Something went wrong. Sorry, try again later :(";
|
|
6
|
-
export declare const HEALTHY_RPC_SERVICES_ARE_OVER = "Healthy RPC services are over!";
|
|
7
|
-
export declare class ClientError extends Error {
|
|
8
|
-
}
|
|
9
|
-
export declare class UnsupportedChainIdError extends ClientError {
|
|
10
|
-
constructor(message?: string);
|
|
11
|
-
}
|
|
12
|
-
export declare class UnsupportedHTTPMethodError extends ClientError {
|
|
13
|
-
constructor(message?: string);
|
|
14
|
-
}
|
|
15
|
-
export declare class InvalidRequestError extends ClientError {
|
|
16
|
-
constructor(message?: string);
|
|
17
|
-
}
|
|
18
|
-
export declare class SizeTooLargeError extends ClientError {
|
|
19
|
-
constructor(message?: string);
|
|
20
|
-
}
|
|
21
|
-
export type RPCFactoryParams = {
|
|
22
|
-
metrics: {
|
|
23
|
-
prefix: string;
|
|
24
|
-
registry: Registry;
|
|
25
|
-
};
|
|
26
|
-
providers: RpcProviders;
|
|
27
|
-
fetchRPC: TrackedFetchRPC;
|
|
28
|
-
defaultChain: string | number;
|
|
29
|
-
allowedRPCMethods: string[];
|
|
30
|
-
allowedCallAddresses?: Record<number, string[]>;
|
|
31
|
-
allowedLogsAddresses?: Record<number, string[]>;
|
|
32
|
-
disallowEmptyAddressGetLogs?: boolean;
|
|
33
|
-
maxBatchCount?: number;
|
|
34
|
-
maxResponseSize?: number;
|
|
35
|
-
};
|
|
36
|
-
export declare const rpcFactory: ({ metrics: { prefix, registry }, providers, fetchRPC, defaultChain, allowedRPCMethods, allowedCallAddresses, allowedLogsAddresses, maxBatchCount, maxResponseSize, disallowEmptyAddressGetLogs, }: RPCFactoryParams) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
37
|
-
//# sourceMappingURL=rpcFactory.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rpcFactory.d.ts","sourceRoot":"","sources":["../../src/api/rpcFactory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAC3D,OAAO,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAI3D,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAEzE,eAAO,MAAM,yBAAyB,oDAAoD,CAAA;AAE1F,eAAO,MAAM,6BAA6B,mCAAmC,CAAA;AAE7E,qBAAa,WAAY,SAAQ,KAAK;CAAG;AACzC,qBAAa,uBAAwB,SAAQ,WAAW;gBAC1C,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,0BAA2B,SAAQ,WAAW;gBAC7C,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,mBAAoB,SAAQ,WAAW;gBACtC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,qBAAa,iBAAkB,SAAQ,WAAW;gBACpC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAoBD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,QAAQ,CAAA;KACnB,CAAA;IACD,SAAS,EAAE,YAAY,CAAA;IACvB,QAAQ,EAAE,eAAe,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;IAI7B,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAE3B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/C,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/C,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,eAAO,MAAM,UAAU,sMAWpB,gBAAgB,WAmBE,cAAc,OAAO,eAAe,KAAG,QAAQ,IAAI,CAuGvE,CAAA"}
|