From e84195f822139348556f691f5fc5ca2fd8696b9f Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Fri, 13 Dec 2024 20:46:51 +0700 Subject: [PATCH] fix: use package rpcFactory --- package.json | 20 +-- pages/api/rpc.ts | 2 +- utilsApi/rpcFactory/errors.ts | 30 ---- utilsApi/rpcFactory/index.ts | 6 - utilsApi/rpcFactory/rpc-factory.ts | 155 -------------------- utilsApi/rpcFactory/types.ts | 38 ----- utilsApi/rpcFactory/validation.ts | 223 ----------------------------- yarn.lock | 84 +++++------ 8 files changed, 54 insertions(+), 504 deletions(-) delete mode 100644 utilsApi/rpcFactory/errors.ts delete mode 100644 utilsApi/rpcFactory/index.ts delete mode 100644 utilsApi/rpcFactory/rpc-factory.ts delete mode 100644 utilsApi/rpcFactory/types.ts delete mode 100644 utilsApi/rpcFactory/validation.ts diff --git a/package.json b/package.json index 6e0976da2..9827e01c2 100644 --- a/package.json +++ b/package.json @@ -20,18 +20,18 @@ "test:e2e": "playwright test" }, "dependencies": { - "@lidofinance/analytics-matomo": "^0.45.1", - "@lidofinance/api-metrics": "^0.45.1", - "@lidofinance/api-rpc": "^0.45.1", - "@lidofinance/eth-api-providers": "^0.45.1", - "@lidofinance/eth-providers": "^0.45.1", + "@lidofinance/analytics-matomo": "^0.47.0", + "@lidofinance/api-metrics": "^0.47.0", + "@lidofinance/api-rpc": "^0.47.0", + "@lidofinance/eth-api-providers": "^0.47.0", + "@lidofinance/eth-providers": "^0.47.0", "@lidofinance/lido-ethereum-sdk": "4.1.0", "@lidofinance/lido-ui": "^3.26.0", - "@lidofinance/next-api-wrapper": "^0.45.1", - "@lidofinance/next-ip-rate-limit": "^0.45.1", - "@lidofinance/next-pages": "^0.45.1", - "@lidofinance/rpc": "^0.45.1", - "@lidofinance/satanizer": "^0.45.1", + "@lidofinance/next-api-wrapper": "^0.47.0", + "@lidofinance/next-ip-rate-limit": "^0.47.0", + "@lidofinance/next-pages": "^0.47.0", + "@lidofinance/rpc": "^0.47.0", + "@lidofinance/satanizer": "^0.47.0", "@reef-knot/types": "^4.0.0", "@tanstack/react-query": "^5.51.21", "copy-to-clipboard": "^3.3.1", diff --git a/pages/api/rpc.ts b/pages/api/rpc.ts index 57bffcd24..5aed36e27 100644 --- a/pages/api/rpc.ts +++ b/pages/api/rpc.ts @@ -1,5 +1,6 @@ import { wrapRequest as wrapNextRequest } from '@lidofinance/next-api-wrapper'; import { trackedFetchRpcFactory } from '@lidofinance/api-rpc'; +import { rpcFactory } from '@lidofinance/next-pages'; import { config, secretConfig } from 'config'; import { API_ROUTES } from 'consts/api'; @@ -14,7 +15,6 @@ import { HttpMethod, } from 'utilsApi'; import Metrics from 'utilsApi/metrics'; -import { rpcFactory } from 'utilsApi/rpcFactory'; import { METRIC_CONTRACT_ADDRESSES, METRIC_CONTRACT_EVENT_ADDRESSES, diff --git a/utilsApi/rpcFactory/errors.ts b/utilsApi/rpcFactory/errors.ts deleted file mode 100644 index 646790891..000000000 --- a/utilsApi/rpcFactory/errors.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const DEFAULT_API_ERROR_MESSAGE = - 'Something went wrong. Sorry, try again later :('; - -export const HEALTHY_RPC_SERVICES_ARE_OVER = 'Healthy RPC services are over!'; - -export class ClientError extends Error {} - -export class UnsupportedChainIdError extends ClientError { - constructor(message?: string) { - super(message || 'Unsupported chainId'); - } -} - -export class UnsupportedHTTPMethodError extends ClientError { - constructor(message?: string) { - super(message || 'Unsupported HTTP method'); - } -} - -export class InvalidRequestError extends ClientError { - constructor(message?: string) { - super(message || 'Invalid Request'); - } -} - -export class SizeTooLargeError extends ClientError { - constructor(message?: string) { - super(message || 'Invalid Request'); - } -} diff --git a/utilsApi/rpcFactory/index.ts b/utilsApi/rpcFactory/index.ts deleted file mode 100644 index a15e981c7..000000000 --- a/utilsApi/rpcFactory/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { rpcFactory } from './rpc-factory'; -export type { - RPCFactoryParams, - RPCFactoryValidationParams, - RpcProviders, -} from './types'; diff --git a/utilsApi/rpcFactory/rpc-factory.ts b/utilsApi/rpcFactory/rpc-factory.ts deleted file mode 100644 index b8e15ede1..000000000 --- a/utilsApi/rpcFactory/rpc-factory.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Readable } from 'node:stream'; -import { ReadableStream } from 'node:stream/web'; -import { pipeline } from 'node:stream/promises'; -import type { NextApiRequest, NextApiResponse } from 'next'; -import { Counter } from 'prom-client'; -import type { FetchRpcInitBody } from '@lidofinance/rpc'; -import { iterateUrls } from '@lidofinance/rpc'; - -import { - DEFAULT_API_ERROR_MESSAGE, - HEALTHY_RPC_SERVICES_ARE_OVER, - ClientError, -} from './errors'; -import { - baseRequestValidationFactory, - ethCallValidationFactory, - ethGetLogsValidationFactory, - rpcMethodsValidationFactory, - shouldValidateRpcMethod, - streamMaxSizeValidationFactory, -} from './validation'; -import { RPCFactoryParams } from './types'; - -export const rpcFactory = ({ - metrics, - providers, - fetchRPC, - defaultChain, - validation, -}: RPCFactoryParams) => { - const { - allowedRPCMethods, - allowedCallAddresses, - allowedLogsAddresses, - maxResponseSize = 1_000_000, - maxBatchCount = 20, - currentBlockTTLms = 60_000, - maxGetLogsRange = 20_000, - blockEmptyAddressGetLogs = true, - } = validation; - - // optional metrics - const { registry, prefix = '' } = metrics ?? {}; - const rpcRequestBlocked = new Counter({ - name: prefix + 'rpc_service_request_blocked', - help: 'RPC service request blocked', - labelNames: ['reason'], - registers: [], - }); - registry?.registerMetric(rpcRequestBlocked); - - const validateBaseRequest = baseRequestValidationFactory( - defaultChain, - providers, - maxBatchCount, - ); - - const validateRpcMethod = rpcMethodsValidationFactory(allowedRPCMethods); - - const validateEthCall = - allowedCallAddresses && - shouldValidateRpcMethod('eth_call', allowedRPCMethods) - ? ethCallValidationFactory(allowedCallAddresses) - : undefined; - - const validateEthGetLogs = - (allowedLogsAddresses || blockEmptyAddressGetLogs) && - shouldValidateRpcMethod('eth_getLogs', allowedRPCMethods) - ? ethGetLogsValidationFactory( - allowedLogsAddresses, - blockEmptyAddressGetLogs, - maxGetLogsRange, - currentBlockTTLms, - ) - : undefined; - - const validateMaxSteamSize = streamMaxSizeValidationFactory(maxResponseSize); - - const requestRPC = (chainId: number, body: FetchRpcInitBody) => - iterateUrls( - providers[chainId], - // TODO: consider adding verification that body is actually matches FetchRpcInitBody - (url) => fetchRPC(url, { body }, { chainId }), - // eslint-disable-next-line @typescript-eslint/unbound-method - console.error, - ); - - return async (req: NextApiRequest, res: NextApiResponse): Promise => { - const abortController = new AbortController(); - try { - const { chainId, requests } = validateBaseRequest(req); - - const validationContext = { - chainId, - rpcRequestBlocked, - requestRPC, - }; - - // We throw HTTP error for ANY invalid RPC request out of batch - // because we assume that frontend must not send invalid requests at all - for (const request of requests) { - validateRpcMethod(request, validationContext); - - const method = request.method; - - if (method === 'eth_call' && validateEthCall) { - validateEthCall(request, validationContext); - } - if (method === 'eth_getLogs' && validateEthGetLogs) { - await validateEthGetLogs(request, validationContext); - } - } - - const proxyedRPC = await requestRPC( - chainId, - req.body as FetchRpcInitBody, - ); - - res.setHeader( - 'Content-Type', - proxyedRPC.headers.get('Content-Type') ?? 'application/json', - ); - if (!proxyedRPC.body) { - throw new Error('There are a problems with RPC provider'); - } - - // auto closes both Readable.fromWeb() and underlying proxyedRPC streams on error - await pipeline( - Readable.fromWeb(proxyedRPC.body as ReadableStream), - validateMaxSteamSize(validationContext), - res, - { - signal: abortController.signal, - }, - ); - } catch (error) { - if (error instanceof ClientError) { - res.status(400).json(error.message ?? DEFAULT_API_ERROR_MESSAGE); - } else if (error instanceof Error) { - // TODO: check if there are errors duplication with iterateUrls - console.error( - '[rpcFactory]' + (error.message ?? DEFAULT_API_ERROR_MESSAGE), - ); - if (!res.headersSent) { - res.status(500).json(error.message ?? DEFAULT_API_ERROR_MESSAGE); - } - } else { - res.status(500).json(HEALTHY_RPC_SERVICES_ARE_OVER); - } - } finally { - // forces pipeline closure in case of external error/abort - abortController.abort(); - } - }; -}; diff --git a/utilsApi/rpcFactory/types.ts b/utilsApi/rpcFactory/types.ts deleted file mode 100644 index da9b34a31..000000000 --- a/utilsApi/rpcFactory/types.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { TrackedFetchRPC } from '@lidofinance/api-rpc'; -import type { FetchRpcInitBody } from '@lidofinance/rpc'; -import type { Counter, Registry } from 'prom-client'; - -export type RpcProviders = Record; - -export type RPCFactoryValidationParams = { - maxBatchCount?: number; - maxResponseSize?: number; - allowedRPCMethods?: string[]; - allowedCallAddresses?: Record; - allowedLogsAddresses?: Record; - blockEmptyAddressGetLogs?: boolean; - maxGetLogsRange?: number; - currentBlockTTLms?: number; -}; - -export type RPCFactoryParams = { - metrics?: { - prefix: string; - registry: Registry; - }; - providers: RpcProviders; - fetchRPC: TrackedFetchRPC; - defaultChain: string | number; - validation: RPCFactoryValidationParams; -}; - -export type RpcRequest = { - method: string; - params: unknown[]; -}; - -export type ValidationContext = { - chainId: number; - rpcRequestBlocked: Counter<'reason'>; - requestRPC: (chainId: number, body: FetchRpcInitBody) => Promise; -}; diff --git a/utilsApi/rpcFactory/validation.ts b/utilsApi/rpcFactory/validation.ts deleted file mode 100644 index 27f554a93..000000000 --- a/utilsApi/rpcFactory/validation.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { Transform } from 'node:stream'; -import type { NextApiRequest } from 'next'; -import { Cache } from 'memory-cache'; - -import { - ClientError, - InvalidRequestError, - SizeTooLargeError, - UnsupportedChainIdError, - UnsupportedHTTPMethodError, -} from './errors'; - -import type { RpcRequest, ValidationContext } from './types'; - -// validation factories - -export const baseRequestValidationFactory = ( - defaultChain: number | string, - providers: Record, - maxBatchSize?: number, -) => { - return (req: NextApiRequest) => { - if (req.method !== 'POST') { - // We don't care about tracking blocked requests here - throw new UnsupportedHTTPMethodError(); - } - - const chainId = Number(req.query.chainId || defaultChain); - - // Allow only chainId of specified chains - if (providers[chainId] == null) { - // We don't care about tracking blocked requests here - throw new UnsupportedChainIdError(); - } - - const requests = Array.isArray(req.body) ? req.body : [req.body]; - - if (typeof maxBatchSize === 'number' && requests.length > maxBatchSize) { - throw new InvalidRequestError(`Too many batched requests`); - } - return { chainId, requests }; - }; -}; - -export const rpcMethodsValidationFactory = (allowedMethods?: string[]) => { - const methodsMap = new Set(allowedMethods ?? []); - - return (request: RpcRequest, context: ValidationContext) => { - const method = request.method; - if (typeof method !== 'string') { - throw new InvalidRequestError(`RPC method isn't string`); - } - if (allowedMethods && !methodsMap.has(request.method)) { - context.rpcRequestBlocked.inc({ reason: 'method not allowed' }); - throw new InvalidRequestError(`RPC method ${method} not allowed`); - } - }; -}; - -export const ethCallValidationFactory = ( - allowedAddress: Record, -) => { - const allowedCallAddressMap = createAllowedAddressMap(allowedAddress); - - return ( - request: RpcRequest, - { chainId, rpcRequestBlocked }: ValidationContext, - ) => { - const { params } = request; - if ( - Array.isArray(params) && - params[0] && - typeof params[0] === 'object' && - 'to' in params[0] && - typeof params[0].to === 'string' - ) { - if (!allowedCallAddressMap[chainId]?.has(params[0].to.toLowerCase())) { - rpcRequestBlocked.inc({ reason: 'address not allowed for eth_call' }); - throw new InvalidRequestError(`Address not allowed for eth_call`); - } - } else throw new InvalidRequestError(`RPC method eth_call is invalid`); - }; -}; - -export const ethGetLogsValidationFactory = ( - allowedAddress: Record | undefined, - blockEmptyAddressGetLogs: boolean | undefined, - maxBlockRange: number | undefined, - currentBlockTTL = 60_000, -) => { - const allowedAddressMap = createAllowedAddressMap(allowedAddress ?? {}); - - const maxBlockRangeBigInt = maxBlockRange ? BigInt(maxBlockRange) : 0n; - - const currentBlock = new Cache(); - - return async ( - request: RpcRequest, - { chainId, rpcRequestBlocked, requestRPC }: ValidationContext, - ) => { - try { - const params = request.params[0] as any; - const fromBlock: string = params.fromBlock; - const toBlock: string = params.toBlock ?? 'latest'; - - if (blockEmptyAddressGetLogs || allowedAddress) { - // address validation - const address: string | string[] = params.address; - - if (blockEmptyAddressGetLogs && !address) - throw new InvalidRequestError( - `RPC method eth_getLogs address is invalid`, - ); - - const addresses = Array.isArray(address) ? address : [address]; - - if (blockEmptyAddressGetLogs && addresses.length === 0) { - throw new InvalidRequestError( - `RPC method eth_getLogs address is invalid`, - ); - } - if ( - allowedAddress && - addresses.some( - (eventAddress) => - // needs this check before toLowerCase - typeof eventAddress !== 'string' || - !allowedAddressMap[chainId]?.has(eventAddress.toLowerCase()), - ) - ) { - rpcRequestBlocked.inc({ - reason: 'address not allowed for eth_getLogs', - }); - throw new InvalidRequestError(`Address not allowed for eth_getLogs`); - } - } - - // block range validation - if (maxBlockRange) { - if (fromBlock === 'earliest') - throw new InvalidRequestError( - `RPC method eth_getLogs fromBlock is invalid`, - ); - - const shouldValidateBlockDistance = fromBlock.startsWith('0x'); - - if (shouldValidateBlockDistance) { - const normalizedFromBlock = BigInt(fromBlock); - let normalizedToBlock: bigint; - if (toBlock.startsWith('0x')) normalizedToBlock = BigInt(toBlock); - else { - const cached = currentBlock.get(chainId); - if (cached) { - normalizedToBlock = cached; - } else { - normalizedToBlock = await requestRPC(chainId, { - id: 1, - jsonrpc: '2.0', - method: 'eth_blockNumber', - }) - .then((res) => res.json()) - .then((res) => BigInt(res.result)); - currentBlock.put(chainId, normalizedToBlock, currentBlockTTL); - } - } - const range = normalizedToBlock - normalizedFromBlock; - - if (range < 0n || range > maxBlockRangeBigInt) { - rpcRequestBlocked.inc({ - reason: 'eth_getLogs range is invalid', - }); - throw new InvalidRequestError( - `RPC method eth_getLogs range is invalid`, - ); - } - } - } - } catch (error) { - if (error instanceof ClientError) throw error; - throw new InvalidRequestError(`RPC method eth_getLogs is invalid`); - } - }; -}; - -export const streamMaxSizeValidationFactory = - (MAX_SIZE: number) => (context: ValidationContext) => { - let bytesWritten = 0; - return new Transform({ - transform(chunk, _encoding, callback) { - bytesWritten += chunk.length; - if (bytesWritten > MAX_SIZE) { - // Emit an error if size exceeds MAX_SIZE - context.rpcRequestBlocked.inc({ reason: 'response too large' }); - return callback( - new SizeTooLargeError( - `Stream size exceeds the maximum limit of ${MAX_SIZE} bytes`, - ), - ); - } - return callback(null, chunk); // Pass the chunk through - }, - flush(callback) { - callback(); - }, - }); - }; - -// utils - -export const shouldValidateRpcMethod = ( - method: string, - allowedRPCMethods?: string[], -) => !allowedRPCMethods || allowedRPCMethods.includes(method); - -const createAllowedAddressMap = (allowedAddress: Record) => { - return Object.entries(allowedAddress).reduce( - (acc, [chainId, addresses]) => { - acc[chainId] = new Set(addresses.map((a) => a.toLowerCase())); - return acc; - }, - {} as Record>, - ); -}; diff --git a/yarn.lock b/yarn.lock index 9d74da266..f6494ec91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2280,20 +2280,20 @@ bignumber.js "^9.1.2" rxjs "^7.8.1" -"@lidofinance/analytics-matomo@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/analytics-matomo/-/analytics-matomo-0.45.1.tgz#f1ee0df1f6babb1f806e258831efd4ba568ad188" - integrity sha512-D35q+0XnqhWkKJidmT0iWKtLZ+KDj8z9J3t3wP+D0zi53+bP9dd7sU1ciZBUal1Sp0IjEzfiGVSiWKFQqyB8Pg== +"@lidofinance/analytics-matomo@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/analytics-matomo/-/analytics-matomo-0.47.0.tgz#3c0c608048b1b41eadfff08daac5ea454ddd3616" + integrity sha512-/cn5y0R85EzVDZI1ShUizsToWnpMXs7jMGRu8yRrX4iN3pMLgYJzqawKbZf2rwDyyNfHZpUOs4ZYyTRZUMslIg== -"@lidofinance/api-metrics@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/api-metrics/-/api-metrics-0.45.1.tgz#0c093ebeb5fa3b2b73533ec933e0bbd20bbb37dd" - integrity sha512-iPzE4RzPxeApCF7+rIQcng/zos7gHY4ibTBkTF4Vt32P9aCtt70YinQ2Sf2KY8AnBeWhGJLXjvAPWr9P3OI18A== +"@lidofinance/api-metrics@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/api-metrics/-/api-metrics-0.47.0.tgz#285b941e588ad33cc1032ed7e7080977b7444dec" + integrity sha512-lyxLAji9uCQgAa2N++y5XrC99iEf7oaBC4sHDQEoKjed1Tt30AAEjm8Wuko1MfwPscSCeeBc4u/jKAK4KuAcbA== -"@lidofinance/api-rpc@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/api-rpc/-/api-rpc-0.45.1.tgz#0a2002e233bd1233ed68d3a479a41ed93868a6d8" - integrity sha512-rTuLPs0EiOZa3tap2+zAgJ1/kpygjX+R5LgfkBw5oxZn1Dj9k+XvWuPAWBkHAsNVKonYTucD4F/NP0XhNlYuYQ== +"@lidofinance/api-rpc@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/api-rpc/-/api-rpc-0.47.0.tgz#2367eec1c8363516bde48b632a6fbe32aec33a1e" + integrity sha512-6xx2O89OhWAxaob+SqXEX2xtrumG4MCUrclooCIvAhz64Lp3Wa0Hzt2lgJyFMhJxpDPMj3LmYNbuB15lwEQKwg== "@lidofinance/eslint-config@^0.34.0": version "0.34.0" @@ -2302,15 +2302,15 @@ dependencies: typescript "^4.7" -"@lidofinance/eth-api-providers@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/eth-api-providers/-/eth-api-providers-0.45.1.tgz#090e6a3957b664ce54641453357850cac0022a97" - integrity sha512-/8BbPUmBUhB8hRHZpzFXcK/i2jk7pmzhh2NJWjya+G3nSmXaw8U6PInpArB/ZxRAErs0zufUIOm4w7Qrr9mVPw== +"@lidofinance/eth-api-providers@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/eth-api-providers/-/eth-api-providers-0.47.0.tgz#c5a5ce843de4347277db2f046a07c804b1a5f454" + integrity sha512-thuuF9CGcXQD1FAP1LY3wQHvpNElrGEvQPPmeF1kfE7KuaQKv6DpNZxdP+QnLnxxLtXRn8RD92y8wSbWAzy/EA== -"@lidofinance/eth-providers@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/eth-providers/-/eth-providers-0.45.1.tgz#54cbd893c92c06f7ad605ebe0fd8059fc326f180" - integrity sha512-ugkRCI0BPFjWY2h/cDcM8IiibQ8i/wycUgVk2C+E7gCZkjL1YPxGZdnQkfFgIHXAtPhw2YeKHP+VC6HiZpVrZw== +"@lidofinance/eth-providers@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/eth-providers/-/eth-providers-0.47.0.tgz#186d6e2695c9470cc171d111dac352da5106e681" + integrity sha512-dF8eUUnK35CNCVa3Z++INtlxlRlYKGx2//eLz7nyFkqwPoOGI0PmVW0qgGiGY9TrGAqkJuXlERszclH8eunYCw== "@lidofinance/lido-ethereum-sdk@4.1.0": version "4.1.0" @@ -2349,34 +2349,36 @@ ua-parser-js "^1.0.35" use-callback-ref "1.2.5" -"@lidofinance/next-api-wrapper@^0.45.1", "@lidofinance/next-api-wrapper@~0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/next-api-wrapper/-/next-api-wrapper-0.45.1.tgz#59fe3c654c9276eaef8674302470fcc7bb9d8211" - integrity sha512-SUAc520aooNwNJZ0Q1391Yz8iONPiVMQrRC0wWXWDETMFy0ZhCsQ5VCuRKf6rmZwxYjK7K600GV8ke06IIetaw== +"@lidofinance/next-api-wrapper@^0.47.0", "@lidofinance/next-api-wrapper@~0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/next-api-wrapper/-/next-api-wrapper-0.47.0.tgz#8452659d8f5a75fb1e6a91d814397797a2113a69" + integrity sha512-td1ckCuqguXQ4imQ6smnekh72gT1C9ku9Cl9P4Mo0Cd70NYTv0QIGoY1swARHOrAdTGhxyE0H5l7vMOdcGSgFQ== -"@lidofinance/next-ip-rate-limit@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/next-ip-rate-limit/-/next-ip-rate-limit-0.45.1.tgz#79ab181e9f125ad79c46a642dbeec5d8a194397c" - integrity sha512-8d0l6zi4IqX08Op5UCnGM5Z76tkX6GjzzfyjD6Mt7cNt/eNS8AGGKK9wbhUGmumu97l5L0hTZgEW3rNxA8+ptg== +"@lidofinance/next-ip-rate-limit@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/next-ip-rate-limit/-/next-ip-rate-limit-0.47.0.tgz#73e2eef3b91e32b2356dbc2c0b27269a55132f46" + integrity sha512-BFmz50orMcQYItPOaNBEaz/Cx8TYljvhfdIfrdShjyX8HO+ohfesMJlSdEHmHLiC/J7gEoDdiZXRl63HO5+WDg== dependencies: - "@lidofinance/next-api-wrapper" "~0.45.1" + "@lidofinance/next-api-wrapper" "~0.47.0" -"@lidofinance/next-pages@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/next-pages/-/next-pages-0.45.1.tgz#b4c8fe555ba8f04b9cb2fe88a258632fe3e8552a" - integrity sha512-eaqZ2LUWsQg+lOhdzdL+WGQ3h5p7R+HYJ78q3UhnhWMrwZkMDNgxX6dWw7TudcD1yVC9yeZeT9tpfe7Hbd/XEQ== +"@lidofinance/next-pages@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/next-pages/-/next-pages-0.47.0.tgz#3f4551209a4aec0e43dfee36ec20b18bdc473576" + integrity sha512-UewzyshAQqxHx7I/xTnZIM8Gv7zoa0VfkRmDUDLwY8Xk3A1bHqaf5uRqF+yJu3aQZVvFrNSVe7DTTsavlzINzQ== + dependencies: + memory-cache "^0.2.0" -"@lidofinance/rpc@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/rpc/-/rpc-0.45.1.tgz#53e2717ac434c4467ba0135d5144b64cb7af5548" - integrity sha512-F3ZgvBqM9sm8sQEiqGRO05fwBV/ZaexT4RmeSp3I6NcBtpaNuTf8z8IMDcslDhgk7HZZHt/VIAY+8TgVqV8jXw== +"@lidofinance/rpc@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/rpc/-/rpc-0.47.0.tgz#8721e2ac492ab8a5590d17d988546bd89bca7537" + integrity sha512-niepeZ5nTr3QQwZMW0j6c0i8z8KZSmjW1GKDIXcC0ro51rMgGu3gQ7iuXiQ2HnTYEb/fCHTH/Migz4ldPAhtjg== dependencies: isomorphic-fetch "^3.0.0" -"@lidofinance/satanizer@^0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@lidofinance/satanizer/-/satanizer-0.45.1.tgz#22fe644d7657b333074e9806e2b4229d210ba3a7" - integrity sha512-RC9jKEKykFhHFHUJNziFzvTP4LEIr1idRHgK96myKOn2w5GsiZkTd6SiaI7H9/X+Rg9ZbnTb0zcLZdYQPG946A== +"@lidofinance/satanizer@^0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@lidofinance/satanizer/-/satanizer-0.47.0.tgz#e6d6af704a7a111bca1bdea70e89f1089b646bff" + integrity sha512-oB62ld2W0XKxC3YFvRd2ZzAh5MY28ZLs01Jo9Cp8HOFvWvxnF5GrThm8Op3D/urq+cCmZODiUiMI9BMZuymDog== "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": version "1.1.2"