Repository: unlock-protocol/unlock
Pull request #15732: feature(provider): Name resolution caching
Author: login: 0xTxbi
Labels: name: cla-signed
Created: 2025-03-19T18:57:48Z | Merged: 2025-03-20T18:49:34Z

# Description
This PR introduces caching for ens and basenames resolution requests in the provider.


# Issues
Fixes #
Refs #

# Checklist:
- [x] 1 PR, 1 purpose: my Pull Request applies to a single purpose
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have updated the docs to reflect my changes if applicable
- [ ] I have added tests (and stories for frontend components) that prove my fix is effective or that my feature works
- [x] I have performed a self-review of my own code
- [ ] If my code involves visual changes, I am adding applicable screenshots to this thread


Diff:
diff --git a/provider/src/batchProcessor.ts b/provider/src/batchProcessor.ts
index cfd238f6d08..458d7b1352e 100644
--- a/provider/src/batchProcessor.ts
+++ b/provider/src/batchProcessor.ts
@@ -5,18 +5,23 @@ import {
 } from './errorHandlers'
 import { processBatchRequests } from './requestProcessor'
 import { forwardRequestsToProvider } from './providerClient'
+import { storeResponseInCache, shouldStore } from './cache'
 
 /**
  * Combine the locally processed responses with the responses from the provider
  *
  * @param processedRequests The array of processed requests
  * @param providerResponses The responses from the provider (if any)
+ * @param chainId The chain ID
+ * @param env The environment variables
  * @returns An array of combined responses in the same order as the original batch
  */
-export const combineResponses = (
+export const combineResponses = async (
   processedRequests: ProcessedRequest[],
-  providerResponses: any[] | null
-): any[] => {
+  providerResponses: any[] | null,
+  chainId: string,
+  env: any
+): Promise<any[]> => {
   // a map of provider responses by request ID for quick lookup
   const responseMap = new Map<number | string, any>()
 
@@ -28,31 +33,50 @@ export const combineResponses = (
     })
   }
 
-  // Combine the responses in the same order as the original batch
-  return processedRequests.map((processed) => {
-    // If the request was processed locally, use that response
-    if (!processed.shouldForward) {
-      return processed.response
-    }
-
-    // Otherwise, look up the response from the provider
-    const providerResponse = responseMap.get(processed.request.id)
+  // Process all responses in parallel
+  return Promise.all(
+    processedRequests.map(async (processed) => {
+      // If the request was processed locally, use that response
+      if (!processed.shouldForward) {
+        return processed.response
+      }
 
-    // If we couldn't find a matching response, return an error
-    if (!providerResponse) {
-      return {
-        id: processed.request.id,
-        jsonrpc: '2.0',
-        error: {
-          code: -32603,
-          message: 'Internal JSON-RPC error',
-          data: 'No response received from provider for this request',
-        },
+      // Otherwise, look up the response from the provider
+      const providerResponse = responseMap.get(processed.request.id)
+
+      // If provider response is found and the request qualifies for caching, store it
+      if (providerResponse && shouldStore(processed.request, chainId)) {
+        try {
+          await storeResponseInCache(
+            processed.request,
+            chainId,
+            providerResponse,
+            env
+          )
+        } catch (error: any) {
+          console.error('Error caching response:', error)
+        }
       }
-    }
 
-    return providerResponse
-  })
+      // If we couldn't find a matching response, return an error
+      if (!providerResponse) {
+        console.error(
+          `Missing provider response for request ID: ${processed.request.id}, method: ${processed.request.method}`
+        )
+        return {
+          id: processed.request.id,
+          jsonrpc: '2.0',
+          error: {
+            code: -32603,
+            message: 'Internal JSON-RPC error',
+            data: 'No response received from provider for this request',
+          },
+        }
+      } else {
+        return providerResponse
+      }
+    })
+  )
 }
 
 /**
@@ -60,14 +84,14 @@ export const combineResponses = (
  * This function handles the complete processing flow, including forwarding requests and combining responses
  *
  * @param body The original request body (single request or batch)
- * @param networkId The network ID
+ * @param chainId The chain ID
  * @param originalRequest The original HTTP request
  * @param env The environment variables
  * @returns A ProcessingResult containing the responses and any error information
  */
 export const processAndForwardRequests = async (
   body: RpcRequest | RpcRequest[],
-  networkId: string,
+  chainId: string,
   originalRequest: Request,
   env: Env
 ): Promise<ProcessingResult> => {
@@ -79,16 +103,15 @@ export const processAndForwardRequests = async (
     // Process the batch of requests
     const batchResult = await processBatchRequests(
       requests,
-      networkId,
+      chainId,
       originalRequest,
       env
     )
 
     // If all requests can be handled locally, return the combined responses
     if (batchResult.requestsToForward.length === 0) {
-      const responses = batchResult.processedRequests.map((pr) => pr.response)
       return {
-        responses: responses,
+        responses: batchResult.processedRequests.map((pr) => pr.response),
         isBatchRequest,
       }
     }
@@ -98,7 +121,7 @@ export const processAndForwardRequests = async (
       // Forward requests to the provider
       const forwardingResult = await forwardRequestsToProvider(
         batchResult.requestsToForward,
-        networkId,
+        chainId,
         env
       )
 
@@ -116,9 +139,11 @@ export const processAndForwardRequests = async (
 
         // If this was a batch request, combine with local responses
         if (isBatchRequest) {
-          const combinedResponses = combineResponses(
+          const combinedResponses = await combineResponses(
             batchResult.processedRequests,
-            [errorResponse]
+            [errorResponse],
+            chainId,
+            env
           )
 
           return {
@@ -148,9 +173,11 @@ export const processAndForwardRequests = async (
 
       // For batch requests, combine the local and provider responses
       if (isBatchRequest) {
-        const combinedResponses = combineResponses(
+        const combinedResponses = await combineResponses(
           batchResult.processedRequests,
-          providerResponses
+          providerResponses,
+          chainId,
+          env
         )
 
         return {
@@ -176,9 +203,11 @@ export const processAndForwardRequests = async (
 
       // If this was a batch request, combine with local responses
       if (isBatchRequest) {
-        const combinedResponses = combineResponses(
+        const combinedResponses = await combineResponses(
           batchResult.processedRequests,
-          [errorResponse]
+          [errorResponse],
+          chainId,
+          env
         )
 
         return {
diff --git a/provider/src/cache.ts b/provider/src/cache.ts
index 08da37e491c..f3a089c24cd 100644
--- a/provider/src/cache.ts
+++ b/provider/src/cache.ts
@@ -1,107 +1,38 @@
-import { Env } from './types'
+import { ContractType, Env, RpcRequest } from './types'
 import {
-  RpcRequest,
-  getCacheTTL,
-  createCacheKey,
-  isRequestCacheable,
   getKVContractTypeKey,
+  getCacheTTL,
+  isNameResolutionRequest,
 } from './utils'
-import { ContractType } from './types'
 
 /**
- * RPC RESPONSE CACHING
+ * Generate a cache key from a request's method and params
+ * @param request The RPC request
+ * @returns The cache key
  */
+const generateRequestCacheKey = (request: RpcRequest): string => {
+  let paramsStr = ''
 
-/**
- * Checks if a request is cacheable and attempts to retrieve it from cache
- * Returns the cached response if found, null otherwise
- */
-export const getRPCResponseFromCache = async (
-  networkId: string,
-  body: RpcRequest | RpcRequest[],
-  request: Request
-): Promise<Response | null> => {
   try {
-    // Check if this is a cacheable request
-    if (!isRequestCacheable(body)) {
-      return null
-    }
-
-    // Try to get the cached response
-    const cacheKey = createCacheKey(networkId, body)
-    const cache = caches.default
-    const cachedResponse = await cache.match(new Request(cacheKey))
-
-    if (cachedResponse) {
-      // Add CORS headers to the cached response if needed
-      const headers = {
-        'access-control-allow-origin': '*',
-      }
-
-      // Clone the response and add CORS headers
-      return new Response(cachedResponse.body, {
-        status: cachedResponse.status,
-        statusText: cachedResponse.statusText,
-        headers: {
-          ...Object.fromEntries(cachedResponse.headers.entries()),
-          ...headers,
-        },
+    paramsStr = request.params
+      .map((param) => {
+        if (typeof param === 'object' && param !== null) {
+          return JSON.stringify(param)
+        }
+        return String(param)
       })
-    }
-
-    return null
+      .join(',')
   } catch (error) {
-    console.error('Error accessing cache:', error)
-    // On cache error, return null to proceed with the actual request
-    return null
+    console.error(
+      `Error generating cache key: ${error instanceof Error ? error.message : String(error)}`
+    )
+    // Fallback to a simple string to avoid breaking
+    paramsStr = String(request.params)
   }
-}
-
-/**
- * Stores a response in the cache if the request is cacheable
- * @returns true if caching was successful, false otherwise
- */
-export const storeRPCResponseInCache = async (
-  networkId: string,
-  body: RpcRequest | RpcRequest[],
-  json: any,
-  env: Env
-): Promise<boolean> => {
-  try {
-    // Check if this is a cacheable request
-    if (!isRequestCacheable(body)) {
-      return false
-    }
 
-    const cacheTTL = getCacheTTL(env)
-    const cacheKey = createCacheKey(networkId, body)
-    const cache = caches.default
-
-    // CORS headers
-    const headers = {
-      'access-control-allow-origin': '*',
-      'Cache-Control': `public, max-age=${cacheTTL}`,
-    }
-
-    // Create a response for caching
-    const responseToCache = new Response(JSON.stringify(json), {
-      headers,
-    })
-
-    // Store the response in the cache with the specified TTL
-    await cache.put(new Request(cacheKey), responseToCache)
-    return true
-  } catch (error) {
-    console.error('Error caching response:', error)
-    // Continue even if caching fails
-    return false
-  }
+  return [request.method, paramsStr].join(':')
 }
 
-/**
- * Contract CACHING - KV STORAGE
- */
-
 /**
  * Retrieves a contract's status from KV storage
  * Returns the contract status: UNLOCK_PROTOCOL_CONTRACT, OTHER_CONTRACT, NOT_DEPLOYED, or null if not found
@@ -151,3 +82,129 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage.
+ * It retrieves and parses the stored value as JSON.
+ * If reading or parsing fails, it attempts to delete the corrupted cache entry and returns null.
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    console.error(
+      `Error reading from cache: ${error instanceof Error ? error.message : String(error)}`
+    )
+    try {
+      await env.REQUEST_CACHE.delete(cacheKey)
+    } catch (err) {
+      console.error(`Error deleting corrupted cache entry: ${err}`)
+    }
+  }
+
+  return null
+}
+
+/**
+ * Cache a response
+ * @param request The RPC request
+ * @param response The response to cache
+ * @param chainId The chain ID
+ * @param env The environment variables
+ */
+export const storeResponseInKV = async (
+  request: RpcRequest,
+  response: any,
+  chainId: string,
+  env: Env
+): Promise<void> => {
+  if (!env.REQUEST_CACHE || !response?.result) {
+    return
+  }
+  const cacheKey = generateRequestCacheKey(request)
+  const ttl = getCacheTTL(env)
+
+  try {
+    const cacheValue = { result: response.result }
+    await env.REQUEST_CACHE.put(cacheKey, JSON.stringify(cacheValue), {
+      expirationTtl: ttl,
+    })
+  } catch (error) {
+    console.error(
+      `Error writing to cache: ${error instanceof Error ? error.message : String(error)}`
+    )
+  }
+}
+
+/**
+ * Determine if a request should be cached
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @returns True if the request should be cached, false otherwise
+ */
+export const shouldStore = (request: RpcRequest, chainId: string): boolean => {
+  if (!request.method) return false
+  return isNameResolutionRequest(request, chainId)
+}
+
+/**
+ * Get a cached response for a request
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ */
+export const getCachedResponseForRequest = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!shouldStore(request, chainId)) {
+    return null
+  }
+  const cached = await getResponseFromKV(request, chainId, env)
+  if (cached && cached.result !== undefined) {
+    return {
+      id: request.id,
+      jsonrpc: request.jsonrpc || '2.0',
+      result: cached.result,
+    }
+  }
+  return null
+}
+
+/**
+ * Store a response in cache
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param response The response to cache
+ * @param env The environment variables
+ */
+export const storeResponseInCache = async (
+  request: RpcRequest,
+  chainId: string,
+  response: any,
+  env: Env
+): Promise<void> => {
+  if (!shouldStore(request, chainId)) {
+    return
+  }
+  await storeResponseInKV(request, response, chainId, env)
+}
diff --git a/provider/src/lib/constants.ts b/provider/src/lib/constants.ts
new file mode 100644
index 00000000000..601eb17f80b
--- /dev/null
+++ b/provider/src/lib/constants.ts
@@ -0,0 +1,43 @@
+// Default cache TTL in seconds (1 week)
+export const DEFAULT_CACHE_TTL = 60 * 60 * 24 * 7 // 1 week
+
+// Cache API TTL in seconds (1 day)
+export const CACHE_API_TTL = 86400
+
+// contract type prefix for KV namespace
+export const KV_CONTRACT_TYPE_PREFIX = 'contract_type_'
+
+// ENS caching prefix for KV namespace to avoid collisions
+export const KV_ENS_CACHE_PREFIX = 'ens_cache_'
+
+/**
+ * ENS/Basename contract addresses by network
+ */
+export const NAME_RESOLVER_CONTRACTS: Record<string, string[]> = {
+  // Ethereum mainnet ENS contracts
+  '1': [
+    '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', // ENS Registry
+    '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', // Public Resolver
+    '0x231b0Ee14048e9dccD1d247744d114a4eb5E8E63', // Reverse Registrar
+    '0xa2C122BE93b0074270EBee7f6B7292C7dEB45047', // New Public Resolver
+  ].map((addr) => addr.toLowerCase()),
+
+  // Basenames contracts
+  '8453': [
+    '0xC6d566A56A1aFf6508b41f6c90ff131615583BCD',
+    '0x4aE7413182849D062B72518928A4B2DE87f0E411',
+    '0xd4416b13d2B3a9aBae7AcD5D6C2BbDBE25686401',
+  ].map((addr) => addr.toLowerCase()),
+}
+
+/**
+ * Method signatures for ENS/Basename functions
+ */
+export const NAME_RESOLVER_METHOD_SIGNATURES = [
+  '0x0178b8bf', // resolver(bytes32)
+  '0x3b3b57de', // addr(bytes32)
+  '0x691f3431', // name(bytes32) - reverse resolution
+  '0x59d1d43c', // getText(bytes32,string)
+  '0x10f13a8c', // setText(bytes32,string,string)
+  '0x01ffc9a7', // supportsInterface(bytes4)
+]
diff --git a/provider/src/requestProcessor.ts b/provider/src/requestProcessor.ts
index ce14117a174..8eb8a5e0c43 100644
--- a/provider/src/requestProcessor.ts
+++ b/provider/src/requestProcessor.ts
@@ -5,22 +5,23 @@ import {
   BatchProcessingResult,
 } from './types'
 import { shouldRateLimit } from './rateLimit'
+import { getCachedResponseForRequest } from './cache'
 import { getClientIP } from './utils'
 
 /**
  * Process a chainId request locally
  * @param request The RPC request
- * @param networkId The network ID
+ * @param chainId The chain ID
  * @returns The RPC response
  */
 export const processChainIdRequest = (
   request: RpcRequest,
-  networkId: string
+  chainId: string
 ): any => {
   return {
     id: request.id,
     jsonrpc: '2.0',
-    result: `0x${parseInt(networkId).toString(16)}`,
+    result: `0x${parseInt(chainId).toString(16)}`,
   }
 }
 
@@ -36,7 +37,7 @@ export const processChainIdRequest = (
  */
 export const processSingleRequest = async (
   request: RpcRequest,
-  networkId: string,
+  chainId: string,
   originalRequest: Request,
   env: Env
 ): Promise<ProcessedRequest> => {
@@ -44,9 +45,25 @@ export const processSingleRequest = async (
   if (request.method?.toLowerCase().trim() === 'eth_chainid') {
     return {
       request,
-      response: processChainIdRequest(request, networkId),
+      response: processChainIdRequest(request, chainId),
+      shouldForward: false,
+      rateLimited: false,
+    }
+  }
+
+  // Check if this request is cached
+  const cachedResponse = await getCachedResponseForRequest(
+    request,
+    chainId,
+    env
+  )
+  if (cachedResponse) {
+    return {
+      request,
+      response: cachedResponse,
       shouldForward: false,
       rateLimited: false,
+      fromCache: true,
     }
   }
 
@@ -55,18 +72,15 @@ export const processSingleRequest = async (
     originalRequest,
     env,
     request,
-    networkId
+    chainId
   )
 
   if (isRateLimited) {
     // Log the rate limit but still forward the request to maintain current behavior
     // This would later be changed to block rate-limited requests
     console.log(
-      `RATE_LIMIT_WOULD_BLOCK: IP=${getClientIP(originalRequest)}, networkId=${networkId}, Request ID=${request.id}, Method=${request.method}`
+      `RATE_LIMIT_WOULD_BLOCK: IP=${getClientIP(originalRequest)}, networkId=${chainId}, Request ID=${request.id}, Method=${request.method}`
     )
-
-    // TODO: This return should be replaced with a rate limit error response
-    // when we start enforcing rate limiting
     return {
       request,
       response: null,
@@ -90,21 +104,21 @@ export const processSingleRequest = async (
  * can be handled locally and which need to be forwarded to the provider
  *
  * @param requests The array of RPC requests to process
- * @param networkId The network ID
+ * @param chainId The chain ID
  * @param originalRequest The original HTTP request
  * @param env The environment variables
  * @returns A BatchProcessingResult object with the processed requests and requests to forward
  */
 export const processBatchRequests = async (
   requests: RpcRequest[],
-  networkId: string,
+  chainId: string,
   originalRequest: Request,
   env: Env
 ): Promise<BatchProcessingResult> => {
   // process all requests
   const processedRequests: ProcessedRequest[] = await Promise.all(
     requests.map((request) =>
-      processSingleRequest(request, networkId, originalRequest, env)
+      processSingleRequest(request, chainId, originalRequest, env)
     )
   )
 
diff --git a/provider/src/types.ts b/provider/src/types.ts
index c4385d9b35c..bd020442465 100644
--- a/provider/src/types.ts
+++ b/provider/src/types.ts
@@ -27,6 +27,9 @@ export interface Env {
 
   // KV namespace for caching contracts addresses
   ALLOWED_CONTRACTS?: KVNamespace
+
+  // KV namespace for caching requests
+  REQUEST_CACHE?: KVNamespace
 }
 
 /**
@@ -61,6 +64,8 @@ export interface ProcessedRequest {
   response: any | null
   shouldForward: boolean
   rateLimited: boolean
+  shouldCache?: boolean
+  fromCache?: boolean
 }
 
 /**
diff --git a/provider/src/utils.ts b/provider/src/utils.ts
index b5713ea0f3c..3edb29abfe0 100644
--- a/provider/src/utils.ts
+++ b/provider/src/utils.ts
@@ -1,15 +1,12 @@
+import {
+  KV_CONTRACT_TYPE_PREFIX,
+  NAME_RESOLVER_METHOD_SIGNATURES,
+} from './lib/constants'
+import { NAME_RESOLVER_CONTRACTS } from './lib/constants'
+import { DEFAULT_CACHE_TTL } from './lib/constants'
 import { Env, RpcRequest } from './types'
 import { ethers } from 'ethers'
 
-// Default cache TTL in seconds (1 hour)
-export const DEFAULT_CACHE_TTL = 60 * 60
-
-// Cache API TTL in seconds (1 day)
-export const CACHE_API_TTL = 86400
-
-// Key prefix for KV storage to avoid collisions
-export const KV_CONTRACT_TYPE_PREFIX = 'contract_type_'
-
 // Generate the KV key for a given contract type
 export const getKVContractTypeKey = (
   networkId: string,
@@ -41,61 +38,41 @@ export const getCacheTTL = (env: Env): number => {
 }
 
 /**
- * Check if the request is for name resolution (ENS or Base name)
+ * Detect if a request is an ENS/Basename lookup
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @returns Boolean indicating if request is an ENS/Basename lookup
  */
-export const isNameResolutionRequest = (body: RpcRequest): boolean => {
-  if (!body || !body.method || body.method !== 'eth_call') return false
-
-  // ENS and BaseName resolution typically use eth_call with specific contract data
-  // This checks for common ENS and BaseName resolution patterns in the call data
-  const callParams = body.params[0] as { data?: string } | undefined
-  if (!callParams || !callParams.data) return false
-
-  const callData = callParams.data.toLowerCase()
-
-  // ENS resolver methods
-  const ensPatterns = [
-    '0x3b3b57de', // addr(bytes32)
-    '0xf1cb7e06', // addr(bytes32,uint256)
-    '0x691f3431', // name(bytes32)
-    '0x2203ab56', // text(bytes32,string)
-  ]
-
-  // Base Name resolver patterns (L2 resolver methods)
-  const baseNamePatterns = [
-    '0x691f3431', // name(bytes32)
-  ]
+export const isNameResolutionRequest = (
+  request: RpcRequest,
+  chainId: string
+): boolean => {
+  // Only process on networks that support ENS or Basenames
+  if (!NAME_RESOLVER_CONTRACTS[chainId]) {
+    return false
+  }
 
-  const isNameResolution =
-    ensPatterns.some((pattern) => callData.startsWith(pattern)) ||
-    baseNamePatterns.some((pattern) => callData.startsWith(pattern))
+  if (request.method !== 'eth_call') {
+    return false
+  }
 
-  return isNameResolution
-}
+  // Make sure params exist and have the right structure
+  if (!request.params?.[0]?.to || !request.params?.[0]?.data) {
+    return false
+  }
 
-/**
- * Create a cache key for a single RPC request
- */
-export const createCacheKey = (
-  networkId: string,
-  request: RpcRequest
-): string => {
-  /*
-   * For name resolution, we want to cache based on the method and params
-   * Using a standardized fake domain for all cache operations
-   * This is just a convention - not an actual domain - to create a properly formatted
-   * cache key that satisfies the Request object format requirements
-   */
-  return `https://cache.unlock-protocol.com/rpc-cache/${networkId}/${request.method}/${encodeURIComponent(JSON.stringify(request.params))}`
-}
+  const toAddress = request.params[0].to.toLowerCase()
+  const data = request.params[0].data.toLowerCase()
 
-/**
- * Helper to check if a single request is cacheable
- */
-export const isSingleRequestCacheable = (req: RpcRequest): boolean => {
-  if (!req || !req.method || !req.params) return false
+  // Check if the call is to a relevant contract on this network
+  if (!NAME_RESOLVER_CONTRACTS[chainId].includes(toAddress)) {
+    return false
+  }
 
-  return CACHEABLE_METHODS.includes(req.method) && isNameResolutionRequest(req)
+  // Check if the data starts with any of our method signatures
+  return NAME_RESOLVER_METHOD_SIGNATURES.some((signature) =>
+    data.startsWith(signature)
+  )
 }
 
 /**
@@ -133,36 +110,6 @@ export const getClientIP = (request: Request): string => {
   }
 }
 
-/**
- * Group similar methods for rate limiting purposes
- */
-export const getMethodGroup = (method: string): string | null => {
-  // Grouped by similar function and expected volume
-  const readMethods = [
-    'eth_call',
-    'eth_getBalance',
-    'eth_getCode',
-    'eth_getTransactionCount',
-    'eth_getStorageAt',
-    'eth_getBlockByNumber',
-    'eth_getBlockByHash',
-  ]
-
-  const writeMethods = [
-    'eth_sendRawTransaction',
-    'eth_sendTransaction',
-    'eth_estimateGas',
-  ]
-
-  const eventMethods = ['eth_getLogs', 'eth_getFilterLogs', 'eth_newFilter']
-
-  if (readMethods.includes(method)) return 'read'
-  if (writeMethods.includes(method)) return 'write'
-  if (eventMethods.includes(method)) return 'event'
-
-  return null
-}
-
 /**
  * Extract contract address from RPC method params
  * This function supports common RPC methods that interact with contracts
@@ -223,15 +170,6 @@ export const getContractAddress = (
   }
 }
 
-/**
- * Generate Cache API key for a lock
- */
-export const getCacheApiKey = (networkId: string, address: string): string => {
-  // Use a valid URL format as required by Cloudflare's Cache API
-  // Using a standardized fake domain for all cache operations
-  return `https://cache.unlock-protocol.com/lock-check/${networkId}/${address.toLowerCase()}`
-}
-
 /**
  * Create an ethers provider from the RPC URL
  */
diff --git a/provider/tests/batchProcessor.test.ts b/provider/tests/batchProcessor.test.ts
index a515752d846..c66397b4f64 100644
--- a/provider/tests/batchProcessor.test.ts
+++ b/provider/tests/batchProcessor.test.ts
@@ -1,16 +1,18 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { describe, it, expect, vi, beforeEach, test } from 'vitest'
 import {
   processBatchRequests,
   processChainIdRequest,
   processSingleRequest,
 } from '../src/requestProcessor'
 import { combineResponses } from '../src/batchProcessor'
-import { RpcRequest } from '../src/types'
+import { RpcRequest, ProcessedRequest } from '../src/types'
 import * as rateLimit from '../src/rateLimit'
+import * as cacheModule from '../src/cache'
 import { setupGlobalMocks } from './__fixtures__/testUtils'
 
 // Mock dependencies
 vi.mock('../src/rateLimit')
+vi.mock('../src/cache')
 
 describe('Batch Processor', () => {
   const mockEnv = {
@@ -123,6 +125,41 @@ describe('Batch Processor', () => {
         rateLimited: false,
       })
     })
+
+    it('should return cached response when available', async () => {
+      const request: RpcRequest = {
+        id: 5,
+        jsonrpc: '2.0',
+        method: 'eth_call',
+        params: [{ to: '0x1234' }, 'latest'],
+      }
+      const envWithCache = { ...mockEnv }
+
+      // Mock the getCachedResponseForRequest function directly
+      const mockCachedResponse = {
+        id: 5,
+        jsonrpc: '2.0',
+        result: '0xCached',
+      }
+
+      vi.spyOn(cacheModule, 'getCachedResponseForRequest').mockResolvedValue(
+        mockCachedResponse
+      )
+
+      const result = await processSingleRequest(
+        request,
+        '1',
+        mockRequest,
+        envWithCache
+      )
+      expect(result).toEqual({
+        request,
+        response: mockCachedResponse,
+        shouldForward: false,
+        rateLimited: false,
+        fromCache: true,
+      })
+    })
   })
 
   describe('processBatchRequests', () => {
@@ -187,7 +224,7 @@ describe('Batch Processor', () => {
   })
 
   describe('combineResponses', () => {
-    it('should combine local and provider responses correctly', () => {
+    it('should combine local and provider responses correctly', async () => {
       const processedRequests = [
         {
           request: { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [] },
@@ -205,7 +242,12 @@ describe('Batch Processor', () => {
 
       const providerResponses = [{ id: 2, jsonrpc: '2.0', result: '0xabc' }]
 
-      const result = combineResponses(processedRequests, providerResponses)
+      const result = await combineResponses(
+        processedRequests,
+        providerResponses,
+        '1',
+        mockEnv
+      )
 
       expect(result).toEqual([
         { id: 1, jsonrpc: '2.0', result: '0x1' },
@@ -213,7 +255,7 @@ describe('Batch Processor', () => {
       ])
     })
 
-    it('should handle missing provider responses', () => {
+    it('should handle missing provider responses', async () => {
       const processedRequests = [
         {
           request: { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [] },
@@ -229,11 +271,38 @@ describe('Batch Processor', () => {
         },
       ]
 
-      const result = combineResponses(processedRequests, [])
+      const result = await combineResponses(processedRequests, [], '1', mockEnv)
 
       expect(result[0]).toEqual({ id: 1, jsonrpc: '2.0', result: '0x1' })
       expect(result[1].error).toBeDefined()
       expect(result[1].error.code).toBe(-32603)
     })
+
+    test('should store response in cache when processed request has shouldCache true and provider response exists', async () => {
+      vi.mocked(cacheModule.shouldStore).mockReturnValue(true)
+      const processedRequests: ProcessedRequest[] = [
+        {
+          request: { id: 10, jsonrpc: '2.0', method: 'eth_call', params: [] },
+          response: null,
+          shouldCache: true,
+          shouldForward: true,
+          rateLimited: false,
+        },
+      ]
+      const providerResponses = [
+        { id: 10, jsonrpc: '2.0', result: '0xcacheTest' },
+      ]
+      const storeSpy = vi
+        .spyOn(cacheModule, 'storeResponseInCache')
+        .mockResolvedValue(undefined)
+
+      await combineResponses(processedRequests, providerResponses, '1', mockEnv)
+      expect(storeSpy).toHaveBeenCalledWith(
+        processedRequests[0].request,
+        '1',
+        providerResponses[0],
+        mockEnv
+      )
+    })
   })
 })
diff --git a/provider/tests/cache.test.ts b/provider/tests/cache.test.ts
new file mode 100644
index 00000000000..2bb487c5404
--- /dev/null
+++ b/provider/tests/cache.test.ts
@@ -0,0 +1,93 @@
+import { describe, test, expect, beforeEach, vi } from 'vitest'
+import {
+  getResponseFromKV,
+  storeResponseInKV,
+  getCachedResponseForRequest,
+} from '../src/cache'
+import { RpcRequest, Env } from '../src/types'
+import * as utils from '../src/utils'
+
+describe('Cache Module', () => {
+  const dummyRequest: RpcRequest = {
+    id: 1,
+    jsonrpc: '2.0',
+    method: 'eth_call',
+    params: [{ to: '0x1234' }],
+  }
+  const dummyResponse = { id: 1, jsonrpc: '2.0', result: '0xresult' }
+  const chainId = '1'
+  let env: Partial<Env>
+
+  beforeEach(() => {
+    env = {
+      REQUEST_CACHE: {
+        get: vi.fn(),
+        put: vi.fn(),
+        delete: vi.fn(),
+        list: vi.fn(),
+        getWithMetadata: vi.fn(),
+      },
+    }
+  })
+
+  test('getResponseFromKV returns value if found as json', async () => {
+    ;(env.REQUEST_CACHE!.get as any).mockResolvedValue({ result: '0xcached' })
+    const result = await getResponseFromKV(dummyRequest, chainId, env as Env)
+    expect(result).toEqual({ result: '0xcached' })
+  })
+
+  test('getResponseFromKV returns null and deletes cache entry when JSON read fails even if valid text is available', async () => {
+    const deleteMock = vi.fn().mockResolvedValue(undefined)
+    env.REQUEST_CACHE!.delete = deleteMock
+    ;(env.REQUEST_CACHE!.get as any)
+      .mockRejectedValueOnce(new Error('json error'))
+      .mockResolvedValueOnce('{"result": "0xcachedText"}')
+    const result = await getResponseFromKV(dummyRequest, chainId, env as Env)
+    expect(result).toBeNull()
+    expect(deleteMock).toHaveBeenCalled()
+  })
+
+  test('storeResponseInKV calls put when response is valid', async () => {
+    await storeResponseInKV(dummyRequest, dummyResponse, chainId, env as Env)
+    expect(env.REQUEST_CACHE!.put).toHaveBeenCalled()
+  })
+
+  test('getCachedResponseForRequest returns formatted response when cache exists and request is cacheable', async () => {
+    vi.spyOn(utils, 'isNameResolutionRequest').mockReturnValue(true)
+    ;(env.REQUEST_CACHE!.get as any).mockResolvedValue({ result: '0xcached' })
+
+    const result = await getCachedResponseForRequest(
+      dummyRequest,
+      chainId,
+      env as Env
+    )
+    expect(result).toEqual({
+      id: dummyRequest.id,
+      jsonrpc: dummyRequest.jsonrpc,
+      result: '0xcached',
+    })
+  })
+
+  test('getCachedResponseForRequest returns null if request is not cacheable', async () => {
+    vi.spyOn(utils, 'isNameResolutionRequest').mockReturnValue(false)
+    ;(env.REQUEST_CACHE!.get as any).mockResolvedValue({ result: '0xcached' })
+
+    const result = await getCachedResponseForRequest(
+      dummyRequest,
+      chainId,
+      env as Env
+    )
+    expect(result).toBeNull()
+  })
+
+  test('getResponseFromKV deletes entry and returns null when JSON read returns invalid JSON', async () => {
+    const deleteMock = vi.fn().mockResolvedValue(undefined)
+    env.REQUEST_CACHE!.delete = deleteMock
+    ;(env.REQUEST_CACHE!.get as any).mockImplementationOnce(() => {
+      throw new Error('invalid json')
+    })
+    const result = await getResponseFromKV(dummyRequest, chainId, env as Env)
+    expect(result).toBeNull()
+    expect(deleteMock).toHaveBeenCalled()
+  })
+})
diff --git a/provider/wrangler.toml b/provider/wrangler.toml
index eda7865b0ac..0106cd2a50a 100644
--- a/provider/wrangler.toml
+++ b/provider/wrangler.toml
@@ -5,8 +5,8 @@ tail_consumers = [{service = "rpc-provider-tail"}]
 
 # Optional environment variables
 [vars]
-# Cache duration in seconds for ENS/Basename resolution (default: 3600 seconds / 1 hour)
-CACHE_DURATION_SECONDS = "3600"
+# Cache duration in seconds for ENS/Basename resolution (default: 1 day)
+CACHE_DURATION_SECONDS = "86400" # TODO: extend to 1 month when we have more confidence in the cache
 
 # Rate limiting using Cloudflare's Rate Limiting API
 # The rate limiting API is in open beta
@@ -29,11 +29,19 @@ simple = { limit = 1000, period = 60 }
 [dev]
 port = 8787
 
-# Development-only KV namespace for local testing
+# KV namespace for allowed contracts
 [[kv_namespaces]]
 binding = "ALLOWED_CONTRACTS"
-preview_id = "dev-allowed-contracts"
 id = "9073bea6f14048f09e64e75bcb3fba81"
+preview_id = "dev-allowed-contracts"
+
+
+# KV namespace for request caching
+[[kv_namespaces]]
+binding = "REQUEST_CACHE"
+id = "0d52c25fc4cf4e8eb5cbd944a8bfb5d9"
+preview_id = "dev-request-cache"
+
 
 [observability]
 enabled = true


Reviews:
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:28:31Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:29:13Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:30:03Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:47:06Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:48:35Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:50:33Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:52:13Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:52:51Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:53:36Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:54:09Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:56:35Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T21:57:57Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T22:00:10Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T13:17:17Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T13:19:57Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T13:20:49Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T13:21:04Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T13:21:37Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T13:22:16Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T13:23:28Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T13:24:15Z
user: login: julien51
body: Your changes from `networkId` to `chainId` are making everything murkier :( It's too late for this one, but please refrain from doing that in the future!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-20T13:25:43Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T14:52:43Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T14:53:18Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T14:57:49Z
user: login: julien51
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T14:58:47Z
user: login: julien51
body: ok that strartrs to look good! Please test the hell out of this with real traffic locally?
I will deploy on cloudflare today!
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T14:59:41Z
user: login: 0xTxbi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T15:00:22Z
user: login: 0xTxbi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T15:20:53Z
user: login: julien51
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T17:40:08Z

Review comments:
diff_hunk: @@ -50,6 +51,34 @@ export const processSingleRequest = async (
     }
   }
 
+  // Check if this is an ENS/Basename request that can be cached
+  if (isNameResolutionRequest(request, networkId)) {
+    // Try to get from cache
+    const cachedData = await getENSResponseFromKV(request, networkId, env)
+    if (cachedData) {
+      return {
+        request,
+        response: {
+          id: request.id,
+          jsonrpc: request.jsonrpc || '2.0',
+          result: cachedData.result,
+        },
+        shouldForward: false,
+        rateLimited: false,
+        fromCache: true,
+      }
+    }
+
+    // If not in cache, mark for later caching
+    return {
+      request,
+      response: null,
+      shouldForward: true,
+      rateLimited: false,
+      shouldCacheENS: true,
path: provider/src/requestProcessor.ts
user: login: julien51
body: I don't like that this is specifuc to ENS. 
created_at: 2025-03-19T21:28:31Z
author_association: MEMBER
diff_hunk: @@ -146,6 +146,27 @@ export const processAndForwardRequests = async (
       // Get the provider responses
       const providerResponses = forwardingResult.responses || []
 
+      // Process ENS caching for all relevant responses
+      if (providerResponses.length > 0) {
+        // Find requests that should be cached
+        batchResult.processedRequests
+          .filter((req) => req.shouldCacheENS)
+          .forEach((req) => {
+            // Find the matching response for this request
+            const response = providerResponses.find(
+              (resp) => resp.id === req.request.id
+            )
+            if (response) {
+              // Don't await to avoid blocking the response
+              storeENSResponseInKV(req.request, response, networkId, env).catch(
+                (error: Error) => {
+                  console.error('Error caching ENS response:', error)
+                }
+              )
+            }
+          })
path: provider/src/batchProcessor.ts
user: login: julien51
body: This is not the right level to do that IMO. You are adding a lot of coupling
created_at: 2025-03-19T21:29:13Z
author_association: MEMBER
diff_hunk: @@ -50,6 +51,34 @@ export const processSingleRequest = async (
     }
   }
 
+  // Check if this is an ENS/Basename request that can be cached
+  if (isNameResolutionRequest(request, networkId)) {
+    // Try to get from cache
+    const cachedData = await getENSResponseFromKV(request, networkId, env)
+    if (cachedData) {
+      return {
+        request,
+        response: {
+          id: request.id,
+          jsonrpc: request.jsonrpc || '2.0',
+          result: cachedData.result,
+        },
+        shouldForward: false,
+        rateLimited: false,
+        fromCache: true,
+      }
+    }
+
+    // If not in cache, mark for later caching
+    return {
+      request,
+      response: null,
+      shouldForward: true,
+      rateLimited: false,
+      shouldCacheENS: true,
path: provider/src/requestProcessor.ts
user: login: julien51
body: It should just be `shouldCache` so we can easily add more caching later if needed
created_at: 2025-03-19T21:30:03Z
author_association: MEMBER
diff_hunk: @@ -50,6 +51,34 @@ export const processSingleRequest = async (
     }
   }
 
+  // Check if this is an ENS/Basename request that can be cached
path: provider/src/requestProcessor.ts
user: login: julien51
body: Here you should just check the cache... no matter what kind of request that is.
If there is a cached value, you return it..., and that's it!
created_at: 2025-03-19T21:47:06Z
author_association: MEMBER
diff_hunk: @@ -50,6 +51,34 @@ export const processSingleRequest = async (
     }
   }
 
+  // Check if this is an ENS/Basename request that can be cached
path: provider/src/requestProcessor.ts
user: login: julien51
body: ```
const cached = await getCachedResponseForRequest(req, networkId)
if (cached) {
return {
      request,
      response: cached,
      shouldForward: false,
      rateLimited: false,
}
}
```
created_at: 2025-03-19T21:48:35Z
author_association: MEMBER
diff_hunk: @@ -5,6 +5,7 @@ import {
 } from './errorHandlers'
 import { processBatchRequests } from './requestProcessor'
 import { forwardRequestsToProvider } from './providerClient'
+import { storeENSResponseInKV } from './cache'
path: provider/src/batchProcessor.ts
user: login: julien51
body: On line 41 below, you call 
`await storeResponseInCache(request, chainId, response)`
created_at: 2025-03-19T21:50:33Z
author_association: MEMBER
diff_hunk: @@ -151,3 +50,86 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
path: provider/src/cache.ts
user: login: julien51
body: function storeResponseInCache(request, chainId, response) {
if (shouldStore(request)) {
storeResponseInCache(request, chaindId, response)
}
}
created_at: 2025-03-19T21:52:13Z
author_association: MEMBER
diff_hunk: @@ -151,3 +50,86 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
path: provider/src/cache.ts
user: login: julien51
body: function shouldStore(request) {
// Check if ENS, return true
// Check if Basename, return true

// return false
}
created_at: 2025-03-19T21:52:51Z
author_association: MEMBER
diff_hunk: @@ -151,3 +50,86 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached ENS/Basename response from KV storage
+ * @param request The RPC request
+ * @param networkId The network ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ *
+ * First attempts to retrieve and parse as JSON directly. If that fails,
+ * falls back to retrieving as text and parsing manually. If the text
+ * parsing also fails, deletes the corrupted cache entry.
+ */
+export const getENSResponseFromKV = async (
path: provider/src/cache.ts
user: login: julien51
body: ```suggestion
export const getResponseFromKV = async (
```
created_at: 2025-03-19T21:53:36Z
author_association: MEMBER
diff_hunk: @@ -151,3 +50,86 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached ENS/Basename response from KV storage
+ * @param request The RPC request
+ * @param networkId The network ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ *
+ * First attempts to retrieve and parse as JSON directly. If that fails,
+ * falls back to retrieving as text and parsing manually. If the text
+ * parsing also fails, deletes the corrupted cache entry.
+ */
+export const getENSResponseFromKV = async (
+  request: RpcRequest,
+  networkId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.NAME_RESOLUTION_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateENSCacheKey(request, networkId)
path: provider/src/cache.ts
user: login: julien51
body: ```suggestion
  const cacheKey = [request.method, request.params.join(',' )].join(':')
```
created_at: 2025-03-19T21:54:09Z
author_association: MEMBER
diff_hunk: @@ -151,3 +50,86 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached ENS/Basename response from KV storage
+ * @param request The RPC request
+ * @param networkId The network ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ *
+ * First attempts to retrieve and parse as JSON directly. If that fails,
+ * falls back to retrieving as text and parsing manually. If the text
+ * parsing also fails, deletes the corrupted cache entry.
+ */
+export const getENSResponseFromKV = async (
+  request: RpcRequest,
+  networkId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.NAME_RESOLUTION_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateENSCacheKey(request, networkId)
+
+  try {
+    // Try to get and parse as JSON
+    const cached = await env.NAME_RESOLUTION_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    // If JSON parsing fails, try simple text retrieval and parse it
+    try {
+      const rawText = await env.NAME_RESOLUTION_CACHE.get(cacheKey, 'text')
+      if (rawText) {
+        try {
+          return JSON.parse(rawText)
+        } catch {
+          // If still can't parse, delete the corrupted entry
+          await env.NAME_RESOLUTION_CACHE.delete(cacheKey).catch(() => {})
+        }
+      }
+    } catch (error) {
+      console.error(
+        `Error reading from ENS cache: ${error instanceof Error ? error.message : String(error)}`
+      )
+    }
+  }
+
+  return null
+}
+
+/**
+ * Cache an ENS/Basename response
+ * @param request The RPC request
+ * @param response The response to cache
+ * @param networkId The network ID
+ * @param env The environment variables
+ */
+export const storeENSResponseInKV = async (
path: provider/src/cache.ts
user: login: julien51
body: ```suggestion
export const storeResponseInKV = async (
```
created_at: 2025-03-19T21:56:35Z
author_association: MEMBER
diff_hunk: @@ -5,6 +5,7 @@ import {
 } from './errorHandlers'
 import { processBatchRequests } from './requestProcessor'
 import { forwardRequestsToProvider } from './providerClient'
+import { storeENSResponseInKV } from './cache'
path: provider/src/batchProcessor.ts
user: login: julien51
body: In that funcyiom, make sure you dont store requestId and json2.0
created_at: 2025-03-19T21:57:57Z
author_association: MEMBER
diff_hunk: @@ -1,106 +1,5 @@
-import { Env } from './types'
-import {
-  RpcRequest,
-  getCacheTTL,
-  createCacheKey,
-  isRequestCacheable,
-  getKVContractTypeKey,
-} from './utils'
-import { ContractType } from './types'
-
-/**
- * RPC RESPONSE CACHING
- */
-
-/**
- * Checks if a request is cacheable and attempts to retrieve it from cache
- * Returns the cached response if found, null otherwise
- */
-export const getRPCResponseFromCache = async (
-  networkId: string,
-  body: RpcRequest | RpcRequest[],
-  request: Request
-): Promise<Response | null> => {
-  try {
-    // Check if this is a cacheable request
-    if (!isRequestCacheable(body)) {
-      return null
-    }
-
-    // Try to get the cached response
-    const cacheKey = createCacheKey(networkId, body)
-    const cache = caches.default
-    const cachedResponse = await cache.match(new Request(cacheKey))
-
-    if (cachedResponse) {
-      // Add CORS headers to the cached response if needed
-      const headers = {
-        'access-control-allow-origin': '*',
-      }
-
-      // Clone the response and add CORS headers
-      return new Response(cachedResponse.body, {
-        status: cachedResponse.status,
-        statusText: cachedResponse.statusText,
-        headers: {
-          ...Object.fromEntries(cachedResponse.headers.entries()),
-          ...headers,
-        },
-      })
-    }
-
-    return null
-  } catch (error) {
-    console.error('Error accessing cache:', error)
-    // On cache error, return null to proceed with the actual request
-    return null
-  }
-}
-
-/**
- * Stores a response in the cache if the request is cacheable
- * @returns true if caching was successful, false otherwise
- */
-export const storeRPCResponseInCache = async (
-  networkId: string,
-  body: RpcRequest | RpcRequest[],
-  json: any,
-  env: Env
-): Promise<boolean> => {
-  try {
-    // Check if this is a cacheable request
-    if (!isRequestCacheable(body)) {
-      return false
-    }
-
-    const cacheTTL = getCacheTTL(env)
-    const cacheKey = createCacheKey(networkId, body)
-    const cache = caches.default
-
-    // CORS headers
-    const headers = {
-      'access-control-allow-origin': '*',
-      'Cache-Control': `public, max-age=${cacheTTL}`,
-    }
-
-    // Create a response for caching
-    const responseToCache = new Response(JSON.stringify(json), {
-      headers,
-    })
-
-    // Store the response in the cache with the specified TTL
-    await cache.put(new Request(cacheKey), responseToCache)
-    return true
-  } catch (error) {
-    console.error('Error caching response:', error)
-    // Continue even if caching fails
-    return false
-  }
-}
-
-/**
- * Contract CACHING - KV STORAGE
- */
+import { ContractType, Env, RpcRequest } from './types'
+import { getKVContractTypeKey, generateENSCacheKey, getCacheTTL } from './utils'
path: provider/src/cache.ts
user: login: julien51
body: You are going to add `getCachedResponseForRequest` here:
// first use shouldStore() to determine if we should even try to retrieve from the cache (if false, return)
// Then, use getResponseFromKV()
// If there is a reponse, rescontruct the json objec ( replace requestId and json2.0) 
created_at: 2025-03-19T22:00:10Z
author_association: MEMBER
diff_hunk: @@ -38,6 +43,18 @@ export const combineResponses = (
     // Otherwise, look up the response from the provider
     const providerResponse = responseMap.get(processed.request.id)
 
+    // If provider response is found and the request is marked as cacheable, store it
+    if (providerResponse && processed.shouldCache) {
+      storeResponseInCache(
path: provider/src/batchProcessor.ts
user: login: julien51
body: I see you are not "awaiting" here. It may be a good idea to not slow down the request but I want to make sure this is what you are trying to acheve!
created_at: 2025-03-20T13:17:16Z
author_association: MEMBER
diff_hunk: @@ -1,106 +1,10 @@
-import { Env } from './types'
+import { ContractType, Env, RpcRequest } from './types'
 import {
-  RpcRequest,
-  getCacheTTL,
-  createCacheKey,
-  isRequestCacheable,
   getKVContractTypeKey,
+  getCacheTTL,
+  isNameResolutionRequest,
+  generateRequestCacheKey,
 } from './utils'
path: provider/src/cache.ts
user: login: julien51
body: I am not sure it makes sense to add this to utils since these are specifc to the cache and will not be re-used anywhere else
created_at: 2025-03-20T13:19:57Z
author_association: MEMBER
diff_hunk: @@ -151,3 +55,151 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ *
+ * First attempts to retrieve and parse as JSON directly. If that fails,
+ * falls back to retrieving as text and parsing manually. If the text
+ * parsing also fails, deletes the corrupted cache entry.
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    // Try to get and parse as JSON
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    // If JSON parsing fails, try simple text retrieval and parse it
+    try {
+      const rawText = await env.REQUEST_CACHE.get(cacheKey, 'text')
+      if (rawText) {
path: provider/src/cache.ts
user: login: julien51
body: This is bad. We should decide to either store json OR store text and not mix things. 
created_at: 2025-03-20T13:20:49Z
author_association: MEMBER
diff_hunk: @@ -151,3 +55,151 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ *
+ * First attempts to retrieve and parse as JSON directly. If that fails,
+ * falls back to retrieving as text and parsing manually. If the text
+ * parsing also fails, deletes the corrupted cache entry.
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    // Try to get and parse as JSON
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    // If JSON parsing fails, try simple text retrieval and parse it
+    try {
+      const rawText = await env.REQUEST_CACHE.get(cacheKey, 'text')
+      if (rawText) {
+        try {
+          return JSON.parse(rawText)
+        } catch {
+          // If still can't parse, delete the corrupted entry
+          await env.REQUEST_CACHE.delete(cacheKey).catch((err) => {
+            console.error(`Error deleting corrupted cache entry: ${err}`)
+          })
+        }
+      }
+    } catch (error) {
+      console.error(
+        `Error reading from cache: ${error instanceof Error ? error.message : String(error)}`
+      )
+    }
+  }
+
+  return null
+}
+
+/**
+ * Cache a response
+ * @param request The RPC request
+ * @param response The response to cache
+ * @param chainId The chain ID
+ * @param env The environment variables
+ */
+export const storeResponseInKV = async (
+  request: RpcRequest,
+  response: any,
+  chainId: string,
+  env: Env
+): Promise<void> => {
+  if (!env.REQUEST_CACHE || !response?.result) {
+    return
+  }
+  const cacheKey = generateRequestCacheKey(request)
+  const ttl = getCacheTTL(env)
+
+  try {
+    const cacheValue = { result: response.result }
+    await env.REQUEST_CACHE.put(cacheKey, JSON.stringify(cacheValue), {
+      expirationTtl: ttl,
+    })
path: provider/src/cache.ts
user: login: julien51
body: Can we just store json?
created_at: 2025-03-20T13:21:04Z
author_association: MEMBER
diff_hunk: @@ -151,3 +55,151 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ *
+ * First attempts to retrieve and parse as JSON directly. If that fails,
+ * falls back to retrieving as text and parsing manually. If the text
+ * parsing also fails, deletes the corrupted cache entry.
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    // Try to get and parse as JSON
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    // If JSON parsing fails, try simple text retrieval and parse it
+    try {
+      const rawText = await env.REQUEST_CACHE.get(cacheKey, 'text')
+      if (rawText) {
+        try {
+          return JSON.parse(rawText)
+        } catch {
+          // If still can't parse, delete the corrupted entry
+          await env.REQUEST_CACHE.delete(cacheKey).catch((err) => {
+            console.error(`Error deleting corrupted cache entry: ${err}`)
+          })
+        }
+      }
+    } catch (error) {
+      console.error(
+        `Error reading from cache: ${error instanceof Error ? error.message : String(error)}`
+      )
+    }
+  }
+
+  return null
+}
+
+/**
+ * Cache a response
+ * @param request The RPC request
+ * @param response The response to cache
+ * @param chainId The chain ID
+ * @param env The environment variables
+ */
+export const storeResponseInKV = async (
+  request: RpcRequest,
+  response: any,
+  chainId: string,
+  env: Env
+): Promise<void> => {
+  if (!env.REQUEST_CACHE || !response?.result) {
+    return
+  }
+  const cacheKey = generateRequestCacheKey(request)
+  const ttl = getCacheTTL(env)
+
+  try {
+    const cacheValue = { result: response.result }
+    await env.REQUEST_CACHE.put(cacheKey, JSON.stringify(cacheValue), {
+      expirationTtl: ttl,
+    })
+  } catch (error) {
+    console.error(
+      `Error writing to cache: ${error instanceof Error ? error.message : String(error)}`
+    )
+  }
+}
+
+/**
+ * Determine if a request should be cached
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @returns True if the request should be cached, false otherwise
+ */
+export const shouldStore = (request: RpcRequest, chainId: string): boolean => {
+  if (!request.method) return false
+
+  // check if it's a name resolution request
+  if (
+    request.method.toLowerCase() === 'eth_call' &&
+    isNameResolutionRequest(request, chainId)
path: provider/src/cache.ts
user: login: julien51
body: `request.method.toLowerCase() === 'eth_call'` shou;d be _inside_ `isNameResolutionRequest` for simplicity
created_at: 2025-03-20T13:21:37Z
author_association: MEMBER
diff_hunk: @@ -151,3 +55,151 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ *
+ * First attempts to retrieve and parse as JSON directly. If that fails,
+ * falls back to retrieving as text and parsing manually. If the text
+ * parsing also fails, deletes the corrupted cache entry.
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    // Try to get and parse as JSON
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    // If JSON parsing fails, try simple text retrieval and parse it
+    try {
+      const rawText = await env.REQUEST_CACHE.get(cacheKey, 'text')
+      if (rawText) {
+        try {
+          return JSON.parse(rawText)
+        } catch {
+          // If still can't parse, delete the corrupted entry
+          await env.REQUEST_CACHE.delete(cacheKey).catch((err) => {
+            console.error(`Error deleting corrupted cache entry: ${err}`)
+          })
+        }
+      }
+    } catch (error) {
+      console.error(
+        `Error reading from cache: ${error instanceof Error ? error.message : String(error)}`
+      )
+    }
+  }
+
+  return null
+}
+
+/**
+ * Cache a response
+ * @param request The RPC request
+ * @param response The response to cache
+ * @param chainId The chain ID
+ * @param env The environment variables
+ */
+export const storeResponseInKV = async (
+  request: RpcRequest,
+  response: any,
+  chainId: string,
+  env: Env
+): Promise<void> => {
+  if (!env.REQUEST_CACHE || !response?.result) {
+    return
+  }
+  const cacheKey = generateRequestCacheKey(request)
+  const ttl = getCacheTTL(env)
+
+  try {
+    const cacheValue = { result: response.result }
+    await env.REQUEST_CACHE.put(cacheKey, JSON.stringify(cacheValue), {
+      expirationTtl: ttl,
+    })
+  } catch (error) {
+    console.error(
+      `Error writing to cache: ${error instanceof Error ? error.message : String(error)}`
+    )
+  }
+}
+
+/**
+ * Determine if a request should be cached
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @returns True if the request should be cached, false otherwise
+ */
+export const shouldStore = (request: RpcRequest, chainId: string): boolean => {
+  if (!request.method) return false
+
+  // check if it's a name resolution request
+  if (
+    request.method.toLowerCase() === 'eth_call' &&
+    isNameResolutionRequest(request, chainId)
+  ) {
+    return true
+  }
+
+  return false
+}
+
+/**
+ * Get a cached response for a request
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ */
+export const getCachedResponseForRequest = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!shouldStore(request, chainId)) {
+    return null
+  }
+  const cached = await getResponseFromKV(request, chainId, env)
+  if (cached && cached.result !== undefined) {
+    return {
+      id: request.id,
+      jsonrpc: request.jsonrpc || '2.0',
+      result: cached.result,
+    }
+  }
+  return null
+}
+
+/**
+ * Store a response in cache
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param response The response to cache
+ * @param env The environment variables
+ */
+export const storeResponseInCache = async (
+  request: RpcRequest,
+  chainId: string,
+  response: any,
+  env: Env
+): Promise<void> => {
+  if (!shouldStore(request, chainId) || !response?.result) {
path: provider/src/cache.ts
user: login: julien51
body: Why would `response?.result` be falsy??
created_at: 2025-03-20T13:22:15Z
author_association: MEMBER
diff_hunk: @@ -36,37 +36,47 @@ export const processChainIdRequest = (
  */
 export const processSingleRequest = async (
   request: RpcRequest,
-  networkId: string,
+  chainId: string,
   originalRequest: Request,
   env: Env
 ): Promise<ProcessedRequest> => {
   // Check if this is a chainId request that can be handled locally
   if (request.method?.toLowerCase().trim() === 'eth_chainid') {
     return {
       request,
-      response: processChainIdRequest(request, networkId),
+      response: processChainIdRequest(request, chainId),
       shouldForward: false,
       rateLimited: false,
     }
   }
 
+  // Check if this request is cached
+  const cachedResponse = await getCachedResponseForRequest(
+    request,
+    chainId,
+    env
+  )
+  if (cachedResponse) {
+    return {
+      request,
+      response: cachedResponse,
+      shouldForward: false,
+      rateLimited: false,
+      fromCache: true,
+    }
+  }
+
   // Check if this request is rate limited
   const isRateLimited = await shouldRateLimit(
     originalRequest,
     env,
     request,
-    networkId
+    chainId
   )
 
   if (isRateLimited) {
     // Log the rate limit but still forward the request to maintain current behavior
     // This would later be changed to block rate-limited requests
-    console.log(
-      `RATE_LIMIT_WOULD_BLOCK: IP=${getClientIP(originalRequest)}, networkId=${networkId}, Request ID=${request.id}, Method=${request.method}`
-    )
-
path: provider/src/requestProcessor.ts
user: login: julien51
body: If you remove this, remove the comment above!
created_at: 2025-03-20T13:23:28Z
author_association: MEMBER
diff_hunk: @@ -1,14 +1,44 @@
+import {
+  KV_CONTRACT_TYPE_PREFIX,
+  NAME_RESOLVER_METHOD_SIGNATURES,
+} from './lib/constants'
+import { NAME_RESOLVER_CONTRACTS } from './lib/constants'
+import { DEFAULT_CACHE_TTL } from './lib/constants'
 import { Env, RpcRequest } from './types'
 import { ethers } from 'ethers'
 
-// Default cache TTL in seconds (1 hour)
-export const DEFAULT_CACHE_TTL = 60 * 60
+/**
+ * Generate a cache key from a request's method and params
+ * @param request The RPC request
+ * @returns The cache key
+ */
+export const generateRequestCacheKey = (request: RpcRequest): string => {
+  let paramsStr = ''
 
-// Cache API TTL in seconds (1 day)
-export const CACHE_API_TTL = 86400
+  try {
+    if (Array.isArray(request.params)) {
+      // Handle each parameter individually to ensure proper serialization
+      paramsStr = request.params
+        .map((param) => {
+          if (param === null) return 'null'
path: provider/src/utils.ts
user: login: julien51
body: I don't think `param` can be null or an object? 
created_at: 2025-03-20T13:24:14Z
author_association: MEMBER
diff_hunk: @@ -28,46 +33,62 @@ export const combineResponses = (
     })
   }
 
-  // Combine the responses in the same order as the original batch
-  return processedRequests.map((processed) => {
-    // If the request was processed locally, use that response
-    if (!processed.shouldForward) {
-      return processed.response
-    }
-
-    // Otherwise, look up the response from the provider
-    const providerResponse = responseMap.get(processed.request.id)
+  // Process all responses in parallel
+  return Promise.all(
+    processedRequests.map(async (processed) => {
+      // If the request was processed locally, use that response
+      if (!processed.shouldForward) {
+        return processed.response
+      }
 
-    // If we couldn't find a matching response, return an error
-    if (!providerResponse) {
-      return {
-        id: processed.request.id,
-        jsonrpc: '2.0',
-        error: {
-          code: -32603,
-          message: 'Internal JSON-RPC error',
-          data: 'No response received from provider for this request',
-        },
+      // Otherwise, look up the response from the provider
+      const providerResponse = responseMap.get(processed.request.id)
+
+      // If provider response is found and the request qualifies for caching, store it
+      if (providerResponse && shouldStore(processed.request, chainId)) {
+        try {
+          await storeResponseInCache(
+            processed.request,
+            chainId,
+            providerResponse,
+            env
+          )
+        } catch (error: any) {
+          console.error('Error caching response:', error)
+        }
       }
-    }
 
-    return providerResponse
-  })
+      // If we couldn't find a matching response, return an error
+      if (!providerResponse) {
+        return {
path: provider/src/batchProcessor.ts
user: login: julien51
body: Let's add a log message here because this should not happen.
created_at: 2025-03-20T14:52:42Z
author_association: MEMBER
diff_hunk: @@ -1,107 +1,38 @@
-import { Env } from './types'
+import { ContractType, Env, RpcRequest } from './types'
 import {
-  RpcRequest,
-  getCacheTTL,
-  createCacheKey,
-  isRequestCacheable,
   getKVContractTypeKey,
+  getCacheTTL,
+  isNameResolutionRequest,
 } from './utils'
-import { ContractType } from './types'
 
 /**
- * RPC RESPONSE CACHING
+ * Generate a cache key from a request's method and params
+ * @param request The RPC request
+ * @returns The cache key
  */
+const generateRequestCacheKey = (request: RpcRequest): string => {
+  let paramsStr = ''
 
-/**
- * Checks if a request is cacheable and attempts to retrieve it from cache
- * Returns the cached response if found, null otherwise
- */
-export const getRPCResponseFromCache = async (
-  networkId: string,
-  body: RpcRequest | RpcRequest[],
-  request: Request
-): Promise<Response | null> => {
   try {
-    // Check if this is a cacheable request
-    if (!isRequestCacheable(body)) {
-      return null
-    }
-
-    // Try to get the cached response
-    const cacheKey = createCacheKey(networkId, body)
-    const cache = caches.default
-    const cachedResponse = await cache.match(new Request(cacheKey))
-
-    if (cachedResponse) {
-      // Add CORS headers to the cached response if needed
-      const headers = {
-        'access-control-allow-origin': '*',
-      }
-
-      // Clone the response and add CORS headers
-      return new Response(cachedResponse.body, {
-        status: cachedResponse.status,
-        statusText: cachedResponse.statusText,
-        headers: {
-          ...Object.fromEntries(cachedResponse.headers.entries()),
-          ...headers,
-        },
-      })
+    if (Array.isArray(request.params)) {
path: provider/src/cache.ts
user: login: julien51
body: Isn't that _always_ an array?
created_at: 2025-03-20T14:53:18Z
author_association: MEMBER
diff_hunk: @@ -151,3 +82,129 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage.
+ * It retrieves and parses the stored value as JSON.
+ * If reading or parsing fails, it attempts to delete the corrupted cache entry and returns null.
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    console.error(
+      `Error reading from cache: ${error instanceof Error ? error.message : String(error)}`
+    )
+    try {
+      await env.REQUEST_CACHE.delete(cacheKey)
+    } catch (err) {
+      console.error(`Error deleting corrupted cache entry: ${err}`)
+    }
path: provider/src/cache.ts
user: login: julien51
body: good idea
created_at: 2025-03-20T14:57:49Z
author_association: MEMBER
diff_hunk: @@ -151,3 +82,129 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage.
+ * It retrieves and parses the stored value as JSON.
+ * If reading or parsing fails, it attempts to delete the corrupted cache entry and returns null.
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    console.error(
+      `Error reading from cache: ${error instanceof Error ? error.message : String(error)}`
+    )
+    try {
+      await env.REQUEST_CACHE.delete(cacheKey)
+    } catch (err) {
+      console.error(`Error deleting corrupted cache entry: ${err}`)
+    }
+  }
+
+  return null
+}
+
+/**
+ * Cache a response
+ * @param request The RPC request
+ * @param response The response to cache
+ * @param chainId The chain ID
+ * @param env The environment variables
+ */
+export const storeResponseInKV = async (
+  request: RpcRequest,
+  response: any,
+  chainId: string,
+  env: Env
+): Promise<void> => {
+  if (!env.REQUEST_CACHE || !response?.result) {
+    return
+  }
+  const cacheKey = generateRequestCacheKey(request)
+  const ttl = getCacheTTL(env)
+
+  try {
+    const cacheValue = { result: response.result }
+    await env.REQUEST_CACHE.put(cacheKey, JSON.stringify(cacheValue), {
path: provider/src/cache.ts
user: login: julien51
body: does this work? I assume if we can "Read" json, it should store json?
```suggestion
    await env.REQUEST_CACHE.put(cacheKey, cacheValue, {
```
created_at: 2025-03-20T14:58:46Z
author_association: MEMBER
diff_hunk: @@ -151,3 +82,129 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage.
+ * It retrieves and parses the stored value as JSON.
+ * If reading or parsing fails, it attempts to delete the corrupted cache entry and returns null.
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    console.error(
+      `Error reading from cache: ${error instanceof Error ? error.message : String(error)}`
+    )
+    try {
+      await env.REQUEST_CACHE.delete(cacheKey)
+    } catch (err) {
+      console.error(`Error deleting corrupted cache entry: ${err}`)
+    }
+  }
+
+  return null
+}
+
+/**
+ * Cache a response
+ * @param request The RPC request
+ * @param response The response to cache
+ * @param chainId The chain ID
+ * @param env The environment variables
+ */
+export const storeResponseInKV = async (
+  request: RpcRequest,
+  response: any,
+  chainId: string,
+  env: Env
+): Promise<void> => {
+  if (!env.REQUEST_CACHE || !response?.result) {
+    return
+  }
+  const cacheKey = generateRequestCacheKey(request)
+  const ttl = getCacheTTL(env)
+
+  try {
+    const cacheValue = { result: response.result }
+    await env.REQUEST_CACHE.put(cacheKey, JSON.stringify(cacheValue), {
path: provider/src/cache.ts
user: login: 0xTxbi
body: no it doesn't. the `put` method only accepts strings, or an array buffer
created_at: 2025-03-20T15:00:21Z
author_association: MEMBER
diff_hunk: @@ -151,3 +55,151 @@ export const storeContractStatusInKV = async (
     console.error('Error storing contract status in KV:', error)
   }
 }
+
+/**
+ * Get a cached response from KV storage
+ * @param request The RPC request
+ * @param chainId The chain ID
+ * @param env The environment variables
+ * @returns The cached response or null if not found
+ *
+ * First attempts to retrieve and parse as JSON directly. If that fails,
+ * falls back to retrieving as text and parsing manually. If the text
+ * parsing also fails, deletes the corrupted cache entry.
+ */
+export const getResponseFromKV = async (
+  request: RpcRequest,
+  chainId: string,
+  env: Env
+): Promise<any> => {
+  if (!env.REQUEST_CACHE) {
+    return null
+  }
+
+  const cacheKey = generateRequestCacheKey(request)
+
+  try {
+    // Try to get and parse as JSON
+    const cached = await env.REQUEST_CACHE.get(cacheKey, 'json')
+    if (cached) {
+      return cached
+    }
+  } catch (error) {
+    // If JSON parsing fails, try simple text retrieval and parse it
+    try {
+      const rawText = await env.REQUEST_CACHE.get(cacheKey, 'text')
+      if (rawText) {
+        try {
+          return JSON.parse(rawText)
+        } catch {
+          // If still can't parse, delete the corrupted entry
+          await env.REQUEST_CACHE.delete(cacheKey).catch((err) => {
+            console.error(`Error deleting corrupted cache entry: ${err}`)
+          })
+        }
+      }
+    } catch (error) {
+      console.error(
+        `Error reading from cache: ${error instanceof Error ? error.message : String(error)}`
+      )
+    }
+  }
+
+  return null
+}
+
+/**
+ * Cache a response
+ * @param request The RPC request
+ * @param response The response to cache
+ * @param chainId The chain ID
+ * @param env The environment variables
+ */
+export const storeResponseInKV = async (
+  request: RpcRequest,
+  response: any,
+  chainId: string,
+  env: Env
+): Promise<void> => {
+  if (!env.REQUEST_CACHE || !response?.result) {
+    return
+  }
+  const cacheKey = generateRequestCacheKey(request)
+  const ttl = getCacheTTL(env)
+
+  try {
+    const cacheValue = { result: response.result }
+    await env.REQUEST_CACHE.put(cacheKey, JSON.stringify(cacheValue), {
+      expirationTtl: ttl,
+    })
path: provider/src/cache.ts
user: login: 0xTxbi
body: nope!
created_at: 2025-03-20T15:20:53Z
author_association: MEMBER

PR comments:
user: login: vercel[bot]
created_at: 2025-03-19T18:57:54Z
body: [vc]: #LVOgVi8tttYELeZWA36Qn0ZFfkzUxaUHmtQEuMA//BE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ1bmxvY2stc3Rvcnlib29rIiwicm9vdERpcmVjdG9yeSI6InBhY2thZ2VzL3VpIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vdW5sb2NrLXByb3RvY29sL3VubG9jay1zdG9yeWJvb2svNnhkV0Njancxd1hRNWJLSmZ5bnBWQ2NkVFFGQSIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIiwicHJldmlld1VybCI6InVubG9jay1zdG9yeWJvb2stZ2l0LWNhY2hlLW5hbWUtcmVzb2x1dGktY2NhMzk1LXVubG9jay1wcm90b2NvbC52ZXJjZWwuYXBwIn0seyJuYW1lIjoidW5sb2NrLWFpcmRyb3BzIiwicm9vdERpcmVjdG9yeSI6ImFpcmRyb3BzIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vdW5sb2NrLXByb3RvY29sL3VubG9jay1haXJkcm9wcy80YjdGaTJUSkxpdGhTRU40a0VINUJGU1dpWEJ3IiwibmV4dENvbW1pdFN0YXR1cyI6IklHTk9SRUQiLCJwcmV2aWV3VXJsIjoidW5sb2NrLWFpcmRyb3BzLWdpdC1jYWNoZS1uYW1lLXJlc29sdXRpby01YzM0YTMtdW5sb2NrLXByb3RvY29sLnZlcmNlbC5hcHAifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>2 Skipped Deployments</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **unlock-airdrops** | ⬜️ Ignored ([Inspect](https://vercel.com/unlock-protocol/unlock-airdrops/4b7Fi2TJLithSEN4kEH5BFSWiXBw)) | [Visit Preview](https://unlock-airdrops-git-cache-name-resolutio-5c34a3-unlock-protocol.vercel.app) |  | Mar 20, 2025 3:15pm |
| **unlock-storybook** | ⬜️ Ignored ([Inspect](https://vercel.com/unlock-protocol/unlock-storybook/6xdWCcjw1wXQ5bKJfynpVCcdTQFA)) | [Visit Preview](https://unlock-storybook-git-cache-name-resoluti-cca395-unlock-protocol.vercel.app) |  | Mar 20, 2025 3:15pm |
</details>



author_association: NONE
user: login: 0xTxbi
created_at: 2025-03-20T15:05:52Z
body: > ok that strartrs to look good! Please test the hell out of this with real traffic locally? I will deploy on cloudflare today!

okay got it!
author_association: MEMBER