From 868fcdc313863a72fdd21c399a46207a1f144b16 Mon Sep 17 00:00:00 2001 From: furiosa Date: Fri, 20 Feb 2026 16:47:05 -0700 Subject: [PATCH] refactor: remove RPC infrastructure types from shared data-models These types (RpcProviderRole, RpcProviderType, RpcEndpointConfig, ChainRpcConfig, RpcProviderConfig, CircuitBreakerConfig, RetryConfig, HealthCheckConfig, UserRpcEndpoint, UserRpcConfig, PrivacyConfig) belong in @cygnus-wealth/rpc-infrastructure per en-1mg1 analysis. Removes 11 type files, their exports, tests, and API report entries. Bumps version to 1.4.0. All 400 remaining tests pass. --- etc/data-models.api.md | 91 --- package-lock.json | 4 +- package.json | 12 +- src/enums/RpcProviderRole.ts | 36 -- src/enums/RpcProviderType.ts | 38 -- src/index.ts | 14 - src/interfaces/ChainRpcConfig.ts | 53 -- src/interfaces/CircuitBreakerConfig.ts | 37 -- src/interfaces/HealthCheckConfig.ts | 33 -- src/interfaces/PrivacyConfig.ts | 32 -- src/interfaces/RetryConfig.ts | 32 -- src/interfaces/RpcEndpointConfig.ts | 58 -- src/interfaces/RpcProviderConfig.ts | 83 --- src/interfaces/UserRpcConfig.ts | 36 -- src/interfaces/UserRpcEndpoint.ts | 37 -- tests/unit/rpc-config.test.ts | 767 ------------------------- 16 files changed, 3 insertions(+), 1360 deletions(-) delete mode 100644 src/enums/RpcProviderRole.ts delete mode 100644 src/enums/RpcProviderType.ts delete mode 100644 src/interfaces/ChainRpcConfig.ts delete mode 100644 src/interfaces/CircuitBreakerConfig.ts delete mode 100644 src/interfaces/HealthCheckConfig.ts delete mode 100644 src/interfaces/PrivacyConfig.ts delete mode 100644 src/interfaces/RetryConfig.ts delete mode 100644 src/interfaces/RpcEndpointConfig.ts delete mode 100644 src/interfaces/RpcProviderConfig.ts delete mode 100644 src/interfaces/UserRpcConfig.ts delete mode 100644 src/interfaces/UserRpcEndpoint.ts delete mode 100644 tests/unit/rpc-config.test.ts diff --git a/etc/data-models.api.md b/etc/data-models.api.md index e78d5b7..b2038d9 100644 --- a/etc/data-models.api.md +++ b/etc/data-models.api.md @@ -226,23 +226,6 @@ export enum Chain { SUI = "SUI" } -// @public -export interface ChainRpcConfig { - cacheStaleAcceptanceMs: number; - chainId: number; - chainName: string; - endpoints: RpcEndpointConfig[]; - totalOperationTimeoutMs: number; -} - -// @public -export interface CircuitBreakerConfig { - failureThreshold: number; - halfOpenMaxAttempts: number; - monitorWindowMs: number; - openDurationMs: number; -} - // @public export interface ConnectedAccount { accountId: AccountId; @@ -331,13 +314,6 @@ export interface GroupPortfolio { totalValue: Price; } -// @public -export interface HealthCheckConfig { - intervalMs: number; - method: string; - timeoutMs: number; -} - // @public export interface IntegrationCredentials { accountId?: AccountId; @@ -510,59 +486,6 @@ export interface Price { value?: number; } -// @public -export interface PrivacyConfig { - privacyMode: boolean; - queryJitterMs: number; - rotateWithinTier: boolean; -} - -// @public -export interface RetryConfig { - baseDelayMs: number; - maxAttempts: number; - maxDelayMs: number; -} - -// @public -export interface RpcEndpointConfig { - provider: string; - rateLimitRps: number; - role: RpcProviderRole; - timeoutMs: number; - type: RpcProviderType; - url: string; - weight?: number; - wsUrl?: string; -} - -// @public -export interface RpcProviderConfig { - chains: Record; - circuitBreaker: CircuitBreakerConfig; - healthCheck: HealthCheckConfig; - privacy: PrivacyConfig; - retry: RetryConfig; - userOverrides?: UserRpcConfig; -} - -// @public -export enum RpcProviderRole { - EMERGENCY = "EMERGENCY", - PRIMARY = "PRIMARY", - SECONDARY = "SECONDARY", - TERTIARY = "TERTIARY" -} - -// @public -export enum RpcProviderType { - COMMUNITY = "COMMUNITY", - DECENTRALIZED = "DECENTRALIZED", - MANAGED = "MANAGED", - PUBLIC = "PUBLIC", - USER = "USER" -} - // @public export type SortOrder = 'ASC' | 'DESC'; @@ -667,20 +590,6 @@ export enum TransactionType { UNSTAKE = "UNSTAKE" } -// @public -export interface UserRpcConfig { - endpoints: UserRpcEndpoint[]; - mode: 'override' | 'prepend'; -} - -// @public -export interface UserRpcEndpoint { - chainId: string; - label?: string; - url: string; - wsUrl?: string; -} - // @public export interface VaultPosition { apy?: number; diff --git a/package-lock.json b/package-lock.json index 1d8efac..0c8911e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cygnus-wealth/data-models", - "version": "1.2.0", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cygnus-wealth/data-models", - "version": "1.2.0", + "version": "1.4.0", "license": "ISC", "devDependencies": { "@eslint/js": "^9.32.0", diff --git a/package.json b/package.json index af0fec1..94e92dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cygnus-wealth/data-models", - "version": "1.3.0", + "version": "1.4.0", "description": "Shared TypeScript data models for CygnusWealth project", "main": "dist/cjs/index.js", "module": "dist/index.js", @@ -61,16 +61,6 @@ "import": "./dist/enums/DeFiDiscoverySource.js", "require": "./dist/cjs/enums/DeFiDiscoverySource.js" }, - "./enums/RpcProviderRole": { - "types": "./dist/enums/RpcProviderRole.d.ts", - "import": "./dist/enums/RpcProviderRole.js", - "require": "./dist/cjs/enums/RpcProviderRole.js" - }, - "./enums/RpcProviderType": { - "types": "./dist/enums/RpcProviderType.d.ts", - "import": "./dist/enums/RpcProviderType.js", - "require": "./dist/cjs/enums/RpcProviderType.js" - }, "./interfaces/*": { "types": "./dist/interfaces/*.d.ts", "import": "./dist/interfaces/*.js", diff --git a/src/enums/RpcProviderRole.ts b/src/enums/RpcProviderRole.ts deleted file mode 100644 index 21858fa..0000000 --- a/src/enums/RpcProviderRole.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Priority role of an RPC endpoint within a chain's fallback chain. - * - * Defines the priority ordering for endpoint selection during RPC calls. - * The fallback chain progresses through roles in order: primary → secondary - * → tertiary → emergency when higher-priority endpoints fail or are unavailable. - * - * @example - * ```typescript - * import { RpcProviderRole } from '@cygnus-wealth/data-models'; - * - * const endpoint = { - * role: RpcProviderRole.PRIMARY, - * url: 'https://eth-mainnet.alchemyapi.io/v2/...' - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link RpcEndpointConfig} for endpoint configuration - * @see {@link ChainRpcConfig} for chain-level fallback ordering - */ -export enum RpcProviderRole { - /** First-choice endpoint — used under normal operating conditions */ - PRIMARY = 'PRIMARY', - - /** Second-choice endpoint — activated when primary is degraded or unavailable */ - SECONDARY = 'SECONDARY', - - /** Third-choice endpoint — activated when primary and secondary are unavailable */ - TERTIARY = 'TERTIARY', - - /** Last-resort endpoint — used only when all other roles are exhausted */ - EMERGENCY = 'EMERGENCY' -} diff --git a/src/enums/RpcProviderType.ts b/src/enums/RpcProviderType.ts deleted file mode 100644 index dbf34a4..0000000 --- a/src/enums/RpcProviderType.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Classification of RPC provider infrastructure ownership. - * - * Categorizes RPC endpoints by their operational model, which affects - * reliability expectations, rate limits, and cost considerations. - * - * @example - * ```typescript - * import { RpcProviderType } from '@cygnus-wealth/data-models'; - * - * const endpoint = { - * type: RpcProviderType.MANAGED, - * provider: 'Alchemy', - * rateLimitRps: 100 - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link RpcEndpointConfig} for endpoint configuration - */ -export enum RpcProviderType { - /** Paid/managed provider (Alchemy, Infura, QuickNode) — highest reliability */ - MANAGED = 'MANAGED', - - /** Free public endpoint (chain-provided, Ankr public) — best-effort availability */ - PUBLIC = 'PUBLIC', - - /** Community-run node (POKT, Llama Nodes) — variable reliability */ - COMMUNITY = 'COMMUNITY', - - /** Decentralized RPC network (POKT Gateway, Lava Network) — censorship-resistant */ - DECENTRALIZED = 'DECENTRALIZED', - - /** User-provided custom endpoint — unverified, user-managed */ - USER = 'USER' -} diff --git a/src/index.ts b/src/index.ts index 0cfa653..18a09a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,9 +9,6 @@ export { VaultStrategyType } from './enums/VaultStrategyType'; export { DeFiPositionType } from './enums/DeFiPositionType'; export { DeFiProtocol } from './enums/DeFiProtocol'; export { DeFiDiscoverySource } from './enums/DeFiDiscoverySource'; -export { RpcProviderRole } from './enums/RpcProviderRole'; -export { RpcProviderType } from './enums/RpcProviderType'; - // Base Interfaces export { BaseEntity } from './interfaces/BaseEntity'; export { Metadata } from './interfaces/Metadata'; @@ -80,17 +77,6 @@ export { TimeRange } from './types/TimeRange'; export { SortOrder } from './types/SortOrder'; export { FilterOptions } from './interfaces/FilterOptions'; -// RPC Provider Configuration -export { RpcEndpointConfig } from './interfaces/RpcEndpointConfig'; -export { ChainRpcConfig } from './interfaces/ChainRpcConfig'; -export { CircuitBreakerConfig } from './interfaces/CircuitBreakerConfig'; -export { RetryConfig } from './interfaces/RetryConfig'; -export { HealthCheckConfig } from './interfaces/HealthCheckConfig'; -export { RpcProviderConfig } from './interfaces/RpcProviderConfig'; -export type { UserRpcEndpoint } from './interfaces/UserRpcEndpoint'; -export type { UserRpcConfig } from './interfaces/UserRpcConfig'; -export type { PrivacyConfig } from './interfaces/PrivacyConfig'; - // Network Environment export { NetworkEnvironment, EnvironmentConfig } from './types/NetworkEnvironment'; diff --git a/src/interfaces/ChainRpcConfig.ts b/src/interfaces/ChainRpcConfig.ts deleted file mode 100644 index 59e110d..0000000 --- a/src/interfaces/ChainRpcConfig.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { RpcEndpointConfig } from './RpcEndpointConfig'; - -/** - * RPC configuration for a single blockchain network. - * - * Groups all RPC endpoints for one chain into a prioritized fallback chain, - * along with chain-level operational parameters. Endpoints within should be - * ordered by {@link RpcProviderRole} priority. - * - * @example - * ```typescript - * import { ChainRpcConfig, RpcProviderRole, RpcProviderType } from '@cygnus-wealth/data-models'; - * - * const ethereumRpc: ChainRpcConfig = { - * chainId: 1, - * chainName: 'Ethereum Mainnet', - * endpoints: [ - * { - * url: 'https://eth-mainnet.g.alchemy.com/v2/key', - * provider: 'Alchemy', - * role: RpcProviderRole.PRIMARY, - * type: RpcProviderType.MANAGED, - * rateLimitRps: 100, - * timeoutMs: 5000 - * } - * ], - * totalOperationTimeoutMs: 30000, - * cacheStaleAcceptanceMs: 60000 - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link RpcEndpointConfig} for individual endpoint configuration - * @see {@link RpcProviderConfig} for top-level multi-chain configuration - */ -export interface ChainRpcConfig { - /** Numeric chain ID (e.g. 1 for Ethereum, 137 for Polygon) */ - chainId: number; - - /** Human-readable chain name (e.g. "Ethereum Mainnet") */ - chainName: string; - - /** Ordered list of RPC endpoints forming the fallback chain */ - endpoints: RpcEndpointConfig[]; - - /** Maximum total time in milliseconds for an operation across all retry/fallback attempts */ - totalOperationTimeoutMs: number; - - /** Duration in milliseconds to accept stale cached data before forcing a fresh RPC call */ - cacheStaleAcceptanceMs: number; -} diff --git a/src/interfaces/CircuitBreakerConfig.ts b/src/interfaces/CircuitBreakerConfig.ts deleted file mode 100644 index 40e7aec..0000000 --- a/src/interfaces/CircuitBreakerConfig.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Circuit breaker configuration for RPC endpoint failure isolation. - * - * Controls the circuit breaker pattern that prevents cascading failures by - * temporarily removing degraded endpoints from the active pool. Follows the - * standard CLOSED → OPEN → HALF-OPEN state machine. - * - * @example - * ```typescript - * import { CircuitBreakerConfig } from '@cygnus-wealth/data-models'; - * - * const circuitBreaker: CircuitBreakerConfig = { - * failureThreshold: 5, - * openDurationMs: 30000, - * halfOpenMaxAttempts: 2, - * monitorWindowMs: 60000 - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link RpcProviderConfig} for top-level configuration - */ -export interface CircuitBreakerConfig { - /** Number of consecutive failures before opening the circuit */ - failureThreshold: number; - - /** Duration in milliseconds the circuit stays open before transitioning to half-open */ - openDurationMs: number; - - /** Maximum probe requests allowed in half-open state before deciding to close or re-open */ - halfOpenMaxAttempts: number; - - /** Sliding window in milliseconds over which failures are counted */ - monitorWindowMs: number; -} diff --git a/src/interfaces/HealthCheckConfig.ts b/src/interfaces/HealthCheckConfig.ts deleted file mode 100644 index 95a5515..0000000 --- a/src/interfaces/HealthCheckConfig.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Health check configuration for RPC endpoint monitoring. - * - * Controls periodic health probes that determine endpoint availability - * and inform circuit breaker state transitions. - * - * @example - * ```typescript - * import { HealthCheckConfig } from '@cygnus-wealth/data-models'; - * - * const healthCheck: HealthCheckConfig = { - * intervalMs: 30000, - * timeoutMs: 5000, - * method: 'eth_blockNumber' - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link RpcProviderConfig} for top-level configuration - * @see {@link CircuitBreakerConfig} for failure isolation - */ -export interface HealthCheckConfig { - /** Interval in milliseconds between health check probes */ - intervalMs: number; - - /** Timeout in milliseconds for each health check request */ - timeoutMs: number; - - /** JSON-RPC method used for health probes (e.g. "eth_blockNumber", "eth_chainId") */ - method: string; -} diff --git a/src/interfaces/PrivacyConfig.ts b/src/interfaces/PrivacyConfig.ts deleted file mode 100644 index 658790e..0000000 --- a/src/interfaces/PrivacyConfig.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Privacy-related configuration for RPC request handling. - * - * Controls endpoint rotation and query obfuscation strategies to - * reduce correlation of user activity across RPC providers. - * - * @example - * ```typescript - * import type { PrivacyConfig } from '@cygnus-wealth/data-models'; - * - * const privacy: PrivacyConfig = { - * rotateWithinTier: true, - * privacyMode: true, - * queryJitterMs: 150 - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link RpcProviderConfig} for top-level usage - */ -export interface PrivacyConfig { - /** Rotate between endpoints of the same tier/role to avoid fingerprinting */ - rotateWithinTier: boolean; - - /** Enable full privacy mode (distributes queries across providers) */ - privacyMode: boolean; - - /** Random jitter added to query timing to resist timing analysis (in milliseconds) */ - queryJitterMs: number; -} diff --git a/src/interfaces/RetryConfig.ts b/src/interfaces/RetryConfig.ts deleted file mode 100644 index 23ceb1e..0000000 --- a/src/interfaces/RetryConfig.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Retry strategy configuration for failed RPC calls. - * - * Controls exponential backoff behavior when individual RPC requests fail. - * Applied per-request before escalating to the next endpoint in the fallback chain. - * - * @example - * ```typescript - * import { RetryConfig } from '@cygnus-wealth/data-models'; - * - * const retry: RetryConfig = { - * maxAttempts: 3, - * baseDelayMs: 1000, - * maxDelayMs: 10000 - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link RpcProviderConfig} for top-level configuration - */ -export interface RetryConfig { - /** Maximum number of retry attempts per request (including the initial attempt) */ - maxAttempts: number; - - /** Base delay in milliseconds for exponential backoff (delay = baseDelayMs * 2^attempt) */ - baseDelayMs: number; - - /** Maximum delay in milliseconds — caps exponential growth */ - maxDelayMs: number; -} diff --git a/src/interfaces/RpcEndpointConfig.ts b/src/interfaces/RpcEndpointConfig.ts deleted file mode 100644 index a6fdcd7..0000000 --- a/src/interfaces/RpcEndpointConfig.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { RpcProviderRole } from '../enums/RpcProviderRole'; -import { RpcProviderType } from '../enums/RpcProviderType'; - -/** - * Configuration for a single RPC endpoint within a chain's fallback chain. - * - * Describes connection details, provider metadata, and operational limits - * for one RPC endpoint. Multiple endpoints are composed into a - * {@link ChainRpcConfig} to form a prioritized fallback chain. - * - * @example - * ```typescript - * import { RpcEndpointConfig, RpcProviderRole, RpcProviderType } from '@cygnus-wealth/data-models'; - * - * const alchemyEndpoint: RpcEndpointConfig = { - * url: 'https://eth-mainnet.g.alchemy.com/v2/key', - * wsUrl: 'wss://eth-mainnet.g.alchemy.com/v2/key', - * provider: 'Alchemy', - * role: RpcProviderRole.PRIMARY, - * type: RpcProviderType.MANAGED, - * rateLimitRps: 100, - * timeoutMs: 5000, - * weight: 100 - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link RpcProviderRole} for endpoint priority in fallback chain - * @see {@link RpcProviderType} for provider classification - * @see {@link ChainRpcConfig} for chain-level endpoint composition - */ -export interface RpcEndpointConfig { - /** HTTP(S) JSON-RPC endpoint URL */ - url: string; - - /** WebSocket endpoint URL for subscriptions (optional — not all providers support WS) */ - wsUrl?: string; - - /** Human-readable provider name (e.g. "Alchemy", "Infura", "Ankr Public") */ - provider: string; - - /** Priority role within the fallback chain */ - role: RpcProviderRole; - - /** Infrastructure classification of the provider */ - type: RpcProviderType; - - /** Maximum requests per second allowed by this endpoint */ - rateLimitRps: number; - - /** Per-request timeout in milliseconds */ - timeoutMs: number; - - /** Relative weight for load balancing among same-role endpoints (higher = more traffic) */ - weight?: number; -} diff --git a/src/interfaces/RpcProviderConfig.ts b/src/interfaces/RpcProviderConfig.ts deleted file mode 100644 index 9aab943..0000000 --- a/src/interfaces/RpcProviderConfig.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { ChainRpcConfig } from './ChainRpcConfig'; -import { CircuitBreakerConfig } from './CircuitBreakerConfig'; -import { RetryConfig } from './RetryConfig'; -import { HealthCheckConfig } from './HealthCheckConfig'; -import { PrivacyConfig } from './PrivacyConfig'; -import { UserRpcConfig } from './UserRpcConfig'; - -/** - * Top-level RPC provider configuration for multi-chain operations. - * - * The root configuration object that composes per-chain endpoint configs - * with shared operational policies (circuit breaking, retry, health checking). - * This is the primary type consumed by downstream RPC fallback chain implementations. - * - * @example - * ```typescript - * import { RpcProviderConfig, RpcProviderRole, RpcProviderType } from '@cygnus-wealth/data-models'; - * - * const config: RpcProviderConfig = { - * chains: { - * '1': { - * chainId: 1, - * chainName: 'Ethereum Mainnet', - * endpoints: [{ - * url: 'https://eth-mainnet.g.alchemy.com/v2/key', - * provider: 'Alchemy', - * role: RpcProviderRole.PRIMARY, - * type: RpcProviderType.MANAGED, - * rateLimitRps: 100, - * timeoutMs: 5000 - * }], - * totalOperationTimeoutMs: 30000, - * cacheStaleAcceptanceMs: 60000 - * } - * }, - * circuitBreaker: { - * failureThreshold: 5, - * openDurationMs: 30000, - * halfOpenMaxAttempts: 2, - * monitorWindowMs: 60000 - * }, - * retry: { - * maxAttempts: 3, - * baseDelayMs: 1000, - * maxDelayMs: 10000 - * }, - * healthCheck: { - * intervalMs: 30000, - * timeoutMs: 5000, - * method: 'eth_blockNumber' - * } - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link ChainRpcConfig} for per-chain configuration - * @see {@link CircuitBreakerConfig} for failure isolation policy - * @see {@link RetryConfig} for retry strategy - * @see {@link HealthCheckConfig} for endpoint monitoring - * @see {@link PrivacyConfig} for privacy and rotation policy - * @see {@link UserRpcConfig} for user-provided endpoint overrides - */ -export interface RpcProviderConfig { - /** Per-chain RPC configurations, keyed by chain ID string (e.g. "1", "137") */ - chains: Record; - - /** Circuit breaker policy applied to all endpoints */ - circuitBreaker: CircuitBreakerConfig; - - /** Retry strategy applied to individual RPC requests */ - retry: RetryConfig; - - /** Health check policy for endpoint monitoring */ - healthCheck: HealthCheckConfig; - - /** Privacy and endpoint rotation policy */ - privacy: PrivacyConfig; - - /** Optional user-provided RPC endpoint overrides */ - userOverrides?: UserRpcConfig; -} diff --git a/src/interfaces/UserRpcConfig.ts b/src/interfaces/UserRpcConfig.ts deleted file mode 100644 index bf1ed2f..0000000 --- a/src/interfaces/UserRpcConfig.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { UserRpcEndpoint } from './UserRpcEndpoint'; - -/** - * Configuration for user-provided RPC endpoint overrides. - * - * Determines how user-supplied endpoints interact with the - * platform-managed endpoint list. In 'override' mode, user endpoints - * completely replace managed endpoints for the given chains. In - * 'prepend' mode, user endpoints are tried first before falling back - * to managed endpoints. - * - * @example - * ```typescript - * import type { UserRpcConfig } from '@cygnus-wealth/data-models'; - * - * const userConfig: UserRpcConfig = { - * endpoints: [ - * { chainId: '1', url: 'https://my-eth.example.com/rpc', label: 'My ETH' } - * ], - * mode: 'prepend' - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link UserRpcEndpoint} for individual endpoint definition - * @see {@link RpcProviderConfig} for top-level usage - */ -export interface UserRpcConfig { - /** List of user-provided RPC endpoints */ - endpoints: UserRpcEndpoint[]; - - /** How user endpoints interact with managed endpoints: 'override' replaces, 'prepend' adds before */ - mode: 'override' | 'prepend'; -} diff --git a/src/interfaces/UserRpcEndpoint.ts b/src/interfaces/UserRpcEndpoint.ts deleted file mode 100644 index 859553a..0000000 --- a/src/interfaces/UserRpcEndpoint.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * A user-provided custom RPC endpoint configuration. - * - * Represents an RPC endpoint supplied by the end user, enabling - * self-sovereign infrastructure choices. These endpoints are not - * managed or verified by the platform. - * - * @example - * ```typescript - * import type { UserRpcEndpoint } from '@cygnus-wealth/data-models'; - * - * const myNode: UserRpcEndpoint = { - * chainId: '1', - * url: 'https://my-private-node.example.com/rpc', - * wsUrl: 'wss://my-private-node.example.com/ws', - * label: 'My Private Ethereum Node' - * }; - * ``` - * - * @since 1.3.0 - * @stability extended - * - * @see {@link UserRpcConfig} for aggregating user endpoints - */ -export interface UserRpcEndpoint { - /** Target chain ID as a string (e.g. "1", "137") */ - chainId: string; - - /** HTTP(S) RPC endpoint URL */ - url: string; - - /** Optional WebSocket endpoint URL for subscriptions */ - wsUrl?: string; - - /** Optional human-readable label for the endpoint */ - label?: string; -} diff --git a/tests/unit/rpc-config.test.ts b/tests/unit/rpc-config.test.ts deleted file mode 100644 index 7f77d22..0000000 --- a/tests/unit/rpc-config.test.ts +++ /dev/null @@ -1,767 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - RpcProviderRole, - RpcProviderType, - RpcEndpointConfig, - ChainRpcConfig, - CircuitBreakerConfig, - RetryConfig, - HealthCheckConfig, - RpcProviderConfig -} from '../../src/index'; -import type { - UserRpcEndpoint, - UserRpcConfig, - PrivacyConfig -} from '../../src/index'; - -/** - * Unit tests for RPC Provider Configuration types. - * Coverage target: 100% - * - * Tests enum values, interface structure, and contract stability. - */ - -describe('RPC Provider Configuration Types', () => { - describe('RpcProviderRole', () => { - it('should have all expected roles', () => { - expect(RpcProviderRole.PRIMARY).toBe('PRIMARY'); - expect(RpcProviderRole.SECONDARY).toBe('SECONDARY'); - expect(RpcProviderRole.TERTIARY).toBe('TERTIARY'); - expect(RpcProviderRole.EMERGENCY).toBe('EMERGENCY'); - }); - - it('should have unique values (no duplicates)', () => { - const values = Object.values(RpcProviderRole); - const uniqueValues = new Set(values); - expect(values.length).toBe(uniqueValues.size); - }); - - it('should have exactly 4 roles', () => { - const values = Object.values(RpcProviderRole); - expect(values).toHaveLength(4); - }); - - it('should represent a priority ordering', () => { - const roles = [ - RpcProviderRole.PRIMARY, - RpcProviderRole.SECONDARY, - RpcProviderRole.TERTIARY, - RpcProviderRole.EMERGENCY - ]; - - // All roles should be distinct - const uniqueRoles = new Set(roles); - expect(uniqueRoles.size).toBe(4); - }); - }); - - describe('RpcProviderType', () => { - it('should have all expected types', () => { - expect(RpcProviderType.MANAGED).toBe('MANAGED'); - expect(RpcProviderType.PUBLIC).toBe('PUBLIC'); - expect(RpcProviderType.COMMUNITY).toBe('COMMUNITY'); - expect(RpcProviderType.DECENTRALIZED).toBe('DECENTRALIZED'); - expect(RpcProviderType.USER).toBe('USER'); - }); - - it('should have unique values (no duplicates)', () => { - const values = Object.values(RpcProviderType); - const uniqueValues = new Set(values); - expect(values.length).toBe(uniqueValues.size); - }); - - it('should have exactly 5 types', () => { - const values = Object.values(RpcProviderType); - expect(values).toHaveLength(5); - }); - - it('should distinguish infrastructure ownership models', () => { - expect(RpcProviderType.MANAGED).not.toBe(RpcProviderType.PUBLIC); - expect(RpcProviderType.PUBLIC).not.toBe(RpcProviderType.COMMUNITY); - expect(RpcProviderType.MANAGED).not.toBe(RpcProviderType.COMMUNITY); - }); - }); - - describe('RpcEndpointConfig', () => { - it('should accept a fully specified endpoint', () => { - const endpoint: RpcEndpointConfig = { - url: 'https://eth-mainnet.g.alchemy.com/v2/key', - wsUrl: 'wss://eth-mainnet.g.alchemy.com/v2/key', - provider: 'Alchemy', - role: RpcProviderRole.PRIMARY, - type: RpcProviderType.MANAGED, - rateLimitRps: 100, - timeoutMs: 5000, - weight: 100 - }; - - expect(endpoint.url).toBe('https://eth-mainnet.g.alchemy.com/v2/key'); - expect(endpoint.wsUrl).toBe('wss://eth-mainnet.g.alchemy.com/v2/key'); - expect(endpoint.provider).toBe('Alchemy'); - expect(endpoint.role).toBe(RpcProviderRole.PRIMARY); - expect(endpoint.type).toBe(RpcProviderType.MANAGED); - expect(endpoint.rateLimitRps).toBe(100); - expect(endpoint.timeoutMs).toBe(5000); - expect(endpoint.weight).toBe(100); - }); - - it('should accept an endpoint without optional fields', () => { - const endpoint: RpcEndpointConfig = { - url: 'https://rpc.ankr.com/eth', - provider: 'Ankr Public', - role: RpcProviderRole.TERTIARY, - type: RpcProviderType.PUBLIC, - rateLimitRps: 10, - timeoutMs: 10000 - }; - - expect(endpoint.url).toBe('https://rpc.ankr.com/eth'); - expect(endpoint.wsUrl).toBeUndefined(); - expect(endpoint.weight).toBeUndefined(); - }); - - it('should be JSON serializable', () => { - const endpoint: RpcEndpointConfig = { - url: 'https://example.com/rpc', - provider: 'Test', - role: RpcProviderRole.SECONDARY, - type: RpcProviderType.COMMUNITY, - rateLimitRps: 50, - timeoutMs: 3000 - }; - - const json = JSON.stringify(endpoint); - const parsed = JSON.parse(json); - - expect(parsed.url).toBe(endpoint.url); - expect(parsed.role).toBe('SECONDARY'); - expect(parsed.type).toBe('COMMUNITY'); - }); - }); - - describe('ChainRpcConfig', () => { - it('should accept a complete chain configuration', () => { - const config: ChainRpcConfig = { - chainId: 1, - chainName: 'Ethereum Mainnet', - endpoints: [ - { - url: 'https://eth-mainnet.g.alchemy.com/v2/key', - provider: 'Alchemy', - role: RpcProviderRole.PRIMARY, - type: RpcProviderType.MANAGED, - rateLimitRps: 100, - timeoutMs: 5000 - }, - { - url: 'https://rpc.ankr.com/eth', - provider: 'Ankr Public', - role: RpcProviderRole.SECONDARY, - type: RpcProviderType.PUBLIC, - rateLimitRps: 10, - timeoutMs: 10000 - } - ], - totalOperationTimeoutMs: 30000, - cacheStaleAcceptanceMs: 60000 - }; - - expect(config.chainId).toBe(1); - expect(config.chainName).toBe('Ethereum Mainnet'); - expect(config.endpoints).toHaveLength(2); - expect(config.endpoints[0].role).toBe(RpcProviderRole.PRIMARY); - expect(config.endpoints[1].role).toBe(RpcProviderRole.SECONDARY); - expect(config.totalOperationTimeoutMs).toBe(30000); - expect(config.cacheStaleAcceptanceMs).toBe(60000); - }); - - it('should accept a chain with a single endpoint', () => { - const config: ChainRpcConfig = { - chainId: 137, - chainName: 'Polygon PoS', - endpoints: [ - { - url: 'https://polygon-rpc.com', - provider: 'Polygon Public', - role: RpcProviderRole.PRIMARY, - type: RpcProviderType.PUBLIC, - rateLimitRps: 25, - timeoutMs: 8000 - } - ], - totalOperationTimeoutMs: 20000, - cacheStaleAcceptanceMs: 30000 - }; - - expect(config.chainId).toBe(137); - expect(config.endpoints).toHaveLength(1); - }); - }); - - describe('CircuitBreakerConfig', () => { - it('should accept a complete circuit breaker configuration', () => { - const config: CircuitBreakerConfig = { - failureThreshold: 5, - openDurationMs: 30000, - halfOpenMaxAttempts: 2, - monitorWindowMs: 60000 - }; - - expect(config.failureThreshold).toBe(5); - expect(config.openDurationMs).toBe(30000); - expect(config.halfOpenMaxAttempts).toBe(2); - expect(config.monitorWindowMs).toBe(60000); - }); - - it('should be JSON serializable', () => { - const config: CircuitBreakerConfig = { - failureThreshold: 3, - openDurationMs: 15000, - halfOpenMaxAttempts: 1, - monitorWindowMs: 30000 - }; - - const json = JSON.stringify(config); - const parsed = JSON.parse(json); - - expect(parsed).toEqual(config); - }); - }); - - describe('RetryConfig', () => { - it('should accept a complete retry configuration', () => { - const config: RetryConfig = { - maxAttempts: 3, - baseDelayMs: 1000, - maxDelayMs: 10000 - }; - - expect(config.maxAttempts).toBe(3); - expect(config.baseDelayMs).toBe(1000); - expect(config.maxDelayMs).toBe(10000); - }); - - it('should support exponential backoff calculation', () => { - const config: RetryConfig = { - maxAttempts: 4, - baseDelayMs: 500, - maxDelayMs: 8000 - }; - - // Verify backoff values stay within bounds - for (let attempt = 0; attempt < config.maxAttempts; attempt++) { - const delay = Math.min( - config.baseDelayMs * Math.pow(2, attempt), - config.maxDelayMs - ); - expect(delay).toBeLessThanOrEqual(config.maxDelayMs); - expect(delay).toBeGreaterThanOrEqual(config.baseDelayMs); - } - }); - }); - - describe('HealthCheckConfig', () => { - it('should accept a complete health check configuration', () => { - const config: HealthCheckConfig = { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }; - - expect(config.intervalMs).toBe(30000); - expect(config.timeoutMs).toBe(5000); - expect(config.method).toBe('eth_blockNumber'); - }); - - it('should support different health check methods', () => { - const ethBlockNumber: HealthCheckConfig = { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }; - - const ethChainId: HealthCheckConfig = { - intervalMs: 60000, - timeoutMs: 3000, - method: 'eth_chainId' - }; - - expect(ethBlockNumber.method).not.toBe(ethChainId.method); - }); - }); - - describe('RpcProviderConfig', () => { - it('should accept a complete multi-chain configuration', () => { - const config: RpcProviderConfig = { - chains: { - '1': { - chainId: 1, - chainName: 'Ethereum Mainnet', - endpoints: [ - { - url: 'https://eth-mainnet.g.alchemy.com/v2/key', - provider: 'Alchemy', - role: RpcProviderRole.PRIMARY, - type: RpcProviderType.MANAGED, - rateLimitRps: 100, - timeoutMs: 5000 - } - ], - totalOperationTimeoutMs: 30000, - cacheStaleAcceptanceMs: 60000 - }, - '137': { - chainId: 137, - chainName: 'Polygon PoS', - endpoints: [ - { - url: 'https://polygon-mainnet.g.alchemy.com/v2/key', - provider: 'Alchemy', - role: RpcProviderRole.PRIMARY, - type: RpcProviderType.MANAGED, - rateLimitRps: 100, - timeoutMs: 5000 - } - ], - totalOperationTimeoutMs: 25000, - cacheStaleAcceptanceMs: 45000 - } - }, - circuitBreaker: { - failureThreshold: 5, - openDurationMs: 30000, - halfOpenMaxAttempts: 2, - monitorWindowMs: 60000 - }, - retry: { - maxAttempts: 3, - baseDelayMs: 1000, - maxDelayMs: 10000 - }, - healthCheck: { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }, - privacy: { - rotateWithinTier: false, - privacyMode: false, - queryJitterMs: 0 - } - }; - - expect(Object.keys(config.chains)).toHaveLength(2); - expect(config.chains['1'].chainName).toBe('Ethereum Mainnet'); - expect(config.chains['137'].chainName).toBe('Polygon PoS'); - expect(config.circuitBreaker.failureThreshold).toBe(5); - expect(config.retry.maxAttempts).toBe(3); - expect(config.healthCheck.method).toBe('eth_blockNumber'); - }); - - it('should support chain lookup by string chain ID', () => { - const config: RpcProviderConfig = { - chains: { - '1': { - chainId: 1, - chainName: 'Ethereum Mainnet', - endpoints: [], - totalOperationTimeoutMs: 30000, - cacheStaleAcceptanceMs: 60000 - } - }, - circuitBreaker: { - failureThreshold: 5, - openDurationMs: 30000, - halfOpenMaxAttempts: 2, - monitorWindowMs: 60000 - }, - retry: { - maxAttempts: 3, - baseDelayMs: 1000, - maxDelayMs: 10000 - }, - healthCheck: { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }, - privacy: { - rotateWithinTier: false, - privacyMode: false, - queryJitterMs: 0 - } - }; - - // Lookup by numeric chain ID converted to string - const chainId = 1; - const chainConfig = config.chains[String(chainId)]; - expect(chainConfig).toBeDefined(); - expect(chainConfig.chainId).toBe(1); - }); - - it('should be fully JSON serializable', () => { - const config: RpcProviderConfig = { - chains: { - '1': { - chainId: 1, - chainName: 'Ethereum Mainnet', - endpoints: [ - { - url: 'https://eth.example.com', - provider: 'Test', - role: RpcProviderRole.PRIMARY, - type: RpcProviderType.MANAGED, - rateLimitRps: 50, - timeoutMs: 5000 - } - ], - totalOperationTimeoutMs: 30000, - cacheStaleAcceptanceMs: 60000 - } - }, - circuitBreaker: { - failureThreshold: 5, - openDurationMs: 30000, - halfOpenMaxAttempts: 2, - monitorWindowMs: 60000 - }, - retry: { - maxAttempts: 3, - baseDelayMs: 1000, - maxDelayMs: 10000 - }, - healthCheck: { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }, - privacy: { - rotateWithinTier: false, - privacyMode: false, - queryJitterMs: 0 - } - }; - - const json = JSON.stringify(config); - const parsed = JSON.parse(json); - - expect(parsed.chains['1'].chainId).toBe(1); - expect(parsed.chains['1'].endpoints[0].role).toBe('PRIMARY'); - expect(parsed.circuitBreaker.failureThreshold).toBe(5); - expect(parsed.retry.maxAttempts).toBe(3); - expect(parsed.healthCheck.method).toBe('eth_blockNumber'); - }); - }); - - describe('UserRpcEndpoint', () => { - it('should accept a fully specified user endpoint', () => { - const endpoint: UserRpcEndpoint = { - chainId: '1', - url: 'https://my-node.example.com/rpc', - wsUrl: 'wss://my-node.example.com/ws', - label: 'My Private Node' - }; - - expect(endpoint.chainId).toBe('1'); - expect(endpoint.url).toBe('https://my-node.example.com/rpc'); - expect(endpoint.wsUrl).toBe('wss://my-node.example.com/ws'); - expect(endpoint.label).toBe('My Private Node'); - }); - - it('should accept an endpoint without optional fields', () => { - const endpoint: UserRpcEndpoint = { - chainId: '137', - url: 'https://polygon-node.example.com/rpc' - }; - - expect(endpoint.chainId).toBe('137'); - expect(endpoint.url).toBe('https://polygon-node.example.com/rpc'); - expect(endpoint.wsUrl).toBeUndefined(); - expect(endpoint.label).toBeUndefined(); - }); - - it('should be JSON serializable', () => { - const endpoint: UserRpcEndpoint = { - chainId: '42161', - url: 'https://arb-node.example.com/rpc', - label: 'Arbitrum Node' - }; - - const json = JSON.stringify(endpoint); - const parsed = JSON.parse(json); - - expect(parsed).toEqual(endpoint); - }); - }); - - describe('UserRpcConfig', () => { - it('should accept override mode config', () => { - const config: UserRpcConfig = { - endpoints: [ - { chainId: '1', url: 'https://my-eth.example.com/rpc' } - ], - mode: 'override' - }; - - expect(config.endpoints).toHaveLength(1); - expect(config.mode).toBe('override'); - }); - - it('should accept prepend mode config', () => { - const config: UserRpcConfig = { - endpoints: [ - { chainId: '1', url: 'https://primary.example.com/rpc', label: 'Primary' }, - { chainId: '1', url: 'https://backup.example.com/rpc', label: 'Backup' } - ], - mode: 'prepend' - }; - - expect(config.endpoints).toHaveLength(2); - expect(config.mode).toBe('prepend'); - }); - - it('should accept multi-chain user endpoints', () => { - const config: UserRpcConfig = { - endpoints: [ - { chainId: '1', url: 'https://eth.example.com/rpc' }, - { chainId: '137', url: 'https://polygon.example.com/rpc' }, - { chainId: '42161', url: 'https://arb.example.com/rpc' } - ], - mode: 'prepend' - }; - - expect(config.endpoints).toHaveLength(3); - const chainIds = config.endpoints.map(e => e.chainId); - expect(chainIds).toEqual(['1', '137', '42161']); - }); - - it('should be JSON serializable', () => { - const config: UserRpcConfig = { - endpoints: [ - { chainId: '1', url: 'https://eth.example.com/rpc', label: 'My ETH' } - ], - mode: 'override' - }; - - const json = JSON.stringify(config); - const parsed = JSON.parse(json); - - expect(parsed).toEqual(config); - }); - }); - - describe('PrivacyConfig', () => { - it('should accept a complete privacy configuration', () => { - const config: PrivacyConfig = { - rotateWithinTier: true, - privacyMode: true, - queryJitterMs: 150 - }; - - expect(config.rotateWithinTier).toBe(true); - expect(config.privacyMode).toBe(true); - expect(config.queryJitterMs).toBe(150); - }); - - it('should accept privacy-disabled configuration', () => { - const config: PrivacyConfig = { - rotateWithinTier: false, - privacyMode: false, - queryJitterMs: 0 - }; - - expect(config.rotateWithinTier).toBe(false); - expect(config.privacyMode).toBe(false); - expect(config.queryJitterMs).toBe(0); - }); - - it('should be JSON serializable', () => { - const config: PrivacyConfig = { - rotateWithinTier: true, - privacyMode: false, - queryJitterMs: 100 - }; - - const json = JSON.stringify(config); - const parsed = JSON.parse(json); - - expect(parsed).toEqual(config); - }); - }); - - describe('RpcProviderConfig (extended with privacy and userOverrides)', () => { - it('should accept config with privacy field', () => { - const config: RpcProviderConfig = { - chains: { - '1': { - chainId: 1, - chainName: 'Ethereum Mainnet', - endpoints: [], - totalOperationTimeoutMs: 30000, - cacheStaleAcceptanceMs: 60000 - } - }, - circuitBreaker: { - failureThreshold: 5, - openDurationMs: 30000, - halfOpenMaxAttempts: 2, - monitorWindowMs: 60000 - }, - retry: { - maxAttempts: 3, - baseDelayMs: 1000, - maxDelayMs: 10000 - }, - healthCheck: { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }, - privacy: { - rotateWithinTier: true, - privacyMode: true, - queryJitterMs: 150 - } - }; - - expect(config.privacy).toBeDefined(); - expect(config.privacy.rotateWithinTier).toBe(true); - expect(config.privacy.privacyMode).toBe(true); - expect(config.privacy.queryJitterMs).toBe(150); - }); - - it('should accept config with userOverrides field', () => { - const config: RpcProviderConfig = { - chains: { - '1': { - chainId: 1, - chainName: 'Ethereum Mainnet', - endpoints: [], - totalOperationTimeoutMs: 30000, - cacheStaleAcceptanceMs: 60000 - } - }, - circuitBreaker: { - failureThreshold: 5, - openDurationMs: 30000, - halfOpenMaxAttempts: 2, - monitorWindowMs: 60000 - }, - retry: { - maxAttempts: 3, - baseDelayMs: 1000, - maxDelayMs: 10000 - }, - healthCheck: { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }, - privacy: { - rotateWithinTier: false, - privacyMode: false, - queryJitterMs: 0 - }, - userOverrides: { - endpoints: [ - { chainId: '1', url: 'https://my-eth.example.com/rpc', label: 'My Node' } - ], - mode: 'prepend' - } - }; - - expect(config.userOverrides).toBeDefined(); - expect(config.userOverrides!.endpoints).toHaveLength(1); - expect(config.userOverrides!.mode).toBe('prepend'); - }); - - it('should accept config without optional userOverrides', () => { - const config: RpcProviderConfig = { - chains: {}, - circuitBreaker: { - failureThreshold: 5, - openDurationMs: 30000, - halfOpenMaxAttempts: 2, - monitorWindowMs: 60000 - }, - retry: { - maxAttempts: 3, - baseDelayMs: 1000, - maxDelayMs: 10000 - }, - healthCheck: { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }, - privacy: { - rotateWithinTier: false, - privacyMode: false, - queryJitterMs: 0 - } - }; - - expect(config.userOverrides).toBeUndefined(); - }); - - it('should serialize extended config to JSON', () => { - const config: RpcProviderConfig = { - chains: {}, - circuitBreaker: { - failureThreshold: 5, - openDurationMs: 30000, - halfOpenMaxAttempts: 2, - monitorWindowMs: 60000 - }, - retry: { - maxAttempts: 3, - baseDelayMs: 1000, - maxDelayMs: 10000 - }, - healthCheck: { - intervalMs: 30000, - timeoutMs: 5000, - method: 'eth_blockNumber' - }, - privacy: { - rotateWithinTier: true, - privacyMode: true, - queryJitterMs: 200 - }, - userOverrides: { - endpoints: [ - { chainId: '1', url: 'https://my-node.example.com/rpc' } - ], - mode: 'override' - } - }; - - const json = JSON.stringify(config); - const parsed = JSON.parse(json); - - expect(parsed.privacy.rotateWithinTier).toBe(true); - expect(parsed.privacy.queryJitterMs).toBe(200); - expect(parsed.userOverrides.mode).toBe('override'); - expect(parsed.userOverrides.endpoints[0].chainId).toBe('1'); - }); - }); - - describe('Contract Tests (Breaking Change Detection)', () => { - it('should not remove RpcProviderRole values', () => { - const coreRoles = ['PRIMARY', 'SECONDARY', 'TERTIARY', 'EMERGENCY']; - const values = Object.values(RpcProviderRole); - coreRoles.forEach(role => { - expect(values).toContain(role); - }); - }); - - it('should not remove RpcProviderType values', () => { - const coreTypes = ['MANAGED', 'PUBLIC', 'COMMUNITY', 'DECENTRALIZED', 'USER']; - const values = Object.values(RpcProviderType); - coreTypes.forEach(type => { - expect(values).toContain(type); - }); - }); - }); -});