diff --git a/packages/rpc-core/src/bundle.ts b/packages/rpc-core/src/bundle.ts
index 49b879029f2..7a99908b054 100644
--- a/packages/rpc-core/src/bundle.ts
+++ b/packages/rpc-core/src/bundle.ts
@@ -11,11 +11,11 @@ import type { RpcCoreStats, RpcInterfaceMethod } from './types/index.js';
import { Observable, publishReplay, refCount } from 'rxjs';
+import { DEFAULT_CAPACITY, LRUCache } from '@polkadot/rpc-provider';
import { rpcDefinitions } from '@polkadot/types';
import { hexToU8a, isFunction, isNull, isUndefined, lazyMethod, logger, memoize, objectSpread, u8aConcat, u8aToU8a } from '@polkadot/util';
import { drr, refCountDelay } from './util/index.js';
-import { DEFAULT_CAPACITY, LRUCache } from './lru.js';
export { packageInfo } from './packageInfo.js';
export * from './util/index.js';
diff --git a/packages/rpc-core/src/lru.spec.ts b/packages/rpc-core/src/lru.spec.ts
deleted file mode 100644
index d36e05b677c..00000000000
--- a/packages/rpc-core/src/lru.spec.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2017-2024 @polkadot/rpc-provider authors & contributors
-// SPDX-License-Identifier: Apache-2.0
-
-///
-
-import { LRUCache } from './lru.js';
-
-describe('LRUCache', (): void => {
- let lru: LRUCache | undefined;
-
- beforeEach((): void => {
- lru = new LRUCache(4);
- });
- afterEach(async () => {
- await lru?.clearInterval();
- lru = undefined;
- });
-
- it('allows getting of items below capacity', (): void => {
- const keys = ['1', '2', '3', '4'];
-
- keys.forEach((k) => lru?.set(k, `${k}${k}${k}`));
- const lruKeys = lru?.keys();
-
- expect(lruKeys?.join(', ')).toBe(keys.reverse().join(', '));
- expect(lru?.length === lru?.lengthData && lru?.length === lru?.lengthRefs).toBe(true);
-
- keys.forEach((k) => expect(lru?.get(k)).toEqual(`${k}${k}${k}`));
- });
-
- it('drops items when at capacity', (): void => {
- const keys = ['1', '2', '3', '4', '5', '6'];
-
- keys.forEach((k) => lru?.set(k, `${k}${k}${k}`));
-
- expect(lru?.keys().join(', ')).toEqual(keys.slice(2).reverse().join(', '));
- expect(lru?.length === lru?.lengthData && lru?.length === lru?.lengthRefs).toBe(true);
-
- keys.slice(2).forEach((k) => expect(lru?.get(k)).toEqual(`${k}${k}${k}`));
- });
-
- it('adjusts the order as they are used', (): void => {
- const keys = ['1', '2', '3', '4', '5'];
-
- keys.forEach((k) => lru?.set(k, `${k}${k}${k}`));
-
- expect(lru?.entries()).toEqual([['5', '555'], ['4', '444'], ['3', '333'], ['2', '222']]);
- expect(lru?.length === lru?.lengthData && lru?.length === lru?.lengthRefs).toBe(true);
-
- lru?.get('3');
-
- expect(lru?.entries()).toEqual([['3', '333'], ['5', '555'], ['4', '444'], ['2', '222']]);
- expect(lru?.length === lru?.lengthData && lru?.length === lru?.lengthRefs).toBe(true);
-
- lru?.set('4', '4433');
-
- expect(lru?.entries()).toEqual([['4', '4433'], ['3', '333'], ['5', '555'], ['2', '222']]);
- expect(lru?.length === lru?.lengthData && lru?.length === lru?.lengthRefs).toBe(true);
-
- lru?.set('6', '666');
-
- expect(lru?.entries()).toEqual([['6', '666'], ['4', '4433'], ['3', '333'], ['5', '555']]);
- expect(lru?.length === lru?.lengthData && lru?.length === lru?.lengthRefs).toBe(true);
- });
-});
diff --git a/packages/rpc-core/src/lru.ts b/packages/rpc-core/src/lru.ts
deleted file mode 100644
index 9483b718b4f..00000000000
--- a/packages/rpc-core/src/lru.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright 2017-2024 @polkadot/rpc-provider authors & contributors
-// SPDX-License-Identifier: Apache-2.0
-
-// Assuming all 1.5MB responses, we apply a default allowing for 192MB
-// cache space (depending on the historic queries this would vary, metadata
-// for Kusama/Polkadot/Substrate falls between 600-750K, 2x for estimate)
-
-export const DEFAULT_CAPACITY = 64;
-
-class LRUNode {
- readonly key: string;
- #lastAccess: number;
- readonly createdAt: number;
-
- public next: LRUNode;
- public prev: LRUNode;
-
- constructor (key: string) {
- this.key = key;
- this.#lastAccess = Date.now();
- this.createdAt = this.#lastAccess;
- this.next = this.prev = this;
- }
-
- public refresh (): void {
- this.#lastAccess = Date.now();
- }
-
- public get lastAccess (): number {
- return this.#lastAccess;
- }
-}
-
-// https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
-export class LRUCache {
- readonly capacity: number;
-
- readonly #data = new Map();
- readonly #refs = new Map();
-
- #length = 0;
- #head: LRUNode;
- #tail: LRUNode;
- // TTL
- readonly #ttl: number;
- readonly #ttlInterval: number;
- #ttlTimerId: ReturnType | null = null;
-
- constructor (capacity = DEFAULT_CAPACITY, ttl = 30000, ttlInterval = 15000) {
- this.capacity = capacity;
- this.#ttl = ttl;
- this.#ttlInterval = ttlInterval;
- this.#head = this.#tail = new LRUNode('');
-
- // make sure the interval is not longer than the ttl
- if (this.#ttlInterval > this.#ttl) {
- this.#ttlInterval = this.#ttl;
- }
- }
-
- get ttl (): number {
- return this.#ttl;
- }
-
- get ttlInterval (): number {
- return this.#ttlInterval;
- }
-
- get length (): number {
- return this.#length;
- }
-
- get lengthData (): number {
- return this.#data.size;
- }
-
- get lengthRefs (): number {
- return this.#refs.size;
- }
-
- entries (): [string, unknown][] {
- const keys = this.keys();
- const count = keys.length;
- const entries = new Array<[string, unknown]>(count);
-
- for (let i = 0; i < count; i++) {
- const key = keys[i];
-
- entries[i] = [key, this.#data.get(key)];
- }
-
- return entries;
- }
-
- keys (): string[] {
- const keys: string[] = [];
-
- if (this.#length) {
- let curr = this.#head;
-
- while (curr !== this.#tail) {
- keys.push(curr.key);
- curr = curr.next;
- }
-
- keys.push(curr.key);
- }
-
- return keys;
- }
-
- get (key: string): T | null {
- const data = this.#data.get(key);
-
- if (data) {
- this.#toHead(key);
-
- return data as T;
- }
-
- return null;
- }
-
- set (key: string, value: T): void {
- if (this.#data.has(key)) {
- this.#toHead(key);
- } else {
- const node = new LRUNode(key);
-
- this.#refs.set(node.key, node);
-
- if (this.length === 0) {
- this.#head = this.#tail = node;
- } else {
- this.#head.prev = node;
- node.next = this.#head;
- this.#head = node;
- }
-
- if (this.#length === this.capacity) {
- this.#data.delete(this.#tail.key);
- this.#refs.delete(this.#tail.key);
-
- this.#tail = this.#tail.prev;
- this.#tail.next = this.#head;
- } else {
- this.#length += 1;
- }
- }
-
- if (this.#ttl > 0 && !this.#ttlTimerId) {
- this.#ttlTimerId = setInterval(() => {
- this.#ttlClean();
- }, this.#ttlInterval);
- }
-
- this.#data.set(key, value);
- }
-
- #ttlClean () {
- // Find last node to keep
- const expires = Date.now() - this.#ttl;
-
- // traverse map to find the lastAccessed
- while (this.#tail.lastAccess && this.#tail.lastAccess < expires && this.#length > 0) {
- if (this.#ttlTimerId && this.#length === 0) {
- clearInterval(this.#ttlTimerId);
- this.#ttlTimerId = null;
- this.#head = this.#tail = new LRUNode('');
- } else {
- this.#refs.delete(this.#tail.key);
- this.#data.delete(this.#tail.key);
- this.#length -= 1;
- this.#tail = this.#tail.prev;
- this.#tail.next = this.#head;
- }
- }
- }
-
- #toHead (key: string): void {
- const ref = this.#refs.get(key);
-
- if (ref && ref !== this.#head) {
- ref.refresh();
- ref.prev.next = ref.next;
- ref.next.prev = ref.prev;
- ref.next = this.#head;
-
- this.#head.prev = ref;
- this.#head = ref;
- }
- }
-
- // eslint-disable-next-line @typescript-eslint/require-await
- public async clearInterval (): Promise {
- if (this.#ttlTimerId) {
- clearInterval(this.#ttlTimerId);
- this.#ttlTimerId = null;
- }
- }
-}
diff --git a/packages/rpc-provider/src/bundle.ts b/packages/rpc-provider/src/bundle.ts
index 06b0acaf1aa..8a41c307b73 100644
--- a/packages/rpc-provider/src/bundle.ts
+++ b/packages/rpc-provider/src/bundle.ts
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
export { HttpProvider } from './http/index.js';
+export { DEFAULT_CAPACITY, LRUCache } from './lru.js';
export { packageInfo } from './packageInfo.js';
export { ScProvider } from './substrate-connect/index.js';
export { WsProvider } from './ws/index.js';