@lidofinance/next-pages 0.58.0 → 0.60.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 +17 -0
- package/dist/api/index.js +21 -4
- package/dist/api/index.js.map +1 -1
- package/dist/api/rpcFactory/rpc-factory.d.ts +1 -1
- package/dist/api/rpcFactory/rpc-factory.d.ts.map +1 -1
- package/dist/api/rpcFactory/types.d.ts +2 -0
- package/dist/api/rpcFactory/types.d.ts.map +1 -1
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -146,11 +146,28 @@ const rpc = rpcFactory({
|
|
|
146
146
|
// 1mb max response after which data stream is interrupted and 413 is returned
|
|
147
147
|
maxResponseSize: 1_000_000, // DEFAULT
|
|
148
148
|
},
|
|
149
|
+
|
|
150
|
+
// optional: pass a masked logger (e.g. @lidofinance/api-logger) to receive
|
|
151
|
+
// structured fallback log lines. Defaults to `console`, which works fine with
|
|
152
|
+
// `next-logger` (pino wrapper) that shims console.*
|
|
153
|
+
// serverLogger: serverLogger,
|
|
149
154
|
})
|
|
150
155
|
|
|
151
156
|
export default rpc
|
|
152
157
|
```
|
|
153
158
|
|
|
159
|
+
When an RPC provider fails and a fallback is available, `rpcFactory` logs a warning of the form:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
[rpcFactory] RPC provider <hostname> failed on attempt <n> (<error message>), switching to fallback <next hostname>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
When every provider for the chain has been exhausted, it logs an error instead:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
[rpcFactory] all RPC providers for chain <chainId> failed after <n> attempts (<error message>)
|
|
169
|
+
```
|
|
170
|
+
|
|
154
171
|
### UI pages
|
|
155
172
|
|
|
156
173
|
#### Page Error (ready implementations)
|
package/dist/api/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import {Readable as $FgOCV$Readable, Transform as $FgOCV$Transform} from "node:s
|
|
|
2
2
|
import {pipeline as $FgOCV$pipeline} from "node:stream/promises";
|
|
3
3
|
import {Counter as $FgOCV$Counter} from "prom-client";
|
|
4
4
|
import {iterateUrls as $FgOCV$iterateUrls} from "@lidofinance/rpc";
|
|
5
|
+
import {getProviderLabel as $FgOCV$getProviderLabel} from "@lidofinance/api-metrics";
|
|
5
6
|
import {Cache as $FgOCV$Cache} from "memory-cache";
|
|
6
7
|
|
|
7
8
|
const $6ad1ea13f08e3d24$export$f0784a54fb0af903 = (_req, res)=>{
|
|
@@ -21,6 +22,7 @@ const $29c7c52f2735c037$export$fc20b1372f40c2c5 = ({ registry: registry })=>asyn
|
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
|
|
24
26
|
const $09f9daeda4842558$export$c982278a0ddc4dbe = "Something went wrong. Sorry, try again later :(";
|
|
25
27
|
const $09f9daeda4842558$export$56a189db5508cdcb = "Healthy RPC services are over!";
|
|
26
28
|
class $09f9daeda4842558$export$bb8c263ad32caa8 extends Error {
|
|
@@ -186,7 +188,7 @@ const $5a404da009e373d8$var$createAllowedAddressMap = (allowedAddress)=>{
|
|
|
186
188
|
};
|
|
187
189
|
|
|
188
190
|
|
|
189
|
-
const $3f76f1f6d2274759$export$9bfcc39f106ad871 = ({ metrics: metrics, providers: providers, fetchRPC: fetchRPC, defaultChain: defaultChain, validation: validation })=>{
|
|
191
|
+
const $3f76f1f6d2274759$export$9bfcc39f106ad871 = ({ metrics: metrics, providers: providers, fetchRPC: fetchRPC, defaultChain: defaultChain, validation: validation, serverLogger: serverLogger = console })=>{
|
|
190
192
|
const { allowedRPCMethods: allowedRPCMethods, allowedCallAddresses: allowedCallAddresses, allowedLogsAddresses: allowedLogsAddresses, maxResponseSize: maxResponseSize = 1000000, maxBatchCount: maxBatchCount = 20, currentBlockTTLms: currentBlockTTLms = 60000, maxGetLogsRange: maxGetLogsRange = 20000, blockEmptyAddressGetLogs: blockEmptyAddressGetLogs = true } = validation;
|
|
191
193
|
// optional metrics
|
|
192
194
|
const { registry: registry, prefix: prefix = "" } = metrics ?? {};
|
|
@@ -209,8 +211,23 @@ const $3f76f1f6d2274759$export$9bfcc39f106ad871 = ({ metrics: metrics, providers
|
|
|
209
211
|
body: body
|
|
210
212
|
}, {
|
|
211
213
|
chainId: chainId
|
|
212
|
-
}),
|
|
213
|
-
|
|
214
|
+
}), (error, { failedUrl: failedUrl, nextUrl: nextUrl, attempt: attempt })=>{
|
|
215
|
+
const failedProvider = (0, $FgOCV$getProviderLabel)(failedUrl);
|
|
216
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
217
|
+
if (nextUrl) {
|
|
218
|
+
const nextProvider = (0, $FgOCV$getProviderLabel)(nextUrl);
|
|
219
|
+
serverLogger.warn(`[rpcFactory] RPC provider ${failedProvider} failed on attempt ${attempt} (${message}), switching to fallback ${nextProvider}`, {
|
|
220
|
+
chainId: chainId,
|
|
221
|
+
failedProvider: failedProvider,
|
|
222
|
+
nextProvider: nextProvider,
|
|
223
|
+
attempt: attempt
|
|
224
|
+
});
|
|
225
|
+
} else serverLogger.error(`[rpcFactory] all RPC providers for chain ${chainId} failed after ${attempt} attempts (${message})`, {
|
|
226
|
+
chainId: chainId,
|
|
227
|
+
failedProvider: failedProvider,
|
|
228
|
+
attempt: attempt
|
|
229
|
+
});
|
|
230
|
+
});
|
|
214
231
|
return async (req, res)=>{
|
|
215
232
|
const abortController = new AbortController();
|
|
216
233
|
try {
|
|
@@ -238,7 +255,7 @@ const $3f76f1f6d2274759$export$9bfcc39f106ad871 = ({ metrics: metrics, providers
|
|
|
238
255
|
} catch (error) {
|
|
239
256
|
if (error instanceof (0, $09f9daeda4842558$export$bb8c263ad32caa8)) res.status(400).json(error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe));
|
|
240
257
|
else if (error instanceof Error) {
|
|
241
|
-
|
|
258
|
+
serverLogger.error("[rpcFactory]" + (error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe)));
|
|
242
259
|
if (!res.headersSent) res.status(500).json(error.message ?? (0, $09f9daeda4842558$export$c982278a0ddc4dbe));
|
|
243
260
|
} else res.status(500).json((0, $09f9daeda4842558$export$56a189db5508cdcb));
|
|
244
261
|
} finally{
|
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/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":"../../../../../"}
|
|
1
|
+
{"mappings":";;;;;;;ACEO,MAAM,4CAAS,CAAC,MAAsB;IAC3C,IAAI,OAAO,KAAK,KAAK;QAAE,QAAQ;IAAK;AACtC;;;ACGO,MAAM,4CACX,CAAC,YAAE,QAAQ,EAA4B,GACvC,OAAO,MAAsB;QAC3B,MAAM,mBAAmB,MAAM,SAAS;QACxC,IAAI,KAAK;IACX;;;;;;;;AGZK,MAAM,4CAA4B;AAElC,MAAM,4CAAgC;AAEtC,MAAM,iDAAoB;AAAO;AAEjC,MAAM,kDAAgC;IAC3C,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AAEO,MAAM,kDAAmC;IAC9C,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AAEO,MAAM,kDAA4B;IACvC,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AAEO,MAAM,kDAA0B;IACrC,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;;;;;;ACbA,uBAAuB;AAEvB,MAAM,+BAAS,OAAO;AAEf,MAAM,4CAA+B,CAC1C,cACA,WACA;IAEA,OAAO,CAAC;QACN,IAAI,IAAI,WAAW,QACjB,qDAAqD;QACrD,MAAM,IAAI,CAAA,GAAA,yCAAyB;QAGrC,MAAM,UAAU,OAAO,IAAI,MAAM,WAAW;QAE5C,yCAAyC;QACzC,IAAI,SAAS,CAAC,QAAQ,IAAI,MACxB,qDAAqD;QACrD,MAAM,IAAI,CAAA,GAAA,yCAAsB;QAGlC,MAAM,WAAW,MAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO;YAAC,IAAI;SAAK;QAEhE,IAAI,OAAO,iBAAiB,YAAY,SAAS,SAAS,cACxD,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,yBAAyB,CAAC;QAE3D,OAAO;qBAAE;sBAAS;QAAS;IAC7B;AACF;AAEO,MAAM,4CAA8B,CAAC;IAC1C,MAAM,aAAa,IAAI,IAAI,kBAAkB,EAAE;IAE/C,OAAO,CAAC,SAAqB;QAC3B,MAAM,SAAS,QAAQ;QACvB,IAAI,OAAO,WAAW,UACpB,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,uBAAuB,CAAC;QAEzD,IAAI,kBAAkB,CAAC,WAAW,IAAI,QAAQ,SAAS;YACrD,QAAQ,kBAAkB,IAAI;gBAAE,QAAQ;YAAqB;YAC7D,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,WAAW,EAAE,OAAO,YAAY,CAAC;QAClE;IACF;AACF;AAEO,MAAM,4CAA2B,CAAC;IACvC,MAAM,wBAAwB,8CAAwB;IAEtD,OAAO,CAAC,SAAqB,WAAE,OAAO,qBAAE,iBAAiB,EAAqB;QAC5E,MAAM,UAAE,MAAM,EAAE,GAAG;QACnB,IACE,MAAM,QAAQ,WACd,MAAM,CAAC,EAAE,IACT,OAAO,MAAM,CAAC,EAAE,KAAK,YACrB,QAAQ,MAAM,CAAC,EAAE,IACjB,OAAO,MAAM,CAAC,EAAE,CAAC,OAAO,UAExB;YAAA,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,IAAI,MAAM,CAAC,EAAE,CAAC,GAAG,gBAAgB;gBACpE,kBAAkB,IAAI;oBAAE,QAAQ;gBAAmC;gBACnE,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,gCAAgC,CAAC;YAClE;QAAA,OACK,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,8BAA8B,CAAC;IACvE;AACF;AAEO,MAAM,4CAA8B,CACzC,gBACA,0BACA,eACA,kBAAkB,KAAM;IAExB,MAAM,oBAAoB,8CAAwB,kBAAkB,CAAC;IAErE,MAAM,kBAAkB,gBAAgB,OAAO,iBAAiB;IAEhE,MAAM,eAAe,IAAI,CAAA,GAAA,YAAI;IAE7B,OAAO,OAAO,SAAqB,WAAE,OAAO,qBAAE,iBAAiB,cAAE,UAAU,EAAqB;QAC9F,IAAI;YACF,MAAM,SAAS,QAAQ,MAAM,CAAC,EAAE;YAChC,MAAM,YAAoB,OAAO;YACjC,MAAM,UAAkB,OAAO,WAAW;YAE1C,IAAI,4BAA4B,gBAAgB;gBAC9C,qBAAqB;gBACrB,MAAM,UAA6B,OAAO;gBAE1C,IAAI,4BAA4B,CAAC,SAC/B,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,yCAAyC,CAAC;gBAE3E,MAAM,YAAY,MAAM,QAAQ,WAAW,UAAU;oBAAC;iBAAQ;gBAE9D,IAAI,4BAA4B,UAAU,WAAW,GACnD,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,yCAAyC,CAAC;gBAE3E,IACE,kBACA,UAAU,KACR,CAAC,eACC,sCAAsC;oBACtC,OAAO,iBAAiB,YAAY,CAAC,iBAAiB,CAAC,QAAQ,EAAE,IAAI,aAAa,iBAEtF;oBACA,kBAAkB,IAAI;wBACpB,QAAQ;oBACV;oBACA,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,mCAAmC,CAAC;gBACrE;YACF;YAEA,yBAAyB;YACzB,IAAI,eAAe;gBACjB,IAAI,cAAc,YAAY,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,2CAA2C,CAAC;gBAEzG,MAAM,8BAA8B,UAAU,WAAW;gBAEzD,IAAI,6BAA6B;oBAC/B,MAAM,sBAAsB,OAAO;oBACnC,IAAI;oBACJ,IAAI,QAAQ,WAAW,OAAO,oBAAoB,OAAO;yBACpD;wBACH,MAAM,SAAS,aAAa,IAAI;wBAChC,IAAI,QACF,oBAAoB;6BACf;4BACL,oBAAoB,MAAM,WAAW,SAAS;gCAC5C,IAAI;gCACJ,SAAS;gCACT,QAAQ;4BACV,GACG,KAAK,CAAC,MAAQ,IAAI,QAClB,KAAK,CAAC,MAAQ,OAAO,IAAI;4BAC5B,aAAa,IAAI,SAAS,mBAAmB;wBAC/C;oBACF;oBACA,MAAM,QAAQ,oBAAoB;oBAElC,IAAI,QAAQ,gCAAU,QAAQ,iBAAiB;wBAC7C,kBAAkB,IAAI;4BACpB,QAAQ;wBACV;wBACA,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,uCAAuC,CAAC;oBACzE;gBACF;YACF;QACF,EAAE,OAAO,OAAO;YACd,IAAI,iBAAiB,CAAA,GAAA,wCAAU,GAAG,MAAM;YACxC,MAAM,IAAI,CAAA,GAAA,yCAAkB,EAAE,CAAC,iCAAiC,CAAC;QACnE;IACF;AACF;AAEO,MAAM,4CAAiC,CAAC,WAAqB,CAAC;QACnE,IAAI,eAAe;QACnB,OAAO,IAAI,CAAA,GAAA,gBAAQ,EAAE;YACnB,WAAU,KAAK,EAAE,SAAS,EAAE,QAAQ;gBAClC,gBAAgB,MAAM;gBACtB,IAAI,eAAe,UAAU;oBAC3B,yCAAyC;oBACzC,QAAQ,kBAAkB,IAAI;wBAAE,QAAQ;oBAAqB;oBAC7D,OAAO,SAAS,IAAI,CAAA,GAAA,yCAAgB,EAAE,CAAC,yCAAyC,EAAE,SAAS,MAAM,CAAC;gBACpG;gBACA,OAAO,SAAS,MAAM,OAAO,yBAAyB;;YACxD;YACA,OAAM,QAAQ;gBACZ;YACF;QACF;IACF;AAIO,MAAM,4CAA0B,CAAC,QAAgB,oBACtD,CAAC,qBAAqB,kBAAkB,SAAS;AAEnD,MAAM,gDAA0B,CAAC;IAC/B,OAAO,OAAO,QAAQ,gBAAgB,OAAO,CAAC,KAAK,CAAC,SAAS,UAAU;QACrE,GAAG,CAAC,QAAQ,GAAG,IAAI,IAAI,UAAU,IAAI,CAAC,IAAM,EAAE;QAC9C,OAAO;IACT,GAAG,CAAC;AACN;;;AF/KO,MAAM,4CAAa,CAAC,WACzB,OAAO,aACP,SAAS,YACT,QAAQ,gBACR,YAAY,cACZ,UAAU,gBACV,eAAe,SACE;IACjB,MAAM,qBACJ,iBAAiB,wBACjB,oBAAoB,wBACpB,oBAAoB,mBACpB,kBAAkB,wBAClB,gBAAgB,uBAChB,oBAAoB,wBACpB,kBAAkB,iCAClB,2BAA2B,MAC5B,GAAG;IAEJ,mBAAmB;IACnB,MAAM,YAAE,QAAQ,UAAE,SAAS,IAAI,GAAG,WAAW,CAAC;IAC9C,MAAM,oBAAoB,IAAI,CAAA,GAAA,cAAM,EAAE;QACpC,MAAM,SAAS;QACf,MAAM;QACN,YAAY;YAAC;SAAS;QACtB,WAAW,EAAE;IACf;IACA,UAAU,eAAe;IAEzB,MAAM,sBAAsB,CAAA,GAAA,yCAA2B,EAAE,cAAc,WAAW;IAElF,MAAM,oBAAoB,CAAA,GAAA,yCAA0B,EAAE;IAEtD,MAAM,kBACJ,wBAAwB,CAAA,GAAA,yCAAsB,EAAE,YAAY,qBACxD,CAAA,GAAA,yCAAuB,EAAE,wBACzB;IAEN,MAAM,qBACJ,AAAC,CAAA,wBAAwB,wBAAuB,KAAM,CAAA,GAAA,yCAAsB,EAAE,eAAe,qBACzF,CAAA,GAAA,yCAA0B,EAAE,sBAAsB,0BAA0B,iBAAiB,qBAC7F;IAEN,MAAM,uBAAuB,CAAA,GAAA,yCAA6B,EAAE;IAE5D,MAAM,aAAa,CAAC,SAAiB,OACnC,CAAA,GAAA,kBAAU,EACR,SAAS,CAAC,QAAQ,EAClB,oFAAoF;QACpF,CAAC,MAAQ,SAAS,KAAK;sBAAE;YAAK,GAAG;yBAAE;YAAQ,IAC3C,CAAC,OAAO,aAAE,SAAS,WAAE,OAAO,WAAE,OAAO,EAAE;YACrC,MAAM,iBAAiB,CAAA,GAAA,uBAAe,EAAE;YACxC,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO;YAChE,IAAI,SAAS;gBACX,MAAM,eAAe,CAAA,GAAA,uBAAe,EAAE;gBACtC,aAAa,KACX,CAAC,0BAA0B,EAAE,eAAe,mBAAmB,EAAE,QAAQ,EAAE,EAAE,QAAQ,yBAAyB,EAAE,aAAa,CAAC,EAC9H;6BAAE;oCAAS;kCAAgB;6BAAc;gBAAQ;YAErD,OACE,aAAa,MACX,CAAC,yCAAyC,EAAE,QAAQ,cAAc,EAAE,QAAQ,WAAW,EAAE,QAAQ,CAAC,CAAC,EACnG;yBAAE;gCAAS;yBAAgB;YAAQ;QAGzC;IAGJ,OAAO,OAAO,KAAqB;QACjC,MAAM,kBAAkB,IAAI;QAC5B,IAAI;YACF,MAAM,WAAE,OAAO,YAAE,QAAQ,EAAE,GAAG,oBAAoB;YAElD,MAAM,oBAAoB;yBACxB;mCACA;4BACA;YACF;YAEA,+DAA+D;YAC/D,wEAAwE;YACxE,KAAK,MAAM,WAAW,SAAU;gBAC9B,kBAAkB,SAAS;gBAE3B,MAAM,SAAS,QAAQ;gBAEvB,IAAI,WAAW,cAAc,iBAC3B,gBAAgB,SAAS;gBAE3B,IAAI,WAAW,iBAAiB,oBAC9B,MAAM,mBAAmB,SAAS;YAEtC;YAEA,MAAM,aAAa,MAAM,WAAW,SAAS,IAAI;YAEjD,IAAI,UAAU,gBAAgB,WAAW,QAAQ,IAAI,mBAAmB;YACxE,IAAI,CAAC,WAAW,MACd,MAAM,IAAI,MAAM;YAGlB,iFAAiF;YACjF,MAAM,CAAA,GAAA,eAAO,EACX,CAAA,GAAA,eAAO,EAAE,QAAQ,WAAW,OAC5B,qBAAqB,oBACrB,KACA;gBACE,QAAQ,gBAAgB;YAC1B;QAEJ,EAAE,OAAO,OAAO;YACd,IAAI,iBAAiB,CAAA,GAAA,wCAAU,GAC7B,IAAI,OAAO,KAAK,KAAK,MAAM,WAAW,CAAA,GAAA,yCAAwB;iBACzD,IAAI,iBAAiB,OAAO;gBACjC,aAAa,MAAM,iBAAkB,CAAA,MAAM,WAAW,CAAA,GAAA,yCAAwB,CAAA;gBAC9E,IAAI,CAAC,IAAI,aACP,IAAI,OAAO,KAAK,KAAK,MAAM,WAAW,CAAA,GAAA,yCAAwB;YAElE,OACE,IAAI,OAAO,KAAK,KAAK,CAAA,GAAA,yCAA4B;QAErD,SAAU;YACR,0DAA0D;YAC1D,gBAAgB;QAClB;IACF;AACF;;;","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'\nimport { getProviderLabel } from '@lidofinance/api-metrics'\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 = ({\n metrics,\n providers,\n fetchRPC,\n defaultChain,\n validation,\n serverLogger = console,\n}: 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 (error, { failedUrl, nextUrl, attempt }) => {\n const failedProvider = getProviderLabel(failedUrl)\n const message = error instanceof Error ? error.message : String(error)\n if (nextUrl) {\n const nextProvider = getProviderLabel(nextUrl)\n serverLogger.warn(\n `[rpcFactory] RPC provider ${failedProvider} failed on attempt ${attempt} (${message}), switching to fallback ${nextProvider}`,\n { chainId, failedProvider, nextProvider, attempt },\n )\n } else {\n serverLogger.error(\n `[rpcFactory] all RPC providers for chain ${chainId} failed after ${attempt} attempts (${message})`,\n { chainId, failedProvider, attempt },\n )\n }\n },\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 serverLogger.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":"../../../../../"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { RPCFactoryParams } from './types';
|
|
2
2
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
3
|
-
export declare const rpcFactory: ({ metrics, providers, fetchRPC, defaultChain, validation }: RPCFactoryParams) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
3
|
+
export declare const rpcFactory: ({ metrics, providers, fetchRPC, defaultChain, validation, serverLogger, }: RPCFactoryParams) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
4
4
|
//# sourceMappingURL=rpc-factory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-factory.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/rpc-factory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rpc-factory.d.ts","sourceRoot":"","sources":["../../../src/api/rpcFactory/rpc-factory.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAG3D,eAAO,MAAM,UAAU,8EAOpB,gBAAgB,WA6DE,cAAc,OAAO,eAAe,KAAG,QAAQ,IAAI,CA0DvE,CAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TrackedFetchRPC } from '@lidofinance/api-rpc';
|
|
2
|
+
import type { ServerLogger } from '@lidofinance/api-logger';
|
|
2
3
|
import type { FetchRpcInitBody } from '@lidofinance/rpc';
|
|
3
4
|
import type { Counter, Registry } from 'prom-client';
|
|
4
5
|
export type RpcProviders = Record<string | number, [string, ...string[]]>;
|
|
@@ -21,6 +22,7 @@ export type RPCFactoryParams = {
|
|
|
21
22
|
fetchRPC: TrackedFetchRPC;
|
|
22
23
|
defaultChain: string | number;
|
|
23
24
|
validation: RPCFactoryValidationParams;
|
|
25
|
+
serverLogger?: ServerLogger;
|
|
24
26
|
};
|
|
25
27
|
export type RpcRequest = {
|
|
26
28
|
method: string;
|
|
@@ -1 +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;
|
|
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,YAAY,EAAE,MAAM,yBAAyB,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;IACtC,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,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"}
|
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.60.0",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|
|
@@ -58,20 +58,22 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@lidofinance/api-logger": "~0.
|
|
62
|
-
"@lidofinance/api-
|
|
63
|
-
"@lidofinance/rpc": "~0.
|
|
64
|
-
"@lidofinance/
|
|
61
|
+
"@lidofinance/api-logger": "~0.60.0",
|
|
62
|
+
"@lidofinance/api-metrics": "~0.60.0",
|
|
63
|
+
"@lidofinance/api-rpc": "~0.60.0",
|
|
64
|
+
"@lidofinance/rpc": "~0.60.0",
|
|
65
|
+
"@lidofinance/ui-pages": "~0.60.0",
|
|
65
66
|
"next": "12 || 13",
|
|
66
67
|
"prom-client": "^14.1.0",
|
|
67
68
|
"react": "17 || 18"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
|
-
"@lidofinance/api-logger": "~0.
|
|
71
|
-
"@lidofinance/api-
|
|
72
|
-
"@lidofinance/
|
|
73
|
-
"@lidofinance/
|
|
74
|
-
"@lidofinance/
|
|
71
|
+
"@lidofinance/api-logger": "~0.60.0",
|
|
72
|
+
"@lidofinance/api-metrics": "~0.60.0",
|
|
73
|
+
"@lidofinance/api-rpc": "~0.60.0",
|
|
74
|
+
"@lidofinance/config-prettier": "~0.60.0",
|
|
75
|
+
"@lidofinance/rpc": "~0.60.0",
|
|
76
|
+
"@lidofinance/ui-pages": "~0.60.0",
|
|
75
77
|
"@types/memory-cache": "^0.2.6",
|
|
76
78
|
"@types/react": "^18.0.25",
|
|
77
79
|
"next": "^12.3.1",
|