From 38b6ecce32aaaebabb7e653f262b9c1783f615ac Mon Sep 17 00:00:00 2001 From: Tobiloba Date: Thu, 28 May 2026 10:08:10 +0100 Subject: [PATCH] feat: improve StellarLend protocol documentation and developer setup guide --- api/src/controllers/debtToken.controller.ts | 18 +- api/src/controllers/lending.controller.ts | 24 +- api/src/controllers/portfolio.controller.ts | 4 +- api/src/controllers/rebalancing.controller.ts | 6 +- api/src/types/index.ts | 19 +- oracle/dist/claims/claim-repository.d.ts | 62 ++++ oracle/dist/claims/claim-repository.d.ts.map | 1 + oracle/dist/claims/claim-repository.js | 158 ++++++++ oracle/dist/claims/claim-repository.js.map | 1 + oracle/dist/claims/claim-verifier.d.ts | 37 ++ oracle/dist/claims/claim-verifier.d.ts.map | 1 + oracle/dist/claims/claim-verifier.js | 156 ++++++++ oracle/dist/claims/claim-verifier.js.map | 1 + oracle/dist/claims/claims-service.d.ts | 85 +++++ oracle/dist/claims/claims-service.d.ts.map | 1 + oracle/dist/claims/claims-service.js | 282 +++++++++++++++ oracle/dist/claims/claims-service.js.map | 1 + oracle/dist/claims/dispute-manager.d.ts | 60 ++++ oracle/dist/claims/dispute-manager.d.ts.map | 1 + oracle/dist/claims/dispute-manager.js | 144 ++++++++ oracle/dist/claims/dispute-manager.js.map | 1 + oracle/dist/claims/fraud-detector.d.ts | 66 ++++ oracle/dist/claims/fraud-detector.d.ts.map | 1 + oracle/dist/claims/fraud-detector.js | 152 ++++++++ oracle/dist/claims/fraud-detector.js.map | 1 + oracle/dist/claims/index.d.ts | 11 + oracle/dist/claims/index.d.ts.map | 1 + oracle/dist/claims/index.js | 11 + oracle/dist/claims/index.js.map | 1 + oracle/dist/claims/payout-calculator.d.ts | 62 ++++ oracle/dist/claims/payout-calculator.d.ts.map | 1 + oracle/dist/claims/payout-calculator.js | 99 +++++ oracle/dist/claims/payout-calculator.js.map | 1 + oracle/dist/claims/types.d.ts | 310 ++++++++++++++++ oracle/dist/claims/types.d.ts.map | 1 + oracle/dist/claims/types.js | 91 +++++ oracle/dist/claims/types.js.map | 1 + oracle/dist/config.d.ts | 38 ++ oracle/dist/config.d.ts.map | 1 + oracle/dist/config.js | 272 ++++++++++++++ oracle/dist/config.js.map | 1 + oracle/dist/config/development.d.ts | 3 + oracle/dist/config/development.d.ts.map | 1 + oracle/dist/config/development.js | 13 + oracle/dist/config/development.js.map | 1 + oracle/dist/config/production.d.ts | 3 + oracle/dist/config/production.d.ts.map | 1 + oracle/dist/config/production.js | 13 + oracle/dist/config/production.js.map | 1 + oracle/dist/devtools/trace-analysis.d.ts | 71 ++++ oracle/dist/devtools/trace-analysis.d.ts.map | 1 + oracle/dist/devtools/trace-analysis.js | 109 ++++++ oracle/dist/devtools/trace-analysis.js.map | 1 + oracle/dist/devtools/trace-report.d.ts | 2 + oracle/dist/devtools/trace-report.d.ts.map | 1 + oracle/dist/devtools/trace-report.js | 20 ++ oracle/dist/devtools/trace-report.js.map | 1 + oracle/dist/index.d.ts | 92 +++++ oracle/dist/index.d.ts.map | 1 + oracle/dist/index.js | 271 ++++++++++++++ oracle/dist/index.js.map | 1 + oracle/dist/providers/base-provider.d.ts | 62 ++++ oracle/dist/providers/base-provider.d.ts.map | 1 + oracle/dist/providers/base-provider.js | 154 ++++++++ oracle/dist/providers/base-provider.js.map | 1 + oracle/dist/providers/binance.d.ts | 38 ++ oracle/dist/providers/binance.d.ts.map | 1 + oracle/dist/providers/binance.js | 145 ++++++++ oracle/dist/providers/binance.js.map | 1 + oracle/dist/providers/coingecko.d.ts | 52 +++ oracle/dist/providers/coingecko.d.ts.map | 1 + oracle/dist/providers/coingecko.js | 185 ++++++++++ oracle/dist/providers/coingecko.js.map | 1 + oracle/dist/providers/index.d.ts | 7 + oracle/dist/providers/index.d.ts.map | 1 + oracle/dist/providers/index.js | 7 + oracle/dist/providers/index.js.map | 1 + oracle/dist/security/monitoring-service.d.ts | 7 + .../dist/security/monitoring-service.d.ts.map | 1 + oracle/dist/security/monitoring-service.js | 23 ++ .../dist/security/monitoring-service.js.map | 1 + .../scenarios/liquidation-cascade.d.ts | 6 + .../scenarios/liquidation-cascade.d.ts.map | 1 + .../security/scenarios/liquidation-cascade.js | 23 ++ .../scenarios/liquidation-cascade.js.map | 1 + .../scenarios/price-manipulation.d.ts | 6 + .../scenarios/price-manipulation.d.ts.map | 1 + .../security/scenarios/price-manipulation.js | 24 ++ .../scenarios/price-manipulation.js.map | 1 + oracle/dist/security/simulation-engine.d.ts | 8 + .../dist/security/simulation-engine.d.ts.map | 1 + oracle/dist/security/simulation-engine.js | 34 ++ oracle/dist/security/simulation-engine.js.map | 1 + oracle/dist/security/types.d.ts | 35 ++ oracle/dist/security/types.d.ts.map | 1 + oracle/dist/security/types.js | 8 + oracle/dist/security/types.js.map | 1 + oracle/dist/services/cache.d.ts | 125 +++++++ oracle/dist/services/cache.d.ts.map | 1 + oracle/dist/services/cache.js | 340 ++++++++++++++++++ oracle/dist/services/cache.js.map | 1 + oracle/dist/services/circuit-breaker.d.ts | 92 +++++ oracle/dist/services/circuit-breaker.d.ts.map | 1 + oracle/dist/services/circuit-breaker.js | 134 +++++++ oracle/dist/services/circuit-breaker.js.map | 1 + oracle/dist/services/contract-updater.d.ts | 71 ++++ .../dist/services/contract-updater.d.ts.map | 1 + oracle/dist/services/contract-updater.js | 257 +++++++++++++ oracle/dist/services/contract-updater.js.map | 1 + oracle/dist/services/index.d.ts | 18 + oracle/dist/services/index.d.ts.map | 1 + oracle/dist/services/index.js | 12 + oracle/dist/services/index.js.map | 1 + oracle/dist/services/price-aggregator.d.ts | 104 ++++++ .../dist/services/price-aggregator.d.ts.map | 1 + oracle/dist/services/price-aggregator.js | 266 ++++++++++++++ oracle/dist/services/price-aggregator.js.map | 1 + oracle/dist/services/price-history.d.ts | 110 ++++++ oracle/dist/services/price-history.d.ts.map | 1 + oracle/dist/services/price-history.js | 252 +++++++++++++ oracle/dist/services/price-history.js.map | 1 + oracle/dist/services/price-validator.d.ts | 54 +++ oracle/dist/services/price-validator.d.ts.map | 1 + oracle/dist/services/price-validator.js | 159 ++++++++ oracle/dist/services/price-validator.js.map | 1 + oracle/dist/types/index.d.ts | 155 ++++++++ oracle/dist/types/index.d.ts.map | 1 + oracle/dist/types/index.js | 19 + oracle/dist/types/index.js.map | 1 + oracle/dist/utils/logger.d.ts | 32 ++ oracle/dist/utils/logger.d.ts.map | 1 + oracle/dist/utils/logger.js | 97 +++++ oracle/dist/utils/logger.js.map | 1 + 133 files changed, 5938 insertions(+), 11 deletions(-) create mode 100644 oracle/dist/claims/claim-repository.d.ts create mode 100644 oracle/dist/claims/claim-repository.d.ts.map create mode 100644 oracle/dist/claims/claim-repository.js create mode 100644 oracle/dist/claims/claim-repository.js.map create mode 100644 oracle/dist/claims/claim-verifier.d.ts create mode 100644 oracle/dist/claims/claim-verifier.d.ts.map create mode 100644 oracle/dist/claims/claim-verifier.js create mode 100644 oracle/dist/claims/claim-verifier.js.map create mode 100644 oracle/dist/claims/claims-service.d.ts create mode 100644 oracle/dist/claims/claims-service.d.ts.map create mode 100644 oracle/dist/claims/claims-service.js create mode 100644 oracle/dist/claims/claims-service.js.map create mode 100644 oracle/dist/claims/dispute-manager.d.ts create mode 100644 oracle/dist/claims/dispute-manager.d.ts.map create mode 100644 oracle/dist/claims/dispute-manager.js create mode 100644 oracle/dist/claims/dispute-manager.js.map create mode 100644 oracle/dist/claims/fraud-detector.d.ts create mode 100644 oracle/dist/claims/fraud-detector.d.ts.map create mode 100644 oracle/dist/claims/fraud-detector.js create mode 100644 oracle/dist/claims/fraud-detector.js.map create mode 100644 oracle/dist/claims/index.d.ts create mode 100644 oracle/dist/claims/index.d.ts.map create mode 100644 oracle/dist/claims/index.js create mode 100644 oracle/dist/claims/index.js.map create mode 100644 oracle/dist/claims/payout-calculator.d.ts create mode 100644 oracle/dist/claims/payout-calculator.d.ts.map create mode 100644 oracle/dist/claims/payout-calculator.js create mode 100644 oracle/dist/claims/payout-calculator.js.map create mode 100644 oracle/dist/claims/types.d.ts create mode 100644 oracle/dist/claims/types.d.ts.map create mode 100644 oracle/dist/claims/types.js create mode 100644 oracle/dist/claims/types.js.map create mode 100644 oracle/dist/config.d.ts create mode 100644 oracle/dist/config.d.ts.map create mode 100644 oracle/dist/config.js create mode 100644 oracle/dist/config.js.map create mode 100644 oracle/dist/config/development.d.ts create mode 100644 oracle/dist/config/development.d.ts.map create mode 100644 oracle/dist/config/development.js create mode 100644 oracle/dist/config/development.js.map create mode 100644 oracle/dist/config/production.d.ts create mode 100644 oracle/dist/config/production.d.ts.map create mode 100644 oracle/dist/config/production.js create mode 100644 oracle/dist/config/production.js.map create mode 100644 oracle/dist/devtools/trace-analysis.d.ts create mode 100644 oracle/dist/devtools/trace-analysis.d.ts.map create mode 100644 oracle/dist/devtools/trace-analysis.js create mode 100644 oracle/dist/devtools/trace-analysis.js.map create mode 100644 oracle/dist/devtools/trace-report.d.ts create mode 100644 oracle/dist/devtools/trace-report.d.ts.map create mode 100644 oracle/dist/devtools/trace-report.js create mode 100644 oracle/dist/devtools/trace-report.js.map create mode 100644 oracle/dist/index.d.ts create mode 100644 oracle/dist/index.d.ts.map create mode 100644 oracle/dist/index.js create mode 100644 oracle/dist/index.js.map create mode 100644 oracle/dist/providers/base-provider.d.ts create mode 100644 oracle/dist/providers/base-provider.d.ts.map create mode 100644 oracle/dist/providers/base-provider.js create mode 100644 oracle/dist/providers/base-provider.js.map create mode 100644 oracle/dist/providers/binance.d.ts create mode 100644 oracle/dist/providers/binance.d.ts.map create mode 100644 oracle/dist/providers/binance.js create mode 100644 oracle/dist/providers/binance.js.map create mode 100644 oracle/dist/providers/coingecko.d.ts create mode 100644 oracle/dist/providers/coingecko.d.ts.map create mode 100644 oracle/dist/providers/coingecko.js create mode 100644 oracle/dist/providers/coingecko.js.map create mode 100644 oracle/dist/providers/index.d.ts create mode 100644 oracle/dist/providers/index.d.ts.map create mode 100644 oracle/dist/providers/index.js create mode 100644 oracle/dist/providers/index.js.map create mode 100644 oracle/dist/security/monitoring-service.d.ts create mode 100644 oracle/dist/security/monitoring-service.d.ts.map create mode 100644 oracle/dist/security/monitoring-service.js create mode 100644 oracle/dist/security/monitoring-service.js.map create mode 100644 oracle/dist/security/scenarios/liquidation-cascade.d.ts create mode 100644 oracle/dist/security/scenarios/liquidation-cascade.d.ts.map create mode 100644 oracle/dist/security/scenarios/liquidation-cascade.js create mode 100644 oracle/dist/security/scenarios/liquidation-cascade.js.map create mode 100644 oracle/dist/security/scenarios/price-manipulation.d.ts create mode 100644 oracle/dist/security/scenarios/price-manipulation.d.ts.map create mode 100644 oracle/dist/security/scenarios/price-manipulation.js create mode 100644 oracle/dist/security/scenarios/price-manipulation.js.map create mode 100644 oracle/dist/security/simulation-engine.d.ts create mode 100644 oracle/dist/security/simulation-engine.d.ts.map create mode 100644 oracle/dist/security/simulation-engine.js create mode 100644 oracle/dist/security/simulation-engine.js.map create mode 100644 oracle/dist/security/types.d.ts create mode 100644 oracle/dist/security/types.d.ts.map create mode 100644 oracle/dist/security/types.js create mode 100644 oracle/dist/security/types.js.map create mode 100644 oracle/dist/services/cache.d.ts create mode 100644 oracle/dist/services/cache.d.ts.map create mode 100644 oracle/dist/services/cache.js create mode 100644 oracle/dist/services/cache.js.map create mode 100644 oracle/dist/services/circuit-breaker.d.ts create mode 100644 oracle/dist/services/circuit-breaker.d.ts.map create mode 100644 oracle/dist/services/circuit-breaker.js create mode 100644 oracle/dist/services/circuit-breaker.js.map create mode 100644 oracle/dist/services/contract-updater.d.ts create mode 100644 oracle/dist/services/contract-updater.d.ts.map create mode 100644 oracle/dist/services/contract-updater.js create mode 100644 oracle/dist/services/contract-updater.js.map create mode 100644 oracle/dist/services/index.d.ts create mode 100644 oracle/dist/services/index.d.ts.map create mode 100644 oracle/dist/services/index.js create mode 100644 oracle/dist/services/index.js.map create mode 100644 oracle/dist/services/price-aggregator.d.ts create mode 100644 oracle/dist/services/price-aggregator.d.ts.map create mode 100644 oracle/dist/services/price-aggregator.js create mode 100644 oracle/dist/services/price-aggregator.js.map create mode 100644 oracle/dist/services/price-history.d.ts create mode 100644 oracle/dist/services/price-history.d.ts.map create mode 100644 oracle/dist/services/price-history.js create mode 100644 oracle/dist/services/price-history.js.map create mode 100644 oracle/dist/services/price-validator.d.ts create mode 100644 oracle/dist/services/price-validator.d.ts.map create mode 100644 oracle/dist/services/price-validator.js create mode 100644 oracle/dist/services/price-validator.js.map create mode 100644 oracle/dist/types/index.d.ts create mode 100644 oracle/dist/types/index.d.ts.map create mode 100644 oracle/dist/types/index.js create mode 100644 oracle/dist/types/index.js.map create mode 100644 oracle/dist/utils/logger.d.ts create mode 100644 oracle/dist/utils/logger.d.ts.map create mode 100644 oracle/dist/utils/logger.js create mode 100644 oracle/dist/utils/logger.js.map diff --git a/api/src/controllers/debtToken.controller.ts b/api/src/controllers/debtToken.controller.ts index 36da768d..8930bacb 100644 --- a/api/src/controllers/debtToken.controller.ts +++ b/api/src/controllers/debtToken.controller.ts @@ -107,7 +107,11 @@ export const burnDebtToken = async (req: Request, res: Response, next: NextFunct } }; -export const getDebtPosition = async (req: Request, res: Response) => { +export const getDebtPosition = async ( + req: Request, + res: Response, + next: NextFunction +) => { try { const { tokenId } = req.query as any; @@ -135,7 +139,11 @@ export const getDebtPosition = async (req: Request, res: Response) => { } }; -export const getUserDebtTokens = async (req: Request, res: Response) => { +export const getUserDebtTokens = async ( + req: Request, + res: Response, + next: NextFunction +) => { try { const { userAddress } = req.query as any; @@ -158,7 +166,11 @@ export const getUserDebtTokens = async (req: Request, res: Response) => { } }; -export const getDebtTokenTotalSupply = async (req: Request, res: Response) => { +export const getDebtTokenTotalSupply = async ( + req: Request, + res: Response, + next: NextFunction +) => { try { logger.info('Get debt token total supply request'); diff --git a/api/src/controllers/lending.controller.ts b/api/src/controllers/lending.controller.ts index b41fef7e..41db9ca8 100644 --- a/api/src/controllers/lending.controller.ts +++ b/api/src/controllers/lending.controller.ts @@ -469,7 +469,11 @@ export const executeRebalancing = async (req: Request, res: Response, next: Next } }; -export const getRebalancingConfig = async (req: Request, res: Response) => { +export const getRebalancingConfig = async ( + req: Request, + res: Response, + next: NextFunction +) => { try { const { userAddress } = req.query as any; @@ -595,7 +599,11 @@ export const burnDebtToken = async (req: Request, res: Response, next: NextFunct } }; -export const getDebtPosition = async (req: Request, res: Response) => { +export const getDebtPosition = async ( + req: Request, + res: Response, + next: NextFunction + ) => { try { const { tokenId } = req.query as any; @@ -623,7 +631,11 @@ export const getDebtPosition = async (req: Request, res: Response) => { } }; -export const getUserDebtTokens = async (req: Request, res: Response) => { +export const getUserDebtTokens = async ( + req: Request, + res: Response, + next: NextFunction + ) => { try { const { userAddress } = req.query as any; @@ -646,7 +658,11 @@ export const getUserDebtTokens = async (req: Request, res: Response) => { } }; -export const getDebtTokenTotalSupply = async (req: Request, res: Response) => { +export const getDebtTokenTotalSupply = async ( + req: Request, + res: Response, + next: NextFunction + ) => { try { logger.info('Get debt token total supply request'); diff --git a/api/src/controllers/portfolio.controller.ts b/api/src/controllers/portfolio.controller.ts index a97f01fc..fc0955e4 100644 --- a/api/src/controllers/portfolio.controller.ts +++ b/api/src/controllers/portfolio.controller.ts @@ -5,7 +5,7 @@ import { toCSV, computeInterestAccrualProjection, computeLiquidationPrice, - getHealthFactorMonitor, + getHealthFactorMonitor as buildHealthFactorMonitor, } from '../services/portfolio.service'; import { PortfolioAnalyticsResponse } from '../types/portfolio'; import { redisCacheService } from '../services/redisCache.service'; @@ -194,7 +194,7 @@ export const getHealthFactorMonitor = async ( const stellarService = new StellarService(); const position = await stellarService.getUserPosition(userAddress); - const monitor = getHealthFactorMonitor(position); + const monitor = buildHealthFactorMonitor(position); res.status(200).json({ userAddress, ...monitor, diff --git a/api/src/controllers/rebalancing.controller.ts b/api/src/controllers/rebalancing.controller.ts index e53d3c59..6091875a 100644 --- a/api/src/controllers/rebalancing.controller.ts +++ b/api/src/controllers/rebalancing.controller.ts @@ -87,7 +87,11 @@ export const executeRebalancing = async (req: Request, res: Response, next: Next } }; -export const getRebalancingConfig = async (req: Request, res: Response) => { +export const getRebalancingConfig = async ( + req: Request, + res: Response, + next: NextFunction +) => { try { const { userAddress } = req.query as any; diff --git a/api/src/types/index.ts b/api/src/types/index.ts index 5f47a97c..2465acbc 100644 --- a/api/src/types/index.ts +++ b/api/src/types/index.ts @@ -138,12 +138,29 @@ export interface WsPingMessage { type: 'ping'; } -export type ClientMessage = WsSubscribeMessage | WsUnsubscribeMessage | WsPingMessage; +export interface WsAnalyticsSubscribeMessage { + type: 'subscribe_analytics'; + channels: ('apy' | 'utilization' | 'revenue')[]; +} + +export interface WsAnalyticsUnsubscribeMessage { + type: 'unsubscribe_analytics'; + channels: ('apy' | 'utilization' | 'revenue')[]; +} + +export type ClientMessage = + | WsSubscribeMessage + | WsUnsubscribeMessage + | WsPingMessage + | WsAnalyticsSubscribeMessage + | WsAnalyticsUnsubscribeMessage; export type ServerMessage = | { type: 'price_update'; asset: string; price: number; timestamp: number } | { type: 'subscribed'; assets: string[] } | { type: 'unsubscribed'; assets: string[] } + | { type: 'subscribed_analytics'; channels: string[] } + | { type: 'unsubscribed_analytics'; channels: string[] } | { type: 'pong' } | { type: 'error'; message: string }; diff --git a/oracle/dist/claims/claim-repository.d.ts b/oracle/dist/claims/claim-repository.d.ts new file mode 100644 index 00000000..9f113438 --- /dev/null +++ b/oracle/dist/claims/claim-repository.d.ts @@ -0,0 +1,62 @@ +/** + * Claim Repository + * + * In-memory store for insurance claims with LRU eviction and an + * append-only audit trail per claim. + */ +import type { InsuranceClaim, ClaimHistoryEntry, ClaimSubmissionRequest } from './types.js'; +import { ClaimStatus } from './types.js'; +/** + * Configuration for the repository. + */ +export interface ClaimRepositoryConfig { + /** Maximum number of claims to hold before evicting oldest. Default: 10_000 */ + maxEntries: number; +} +/** + * In-memory insurance claim store. + * + * Uses a Map (insertion-order preserved) to track LRU position. + * The first key in the Map is always the oldest / least-recently touched. + */ +export declare class ClaimRepository { + private store; + private config; + constructor(config?: Partial); + /** + * Create a new claim from a submission request. + * Returns the persisted claim. + */ + create(request: ClaimSubmissionRequest): InsuranceClaim; + /** + * Persist (insert or update) a claim. + */ + save(claim: InsuranceClaim): void; + /** + * Append a history entry and persist. + */ + appendHistory(claimId: string, entry: ClaimHistoryEntry): InsuranceClaim | null; + /** + * Transition a claim to a new status, recording the history entry. + */ + transition(claimId: string, toStatus: ClaimStatus, actor: string, description: string, metadata?: Record): InsuranceClaim | null; + findById(id: string): InsuranceClaim | null; + findByAddress(address: string): InsuranceClaim[]; + findByStatus(status: ClaimStatus): InsuranceClaim[]; + /** + * Return all claims submitted since a given Unix timestamp. + */ + findSince(sinceTimestamp: number): InsuranceClaim[]; + /** + * Return the full audit history for a claim. + */ + getHistory(claimId: string): ClaimHistoryEntry[]; + count(): number; + getAll(): InsuranceClaim[]; + private persist; +} +/** + * Factory function. + */ +export declare function createClaimRepository(config?: Partial): ClaimRepository; +//# sourceMappingURL=claim-repository.d.ts.map \ No newline at end of file diff --git a/oracle/dist/claims/claim-repository.d.ts.map b/oracle/dist/claims/claim-repository.d.ts.map new file mode 100644 index 00000000..4e25eb04 --- /dev/null +++ b/oracle/dist/claims/claim-repository.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"claim-repository.d.ts","sourceRoot":"","sources":["../../src/claims/claim-repository.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAGtD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,MAAM,CAAwB;gBAE1B,MAAM,GAAE,OAAO,CAAC,qBAAqB,CAAM;IAOvD;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,sBAAsB,GAAG,cAAc;IA+BvD;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAIjC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,cAAc,GAAG,IAAI;IAS/E;;OAEG;IACH,UAAU,CACR,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,WAAW,EACrB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,cAAc,GAAG,IAAI;IA6BxB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAU3C,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE;IAMhD,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,cAAc,EAAE;IAInD;;OAEG;IACH,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,cAAc,EAAE;IAMnD;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAIhD,KAAK,IAAI,MAAM;IAIf,MAAM,IAAI,cAAc,EAAE;IAM1B,OAAO,CAAC,OAAO;CAiBhB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACtC,eAAe,CAEjB"} \ No newline at end of file diff --git a/oracle/dist/claims/claim-repository.js b/oracle/dist/claims/claim-repository.js new file mode 100644 index 00000000..1d6fe80b --- /dev/null +++ b/oracle/dist/claims/claim-repository.js @@ -0,0 +1,158 @@ +/** + * Claim Repository + * + * In-memory store for insurance claims with LRU eviction and an + * append-only audit trail per claim. + */ +import { randomUUID } from 'node:crypto'; +import { ClaimStatus } from './types.js'; +import { logger } from '../utils/logger.js'; +const DEFAULT_CONFIG = { + maxEntries: 10_000, +}; +/** + * In-memory insurance claim store. + * + * Uses a Map (insertion-order preserved) to track LRU position. + * The first key in the Map is always the oldest / least-recently touched. + */ +export class ClaimRepository { + store = new Map(); + config; + constructor(config = {}) { + this.config = { ...DEFAULT_CONFIG, ...config }; + logger.info('ClaimRepository initialized', { maxEntries: this.config.maxEntries }); + } + // ── Write Operations ──────────────────────────────────────────────────────── + /** + * Create a new claim from a submission request. + * Returns the persisted claim. + */ + create(request) { + const now = Math.floor(Date.now() / 1000); + const id = randomUUID(); + const initialEntry = { + toStatus: ClaimStatus.PENDING, + actor: request.claimantAddress, + description: 'Claim submitted by claimant', + timestamp: now, + }; + const claim = { + id, + claimantAddress: request.claimantAddress, + asset: request.asset.toUpperCase(), + claimedAmount: request.claimedAmount, + coverageLimit: request.coverageLimit, + lossDescription: request.lossDescription, + lossTimestamp: request.lossTimestamp, + submittedAt: now, + coveragePurchasedAt: request.coveragePurchasedAt, + status: ClaimStatus.PENDING, + fraudSignals: [], + history: [initialEntry], + }; + this.persist(id, claim); + logger.info('Claim created', { claimId: id, asset: claim.asset, claimant: claim.claimantAddress }); + return claim; + } + /** + * Persist (insert or update) a claim. + */ + save(claim) { + this.persist(claim.id, claim); + } + /** + * Append a history entry and persist. + */ + appendHistory(claimId, entry) { + const claim = this.findById(claimId); + if (!claim) + return null; + claim.history.push(entry); + this.persist(claimId, claim); + return claim; + } + /** + * Transition a claim to a new status, recording the history entry. + */ + transition(claimId, toStatus, actor, description, metadata) { + const claim = this.findById(claimId); + if (!claim) + return null; + const entry = { + fromStatus: claim.status, + toStatus, + actor, + description, + timestamp: Math.floor(Date.now() / 1000), + metadata, + }; + claim.history.push(entry); + claim.status = toStatus; + this.persist(claimId, claim); + logger.info('Claim status transition', { + claimId, + from: entry.fromStatus, + to: toStatus, + actor, + }); + return claim; + } + // ── Read Operations ───────────────────────────────────────────────────────── + findById(id) { + const claim = this.store.get(id); + if (!claim) + return null; + // Refresh LRU position + this.store.delete(id); + this.store.set(id, claim); + return claim; + } + findByAddress(address) { + return Array.from(this.store.values()).filter((c) => c.claimantAddress === address); + } + findByStatus(status) { + return Array.from(this.store.values()).filter((c) => c.status === status); + } + /** + * Return all claims submitted since a given Unix timestamp. + */ + findSince(sinceTimestamp) { + return Array.from(this.store.values()).filter((c) => c.submittedAt >= sinceTimestamp); + } + /** + * Return the full audit history for a claim. + */ + getHistory(claimId) { + return this.findById(claimId)?.history ?? []; + } + count() { + return this.store.size; + } + getAll() { + return Array.from(this.store.values()); + } + // ── Private Helpers ───────────────────────────────────────────────────────── + persist(id, claim) { + // Evict oldest entry if at capacity + if (!this.store.has(id) && this.store.size >= this.config.maxEntries) { + const oldestKey = this.store.keys().next().value; + if (oldestKey) { + this.store.delete(oldestKey); + logger.debug('ClaimRepository: evicted oldest claim', { evicted: oldestKey }); + } + } + // Refresh LRU position for existing keys + if (this.store.has(id)) { + this.store.delete(id); + } + this.store.set(id, claim); + } +} +/** + * Factory function. + */ +export function createClaimRepository(config) { + return new ClaimRepository(config); +} +//# sourceMappingURL=claim-repository.js.map \ No newline at end of file diff --git a/oracle/dist/claims/claim-repository.js.map b/oracle/dist/claims/claim-repository.js.map new file mode 100644 index 00000000..e0eab4f9 --- /dev/null +++ b/oracle/dist/claims/claim-repository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"claim-repository.js","sourceRoot":"","sources":["../../src/claims/claim-repository.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/claims/claim-verifier.d.ts b/oracle/dist/claims/claim-verifier.d.ts new file mode 100644 index 00000000..849cda28 --- /dev/null +++ b/oracle/dist/claims/claim-verifier.d.ts @@ -0,0 +1,37 @@ +/** + * Claim Verifier + * + * Validates an insurance claim using live oracle price data. + * Checks asset support, price freshness, oracle confidence, + * amount validity, and coverage limits. + */ +import type { PriceAggregator } from '../services/price-aggregator.js'; +import type { InsuranceClaim, ClaimVerificationResult } from './types.js'; +/** + * Verifier configuration. + */ +export interface ClaimVerifierConfig { + /** Max age of oracle price in seconds before it is considered stale. Default: 300 */ + maxPriceAgeSeconds: number; + /** Oracle confidence below which LOW_ORACLE_CONFIDENCE is flagged. Default: 80 */ + minOracleConfidence: number; + /** If true, low confidence is an error (not just a warning). Default: false */ + rejectOnLowConfidence: boolean; +} +/** + * Oracle-based claim verifier. + */ +export declare class ClaimVerifier { + private aggregator; + private config; + constructor(aggregator: PriceAggregator, config?: Partial); + /** + * Verify a claim against oracle data and business rules. + */ + verify(claim: InsuranceClaim): Promise; +} +/** + * Factory function. + */ +export declare function createClaimVerifier(aggregator: PriceAggregator, config?: Partial): ClaimVerifier; +//# sourceMappingURL=claim-verifier.d.ts.map \ No newline at end of file diff --git a/oracle/dist/claims/claim-verifier.d.ts.map b/oracle/dist/claims/claim-verifier.d.ts.map new file mode 100644 index 00000000..ff8842c1 --- /dev/null +++ b/oracle/dist/claims/claim-verifier.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"claim-verifier.d.ts","sourceRoot":"","sources":["../../src/claims/claim-verifier.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAI1E;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,+EAA+E;IAC/E,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAQD;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,MAAM,CAAsB;gBAExB,UAAU,EAAE,eAAe,EAAE,MAAM,GAAE,OAAO,CAAC,mBAAmB,CAAM;IAKlF;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,uBAAuB,CAAC;CAuItE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,eAAe,EAC3B,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GACpC,aAAa,CAEf"} \ No newline at end of file diff --git a/oracle/dist/claims/claim-verifier.js b/oracle/dist/claims/claim-verifier.js new file mode 100644 index 00000000..8e02f01c --- /dev/null +++ b/oracle/dist/claims/claim-verifier.js @@ -0,0 +1,156 @@ +/** + * Claim Verifier + * + * Validates an insurance claim using live oracle price data. + * Checks asset support, price freshness, oracle confidence, + * amount validity, and coverage limits. + */ +import { VerificationErrorCode } from './types.js'; +import { logger } from '../utils/logger.js'; +const DEFAULT_CONFIG = { + maxPriceAgeSeconds: 300, + minOracleConfidence: 80, + rejectOnLowConfidence: false, +}; +/** + * Oracle-based claim verifier. + */ +export class ClaimVerifier { + aggregator; + config; + constructor(aggregator, config = {}) { + this.aggregator = aggregator; + this.config = { ...DEFAULT_CONFIG, ...config }; + } + /** + * Verify a claim against oracle data and business rules. + */ + async verify(claim) { + const errors = []; + const now = Math.floor(Date.now() / 1000); + // ── Business Rule Checks (no oracle needed) ──────────────────────────── + if (claim.claimedAmount <= 0n) { + errors.push({ + code: VerificationErrorCode.INVALID_AMOUNT, + message: 'Claimed amount must be greater than zero', + details: { claimedAmount: claim.claimedAmount.toString() }, + }); + } + if (claim.claimedAmount > claim.coverageLimit) { + errors.push({ + code: VerificationErrorCode.AMOUNT_EXCEEDS_COVERAGE, + message: 'Claimed amount exceeds coverage limit', + details: { + claimedAmount: claim.claimedAmount.toString(), + coverageLimit: claim.coverageLimit.toString(), + }, + }); + } + if (claim.lossTimestamp > now) { + errors.push({ + code: VerificationErrorCode.LOSS_TIMESTAMP_IN_FUTURE, + message: 'Loss timestamp cannot be in the future', + details: { lossTimestamp: claim.lossTimestamp, now }, + }); + } + if (claim.lossTimestamp < claim.coveragePurchasedAt) { + errors.push({ + code: VerificationErrorCode.LOSS_BEFORE_COVERAGE, + message: 'Loss occurred before coverage was purchased', + details: { + lossTimestamp: claim.lossTimestamp, + coveragePurchasedAt: claim.coveragePurchasedAt, + }, + }); + } + // Early exit on business rule failures (no need to hit oracle) + if (errors.length > 0) { + logger.warn('Claim failed business rule checks', { + claimId: claim.id, + errors: errors.map((e) => e.code), + }); + return { isValid: false, errors }; + } + // ── Oracle Verification ──────────────────────────────────────────────── + let oraclePrice; + try { + oraclePrice = await this.aggregator.getPrice(claim.asset); + } + catch (err) { + logger.error('Oracle fetch threw an exception during claim verification', { + claimId: claim.id, + asset: claim.asset, + error: err, + }); + errors.push({ + code: VerificationErrorCode.ORACLE_UNAVAILABLE, + message: 'Oracle threw an exception — cannot verify claim', + details: { error: String(err) }, + }); + return { isValid: false, errors }; + } + if (!oraclePrice) { + logger.warn('Oracle returned no price for asset', { + claimId: claim.id, + asset: claim.asset, + }); + errors.push({ + code: VerificationErrorCode.ORACLE_UNAVAILABLE, + message: `Oracle could not provide a price for asset '${claim.asset}'`, + details: { asset: claim.asset }, + }); + return { isValid: false, errors }; + } + // Check price freshness + const priceAgeSeconds = now - oraclePrice.timestamp; + if (priceAgeSeconds > this.config.maxPriceAgeSeconds) { + errors.push({ + code: VerificationErrorCode.PRICE_STALE, + message: `Oracle price is ${priceAgeSeconds}s old — exceeds max ${this.config.maxPriceAgeSeconds}s`, + details: { priceAgeSeconds, maxPriceAgeSeconds: this.config.maxPriceAgeSeconds }, + }); + } + // Check oracle confidence + const isLowConfidence = oraclePrice.confidence < this.config.minOracleConfidence; + if (isLowConfidence) { + const error = { + code: VerificationErrorCode.LOW_ORACLE_CONFIDENCE, + message: `Oracle confidence ${oraclePrice.confidence}% is below threshold ${this.config.minOracleConfidence}%`, + details: { confidence: oraclePrice.confidence, threshold: this.config.minOracleConfidence }, + }; + if (this.config.rejectOnLowConfidence) { + errors.push(error); + } + else { + logger.warn('Low oracle confidence — proceeding with discount', { + claimId: claim.id, + confidence: oraclePrice.confidence, + }); + } + } + if (errors.length > 0) { + return { isValid: false, errors }; + } + const oracleData = { + priceAtVerification: oraclePrice.price, + priceTimestamp: oraclePrice.timestamp, + confidence: oraclePrice.confidence, + sources: oraclePrice.sources.map((s) => s.source), + verifiedAt: now, + }; + logger.info('Claim verified successfully via oracle', { + claimId: claim.id, + asset: claim.asset, + price: oraclePrice.price.toString(), + confidence: oraclePrice.confidence, + }); + return { isValid: true, oracleData, errors: [] }; + } +} +/** + * Factory function. + */ +export function createClaimVerifier(aggregator, config) { + return new ClaimVerifier(aggregator, config); +} +//# sourceMappingURL=claim-verifier.js.map \ No newline at end of file diff --git a/oracle/dist/claims/claim-verifier.js.map b/oracle/dist/claims/claim-verifier.js.map new file mode 100644 index 00000000..be31b1d5 --- /dev/null +++ b/oracle/dist/claims/claim-verifier.js.map @@ -0,0 +1 @@ +{"version":3,"file":"claim-verifier.js","sourceRoot":"","sources":["../../src/claims/claim-verifier.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/claims/claims-service.d.ts b/oracle/dist/claims/claims-service.d.ts new file mode 100644 index 00000000..5aa1ee71 --- /dev/null +++ b/oracle/dist/claims/claims-service.d.ts @@ -0,0 +1,85 @@ +/** + * Claims Service + * + * Top-level facade for the insurance claims automation pipeline. + * + * Flow: + * submitClaim() + * → fraud detection + * → oracle verification (ClaimVerifier) + * → payout calculation (PayoutCalculator) + * → persistence (ClaimRepository) + * + * processPayout() — register approved payout on-chain + * openDispute() — delegate to DisputeManager + * resolveDispute() — admin override + * getClaimHistory() — full audit trail + * getStats() — aggregate metrics + */ +import type { PriceAggregator } from '../services/price-aggregator.js'; +import type { ClaimSubmissionRequest, InsuranceClaim, ClaimHistoryEntry, DisputeRecord, ClaimsStats, ClaimsServiceConfig } from './types.js'; +import { ClaimStatus, DisputeResolution } from './types.js'; +import { DisputeError } from './dispute-manager.js'; +/** + * Result of a claim submission. + */ +export interface SubmitClaimResult { + claim: InsuranceClaim; + /** true if the claim was auto-approved (oracle verified, no fraud) */ + autoApproved: boolean; + /** true if the claim was flagged as fraudulent and rejected */ + fraudRejected: boolean; + /** true if the oracle was unavailable and claim is pending manual review */ + pendingManual: boolean; +} +/** + * Insurance Claims Service. + */ +export declare class ClaimsService { + private repository; + private verifier; + private calculator; + private fraudDetector; + private disputeManager; + private config; + /** Tracks verification durations for stats. */ + private verificationDurationsMs; + constructor(aggregator: PriceAggregator, config?: Partial); + /** + * Submit a new insurance claim and run the automated verification pipeline. + */ + submitClaim(request: ClaimSubmissionRequest): Promise; + /** + * Mark an approved claim as PAID_OUT. + * In a full integration this would call ContractUpdater; here it records + * the transaction hash supplied by the caller after on-chain dispatch. + * + * @param claimId - ID of the APPROVED claim. + * @param txHash - On-chain transaction hash of the payout. + * @param adminAddress - Admin or system address initiating the payout. + */ + processPayout(claimId: string, txHash: string, adminAddress: string): InsuranceClaim; + /** + * Re-run oracle verification on a PENDING or PENDING_MANUAL claim + * (e.g. after oracle comes back online). + */ + verifyClaim(claimId: string): Promise; + openDispute(claimId: string, disputantAddress: string, reason: string, evidence?: string[]): DisputeRecord; + resolveDispute(claimId: string, resolution: DisputeResolution, adminAddress: string, resolutionNotes?: string): { + claim: InsuranceClaim; + dispute: DisputeRecord; + }; + getDispute(claimId: string): DisputeRecord | null; + getClaim(claimId: string): InsuranceClaim | null; + getClaimsByAddress(address: string): InsuranceClaim[]; + getClaimHistory(claimId: string): ClaimHistoryEntry[]; + getClaimsByStatus(status: ClaimStatus): InsuranceClaim[]; + getStats(): ClaimsStats; + private errorCodeToRejectionReason; +} +/** + * Factory function. + */ +export declare function createClaimsService(aggregator: PriceAggregator, config?: Partial): ClaimsService; +export { DisputeError }; +//# sourceMappingURL=claims-service.d.ts.map \ No newline at end of file diff --git a/oracle/dist/claims/claims-service.d.ts.map b/oracle/dist/claims/claims-service.d.ts.map new file mode 100644 index 00000000..13d669d3 --- /dev/null +++ b/oracle/dist/claims/claims-service.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"claims-service.d.ts","sourceRoot":"","sources":["../../src/claims/claims-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EACV,sBAAsB,EACtB,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,mBAAmB,EACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAmB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAiB7E,OAAO,EAGL,YAAY,EACb,MAAM,sBAAsB,CAAC;AAkB9B;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,cAAc,CAAC;IACtB,sEAAsE;IACtE,YAAY,EAAE,OAAO,CAAC;IACtB,+DAA+D;IAC/D,aAAa,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAsB;IAEpC,+CAA+C;IAC/C,OAAO,CAAC,uBAAuB,CAAgB;gBAEnC,UAAU,EAAE,eAAe,EAAE,MAAM,GAAE,OAAO,CAAC,mBAAmB,CAAM;IAgClF;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAkH9E;;;;;;;;OAQG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,cAAc;IA0BpF;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAsC3D,WAAW,CACT,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAAM,EAAO,GACtB,aAAa;IAIhB,cAAc,CACZ,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,iBAAiB,EAC7B,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,MAAM,GACvB;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,aAAa,CAAA;KAAE;IAIpD,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAMjD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAIhD,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE;IAIrD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAIrD,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,cAAc,EAAE;IAMxD,QAAQ,IAAI,WAAW;IAqCvB,OAAO,CAAC,0BAA0B;CAqBnC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,eAAe,EAC3B,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GACpC,aAAa,CAEf;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"} \ No newline at end of file diff --git a/oracle/dist/claims/claims-service.js b/oracle/dist/claims/claims-service.js new file mode 100644 index 00000000..576ce066 --- /dev/null +++ b/oracle/dist/claims/claims-service.js @@ -0,0 +1,282 @@ +/** + * Claims Service + * + * Top-level facade for the insurance claims automation pipeline. + * + * Flow: + * submitClaim() + * → fraud detection + * → oracle verification (ClaimVerifier) + * → payout calculation (PayoutCalculator) + * → persistence (ClaimRepository) + * + * processPayout() — register approved payout on-chain + * openDispute() — delegate to DisputeManager + * resolveDispute() — admin override + * getClaimHistory() — full audit trail + * getStats() — aggregate metrics + */ +import { ClaimStatus, RejectionReason } from './types.js'; +import { createClaimRepository, } from './claim-repository.js'; +import { createClaimVerifier, } from './claim-verifier.js'; +import { createPayoutCalculator, } from './payout-calculator.js'; +import { createFraudDetector, } from './fraud-detector.js'; +import { createDisputeManager, DisputeError, } from './dispute-manager.js'; +import { logger } from '../utils/logger.js'; +/** + * Default service configuration. + */ +const DEFAULT_CONFIG = { + maxClaimsInMemory: 10_000, + minOracleConfidence: 80, + maxPriceAgeSeconds: 300, + deductiblePercent: 5, + velocityThreshold: 3, + velocityWindowSeconds: 3600, + minCoverageAgeSeconds: 300, + amountAnomalyMultiplier: 5, + fallbackToPendingManual: true, +}; +/** + * Insurance Claims Service. + */ +export class ClaimsService { + repository; + verifier; + calculator; + fraudDetector; + disputeManager; + config; + /** Tracks verification durations for stats. */ + verificationDurationsMs = []; + constructor(aggregator, config = {}) { + this.config = { ...DEFAULT_CONFIG, ...config }; + this.repository = createClaimRepository({ maxEntries: this.config.maxClaimsInMemory }); + this.verifier = createClaimVerifier(aggregator, { + maxPriceAgeSeconds: this.config.maxPriceAgeSeconds, + minOracleConfidence: this.config.minOracleConfidence, + }); + this.calculator = createPayoutCalculator({ + deductiblePercent: this.config.deductiblePercent, + minOracleConfidence: this.config.minOracleConfidence, + }); + this.fraudDetector = createFraudDetector({ + velocityThreshold: this.config.velocityThreshold, + velocityWindowSeconds: this.config.velocityWindowSeconds, + minCoverageAgeSeconds: this.config.minCoverageAgeSeconds, + amountAnomalyMultiplier: this.config.amountAnomalyMultiplier, + }); + this.disputeManager = createDisputeManager(this.repository); + logger.info('ClaimsService initialized', { + maxClaimsInMemory: this.config.maxClaimsInMemory, + fallbackToPendingManual: this.config.fallbackToPendingManual, + }); + } + // ── Submit Claim ──────────────────────────────────────────────────────────── + /** + * Submit a new insurance claim and run the automated verification pipeline. + */ + async submitClaim(request) { + logger.info('Processing claim submission', { + claimant: request.claimantAddress, + asset: request.asset, + amount: request.claimedAmount.toString(), + }); + // 1. Persist the claim in PENDING state + const claim = this.repository.create(request); + // 2. Fraud detection + this.repository.transition(claim.id, ClaimStatus.VERIFYING, 'system', 'Starting fraud analysis and oracle verification'); + const allClaims = this.repository.getAll(); + const fraudResult = this.fraudDetector.detect(claim, allClaims.filter((c) => c.id !== claim.id)); + if (fraudResult.signals.length > 0) { + claim.fraudSignals = fraudResult.signals; + this.repository.save(claim); + } + if (fraudResult.isFraudulent) { + this.repository.transition(claim.id, ClaimStatus.REJECTED, 'system', `Fraud detected (risk score: ${fraudResult.riskScore})`, { signals: fraudResult.signals.map((s) => s.type), riskScore: fraudResult.riskScore }); + const updated = this.repository.findById(claim.id); + updated.rejectionReason = RejectionReason.FRAUD_DETECTED; + updated.rejectionDetails = `Fraud risk score ${fraudResult.riskScore}/100. Signals: ${fraudResult.signals.map((s) => s.type).join(', ')}`; + this.repository.save(updated); + return { claim: updated, autoApproved: false, fraudRejected: true, pendingManual: false }; + } + // 3. Oracle verification + const verifyStart = Date.now(); + const verificationResult = await this.verifier.verify(claim); + this.verificationDurationsMs.push(Date.now() - verifyStart); + const freshClaim = this.repository.findById(claim.id); + freshClaim.verificationResult = verificationResult; + this.repository.save(freshClaim); + if (!verificationResult.isValid) { + const isOracleUnavailable = verificationResult.errors.some((e) => e.code === 'ORACLE_UNAVAILABLE' || e.code === 'PRICE_STALE'); + if (isOracleUnavailable && this.config.fallbackToPendingManual) { + this.repository.transition(claim.id, ClaimStatus.PENDING_MANUAL, 'system', 'Oracle unavailable — claim requires manual review', { errors: verificationResult.errors.map((e) => e.code) }); + return { + claim: this.repository.findById(claim.id), + autoApproved: false, + fraudRejected: false, + pendingManual: true, + }; + } + // Determine rejection reason + const firstError = verificationResult.errors[0]; + const rejectionReason = this.errorCodeToRejectionReason(firstError?.code); + this.repository.transition(claim.id, ClaimStatus.REJECTED, 'system', `Verification failed: ${firstError?.message ?? 'unknown'}`, { errors: verificationResult.errors }); + const rejected = this.repository.findById(claim.id); + rejected.rejectionReason = rejectionReason; + rejected.rejectionDetails = verificationResult.errors.map((e) => e.message).join('; '); + this.repository.save(rejected); + return { claim: rejected, autoApproved: false, fraudRejected: false, pendingManual: false }; + } + // 4. Calculate payout + const payoutResult = this.calculator.calculate(freshClaim, verificationResult.oracleData); + freshClaim.payoutResult = payoutResult; + this.repository.save(freshClaim); + // 5. Auto-approve + this.repository.transition(claim.id, ClaimStatus.APPROVED, 'system', `Oracle verification passed. Payout: ${payoutResult.netPayoutAmount} ${freshClaim.asset}`, { netPayoutAmount: payoutResult.netPayoutAmount.toString() }); + return { + claim: this.repository.findById(claim.id), + autoApproved: true, + fraudRejected: false, + pendingManual: false, + }; + } + // ── Payout ────────────────────────────────────────────────────────────────── + /** + * Mark an approved claim as PAID_OUT. + * In a full integration this would call ContractUpdater; here it records + * the transaction hash supplied by the caller after on-chain dispatch. + * + * @param claimId - ID of the APPROVED claim. + * @param txHash - On-chain transaction hash of the payout. + * @param adminAddress - Admin or system address initiating the payout. + */ + processPayout(claimId, txHash, adminAddress) { + const claim = this.repository.findById(claimId); + if (!claim) + throw new Error(`Claim '${claimId}' not found`); + if (claim.status !== ClaimStatus.APPROVED) { + throw new Error(`Claim '${claimId}' must be APPROVED to process payout, got '${claim.status}'`); + } + claim.payoutTransactionHash = txHash; + this.repository.save(claim); + this.repository.transition(claimId, ClaimStatus.PAID_OUT, adminAddress, `Payout dispatched on-chain`, { txHash, netPayoutAmount: claim.payoutResult?.netPayoutAmount.toString() }); + logger.info('Payout processed', { claimId, txHash, admin: adminAddress }); + return this.repository.findById(claimId); + } + // ── Re-verification ────────────────────────────────────────────────────────── + /** + * Re-run oracle verification on a PENDING or PENDING_MANUAL claim + * (e.g. after oracle comes back online). + */ + async verifyClaim(claimId) { + const claim = this.repository.findById(claimId); + if (!claim) + throw new Error(`Claim '${claimId}' not found`); + const allowedStatuses = [ClaimStatus.PENDING, ClaimStatus.PENDING_MANUAL, ClaimStatus.VERIFYING]; + if (!allowedStatuses.includes(claim.status)) { + throw new Error(`Claim '${claimId}' cannot be re-verified in status '${claim.status}'`); + } + // Transition back to VERIFYING + this.repository.transition(claim.id, ClaimStatus.VERIFYING, 'system', 'Re-verification triggered'); + const verificationResult = await this.verifier.verify(claim); + claim.verificationResult = verificationResult; + this.repository.save(claim); + if (!verificationResult.isValid) { + if (this.config.fallbackToPendingManual) { + this.repository.transition(claim.id, ClaimStatus.PENDING_MANUAL, 'system', 'Oracle still unavailable'); + } + else { + this.repository.transition(claim.id, ClaimStatus.REJECTED, 'system', 'Re-verification failed'); + claim.rejectionReason = this.errorCodeToRejectionReason(verificationResult.errors[0]?.code); + this.repository.save(claim); + } + } + else { + const payout = this.calculator.calculate(claim, verificationResult.oracleData); + claim.payoutResult = payout; + this.repository.save(claim); + this.repository.transition(claim.id, ClaimStatus.APPROVED, 'system', 'Re-verification passed'); + } + return this.repository.findById(claimId); + } + // ── Disputes ──────────────────────────────────────────────────────────────── + openDispute(claimId, disputantAddress, reason, evidence = []) { + return this.disputeManager.openDispute(claimId, disputantAddress, reason, evidence); + } + resolveDispute(claimId, resolution, adminAddress, resolutionNotes) { + return this.disputeManager.resolveDispute(claimId, resolution, adminAddress, resolutionNotes); + } + getDispute(claimId) { + return this.disputeManager.getDispute(claimId); + } + // ── History & Query ───────────────────────────────────────────────────────── + getClaim(claimId) { + return this.repository.findById(claimId); + } + getClaimsByAddress(address) { + return this.repository.findByAddress(address); + } + getClaimHistory(claimId) { + return this.repository.getHistory(claimId); + } + getClaimsByStatus(status) { + return this.repository.findByStatus(status); + } + // ── Stats ──────────────────────────────────────────────────────────────────── + getStats() { + const all = this.repository.getAll(); + const byStatus = Object.fromEntries(Object.values(ClaimStatus).map((s) => [s, 0])); + let totalPayoutAmount = 0n; + let fraudCount = 0; + for (const c of all) { + byStatus[c.status] = (byStatus[c.status] ?? 0) + 1; + if (c.payoutResult?.netPayoutAmount) { + totalPayoutAmount += c.payoutResult.netPayoutAmount; + } + if (c.fraudSignals.length > 0) + fraudCount++; + } + const avgVerificationMs = this.verificationDurationsMs.length > 0 + ? this.verificationDurationsMs.reduce((a, b) => a + b, 0) / + this.verificationDurationsMs.length + : 0; + const disputeCount = this.disputeManager.getAllDisputes().length; + return { + totalClaims: all.length, + byStatus, + totalPayoutAmount, + fraudDetectionRate: all.length > 0 ? fraudCount / all.length : 0, + averageVerificationTimeMs: avgVerificationMs, + disputeRate: all.length > 0 ? disputeCount / all.length : 0, + }; + } + // ── Private Helpers ───────────────────────────────────────────────────────── + errorCodeToRejectionReason(code) { + switch (code) { + case 'ORACLE_UNAVAILABLE': + return RejectionReason.ORACLE_PRICE_UNAVAILABLE; + case 'PRICE_STALE': + return RejectionReason.STALE_ORACLE_PRICE; + case 'AMOUNT_EXCEEDS_COVERAGE': + return RejectionReason.INSUFFICIENT_COVERAGE; + case 'INVALID_AMOUNT': + return RejectionReason.INVALID_AMOUNT; + case 'UNSUPPORTED_ASSET': + return RejectionReason.UNSUPPORTED_ASSET; + case 'LOSS_BEFORE_COVERAGE': + case 'LOSS_TIMESTAMP_IN_FUTURE': + return RejectionReason.POLICY_NOT_ACTIVE; + default: + return RejectionReason.ORACLE_PRICE_UNAVAILABLE; + } + } +} +/** + * Factory function. + */ +export function createClaimsService(aggregator, config) { + return new ClaimsService(aggregator, config); +} +export { DisputeError }; +//# sourceMappingURL=claims-service.js.map \ No newline at end of file diff --git a/oracle/dist/claims/claims-service.js.map b/oracle/dist/claims/claims-service.js.map new file mode 100644 index 00000000..1e8b7667 --- /dev/null +++ b/oracle/dist/claims/claims-service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"claims-service.js","sourceRoot":"","sources":["../../src/claims/claims-service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/claims/dispute-manager.d.ts b/oracle/dist/claims/dispute-manager.d.ts new file mode 100644 index 00000000..9082336f --- /dev/null +++ b/oracle/dist/claims/dispute-manager.d.ts @@ -0,0 +1,60 @@ +/** + * Dispute Manager + * + * Manages the lifecycle of claim disputes: + * PENDING / APPROVED / REJECTED / PAID_OUT → DISPUTED → (APPROVED | REJECTED | ESCALATED) + * + * Rules: + * - Only claims that are not already DISPUTED, CANCELLED, or PAID_OUT can be disputed. + * - Admin resolution applies an override payout (APPROVED) or cancels payout (REJECTED). + * - Full audit trail is maintained in ClaimRepository. + */ +import type { DisputeRecord, InsuranceClaim } from './types.js'; +import { DisputeResolution } from './types.js'; +import type { ClaimRepository } from './claim-repository.js'; +/** + * Errors raised by DisputeManager. + */ +export declare class DisputeError extends Error { + readonly code: 'CLAIM_NOT_FOUND' | 'INVALID_STATUS' | 'DISPUTE_NOT_FOUND' | 'ALREADY_RESOLVED'; + constructor(message: string, code: 'CLAIM_NOT_FOUND' | 'INVALID_STATUS' | 'DISPUTE_NOT_FOUND' | 'ALREADY_RESOLVED'); +} +/** + * Dispute Manager. + */ +export declare class DisputeManager { + /** claimId → DisputeRecord */ + private disputes; + private repository; + constructor(repository: ClaimRepository); + /** + * Open a dispute on an existing claim. + * + * @param claimId - ID of the claim to dispute. + * @param disputantAddress - Stellar address opening the dispute. + * @param reason - Human-readable reason. + * @param evidence - List of evidence strings (URLs, hashes, descriptions). + */ + openDispute(claimId: string, disputantAddress: string, reason: string, evidence?: string[]): DisputeRecord; + /** + * Admin resolves a dispute. + * + * @param claimId - The disputed claim ID. + * @param resolution - APPROVED, REJECTED, or ESCALATED. + * @param adminAddress - Admin Stellar address performing resolution. + * @param resolutionNotes - Optional notes. + */ + resolveDispute(claimId: string, resolution: DisputeResolution, adminAddress: string, resolutionNotes?: string): { + claim: InsuranceClaim; + dispute: DisputeRecord; + }; + getDispute(claimId: string): DisputeRecord | null; + getAllDisputes(): DisputeRecord[]; + getOpenDisputes(): DisputeRecord[]; + private mapResolutionToStatus; +} +/** + * Factory function. + */ +export declare function createDisputeManager(repository: ClaimRepository): DisputeManager; +//# sourceMappingURL=dispute-manager.d.ts.map \ No newline at end of file diff --git a/oracle/dist/claims/dispute-manager.d.ts.map b/oracle/dist/claims/dispute-manager.d.ts.map new file mode 100644 index 00000000..62ab0903 --- /dev/null +++ b/oracle/dist/claims/dispute-manager.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"dispute-manager.d.ts","sourceRoot":"","sources":["../../src/claims/dispute-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAe,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;aAGnB,IAAI,EAChB,iBAAiB,GACjB,gBAAgB,GAChB,mBAAmB,GACnB,kBAAkB;gBALtB,OAAO,EAAE,MAAM,EACC,IAAI,EAChB,iBAAiB,GACjB,gBAAgB,GAChB,mBAAmB,GACnB,kBAAkB;CAKzB;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAyC;IACzD,OAAO,CAAC,UAAU,CAAkB;gBAExB,UAAU,EAAE,eAAe;IAMvC;;;;;;;OAOG;IACH,WAAW,CACT,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAAM,EAAO,GACtB,aAAa;IAqDhB;;;;;;;OAOG;IACH,cAAc,CACZ,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,iBAAiB,EAC7B,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,MAAM,GACvB;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,aAAa,CAAA;KAAE;IA2DpD,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAIjD,cAAc,IAAI,aAAa,EAAE;IAIjC,eAAe,IAAI,aAAa,EAAE;IAMlC,OAAO,CAAC,qBAAqB;CAU9B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,eAAe,GAAG,cAAc,CAEhF"} \ No newline at end of file diff --git a/oracle/dist/claims/dispute-manager.js b/oracle/dist/claims/dispute-manager.js new file mode 100644 index 00000000..f3a3ca0b --- /dev/null +++ b/oracle/dist/claims/dispute-manager.js @@ -0,0 +1,144 @@ +/** + * Dispute Manager + * + * Manages the lifecycle of claim disputes: + * PENDING / APPROVED / REJECTED / PAID_OUT → DISPUTED → (APPROVED | REJECTED | ESCALATED) + * + * Rules: + * - Only claims that are not already DISPUTED, CANCELLED, or PAID_OUT can be disputed. + * - Admin resolution applies an override payout (APPROVED) or cancels payout (REJECTED). + * - Full audit trail is maintained in ClaimRepository. + */ +import { ClaimStatus, DisputeResolution } from './types.js'; +import { logger } from '../utils/logger.js'; +/** + * Errors raised by DisputeManager. + */ +export class DisputeError extends Error { + code; + constructor(message, code) { + super(message); + this.code = code; + this.name = 'DisputeError'; + } +} +/** + * Dispute Manager. + */ +export class DisputeManager { + /** claimId → DisputeRecord */ + disputes = new Map(); + repository; + constructor(repository) { + this.repository = repository; + } + // ── Open a Dispute ────────────────────────────────────────────────────────── + /** + * Open a dispute on an existing claim. + * + * @param claimId - ID of the claim to dispute. + * @param disputantAddress - Stellar address opening the dispute. + * @param reason - Human-readable reason. + * @param evidence - List of evidence strings (URLs, hashes, descriptions). + */ + openDispute(claimId, disputantAddress, reason, evidence = []) { + const claim = this.repository.findById(claimId); + if (!claim) { + throw new DisputeError(`Claim '${claimId}' not found`, 'CLAIM_NOT_FOUND'); + } + const nonDisputeableStatuses = [ + ClaimStatus.DISPUTED, + ClaimStatus.CANCELLED, + ClaimStatus.PAID_OUT, + ]; + if (nonDisputeableStatuses.includes(claim.status)) { + throw new DisputeError(`Claim '${claimId}' is in status '${claim.status}' and cannot be disputed`, 'INVALID_STATUS'); + } + if (this.disputes.has(claimId)) { + throw new DisputeError(`A dispute is already open for claim '${claimId}'`, 'INVALID_STATUS'); + } + const now = Math.floor(Date.now() / 1000); + const record = { + claimId, + disputantAddress, + reason, + evidence, + openedAt: now, + }; + this.disputes.set(claimId, record); + this.repository.transition(claimId, ClaimStatus.DISPUTED, disputantAddress, `Dispute opened: ${reason}`, { evidence }); + logger.info('Dispute opened', { claimId, disputant: disputantAddress, reason }); + return record; + } + // ── Resolve a Dispute ─────────────────────────────────────────────────────── + /** + * Admin resolves a dispute. + * + * @param claimId - The disputed claim ID. + * @param resolution - APPROVED, REJECTED, or ESCALATED. + * @param adminAddress - Admin Stellar address performing resolution. + * @param resolutionNotes - Optional notes. + */ + resolveDispute(claimId, resolution, adminAddress, resolutionNotes) { + const claim = this.repository.findById(claimId); + if (!claim) { + throw new DisputeError(`Claim '${claimId}' not found`, 'CLAIM_NOT_FOUND'); + } + const dispute = this.disputes.get(claimId); + if (!dispute) { + throw new DisputeError(`No dispute found for claim '${claimId}'`, 'DISPUTE_NOT_FOUND'); + } + if (dispute.resolution !== undefined) { + throw new DisputeError(`Dispute for claim '${claimId}' has already been resolved`, 'ALREADY_RESOLVED'); + } + if (claim.status !== ClaimStatus.DISPUTED) { + throw new DisputeError(`Claim '${claimId}' is not in DISPUTED status`, 'INVALID_STATUS'); + } + const now = Math.floor(Date.now() / 1000); + dispute.resolution = resolution; + dispute.resolvedBy = adminAddress; + dispute.resolutionNotes = resolutionNotes; + dispute.resolvedAt = now; + // Map dispute resolution to claim status + const newStatus = this.mapResolutionToStatus(resolution); + const updatedClaim = this.repository.transition(claimId, newStatus, adminAddress, `Dispute resolved: ${resolution}${resolutionNotes ? ` — ${resolutionNotes}` : ''}`, { resolution, admin: adminAddress }); + if (!updatedClaim) { + throw new DisputeError(`Failed to update claim '${claimId}'`, 'CLAIM_NOT_FOUND'); + } + logger.info('Dispute resolved', { + claimId, + resolution, + admin: adminAddress, + newStatus, + }); + return { claim: updatedClaim, dispute }; + } + // ── Query ─────────────────────────────────────────────────────────────────── + getDispute(claimId) { + return this.disputes.get(claimId) ?? null; + } + getAllDisputes() { + return Array.from(this.disputes.values()); + } + getOpenDisputes() { + return Array.from(this.disputes.values()).filter((d) => d.resolution === undefined); + } + // ── Helpers ───────────────────────────────────────────────────────────────── + mapResolutionToStatus(resolution) { + switch (resolution) { + case DisputeResolution.APPROVED: + return ClaimStatus.APPROVED; + case DisputeResolution.REJECTED: + return ClaimStatus.REJECTED; + case DisputeResolution.ESCALATED: + return ClaimStatus.PENDING_MANUAL; + } + } +} +/** + * Factory function. + */ +export function createDisputeManager(repository) { + return new DisputeManager(repository); +} +//# sourceMappingURL=dispute-manager.js.map \ No newline at end of file diff --git a/oracle/dist/claims/dispute-manager.js.map b/oracle/dist/claims/dispute-manager.js.map new file mode 100644 index 00000000..d80ca627 --- /dev/null +++ b/oracle/dist/claims/dispute-manager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"dispute-manager.js","sourceRoot":"","sources":["../../src/claims/dispute-manager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/claims/fraud-detector.d.ts b/oracle/dist/claims/fraud-detector.d.ts new file mode 100644 index 00000000..d28e1680 --- /dev/null +++ b/oracle/dist/claims/fraud-detector.d.ts @@ -0,0 +1,66 @@ +/** + * Fraud Detector + * + * Heuristic fraud detection for insurance claims. + * + * Checks: + * 1. Velocity — too many claims from same address in a sliding window. + * 2. Amount anomaly — claim >> historical average for that asset. + * 3. Suspicious timing — claim submitted very soon after coverage purchase. + * 4. Duplicate claim — identical (address, asset, lossTimestamp) tuple. + */ +import type { InsuranceClaim, FraudDetectionResult } from './types.js'; +/** + * Fraud detector configuration. + */ +export interface FraudDetectorConfig { + /** Max claims per address per window before VELOCITY fires. Default: 3 */ + velocityThreshold: number; + /** Sliding window in seconds for velocity check. Default: 3600 */ + velocityWindowSeconds: number; + /** + * Min seconds between coverage purchase and claim submission. + * Claims submitted faster than this trigger SUSPICIOUS_TIMING. Default: 300 + */ + minCoverageAgeSeconds: number; + /** + * Multiplier on per-asset average. Claims > (avg * multiplier) trigger + * AMOUNT_ANOMALY. Default: 5 + */ + amountAnomalyMultiplier: number; + /** + * Minimum number of historical data points required before the amount + * anomaly check is applied. Default: 3 + */ + anomalyMinDataPoints: number; + /** Risk score threshold above which isFraudulent = true. Default: 60 */ + fraudRiskThreshold: number; +} +/** + * Fraud detector. + * + * Receives the full current claim list so it can compute per-address + * and per-asset statistics without an external database dependency. + */ +export declare class FraudDetector { + private config; + constructor(config?: Partial); + /** + * Analyse a claim for fraud signals. + * + * @param claim - The claim being evaluated (not yet persisted). + * @param allClaims - All existing claims in the repository. + */ + detect(claim: InsuranceClaim, allClaims: InsuranceClaim[]): FraudDetectionResult; + private checkVelocity; + private checkAmountAnomaly; + private checkSuspiciousTiming; + private checkDuplicate; + private makeSignal; + private highestSeverity; +} +/** + * Factory function. + */ +export declare function createFraudDetector(config?: Partial): FraudDetector; +//# sourceMappingURL=fraud-detector.d.ts.map \ No newline at end of file diff --git a/oracle/dist/claims/fraud-detector.d.ts.map b/oracle/dist/claims/fraud-detector.d.ts.map new file mode 100644 index 00000000..8e7ab3aa --- /dev/null +++ b/oracle/dist/claims/fraud-detector.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"fraud-detector.d.ts","sourceRoot":"","sources":["../../src/claims/fraud-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAIpB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,uBAAuB,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAmBD;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,GAAE,OAAO,CAAC,mBAAmB,CAAM;IAIrD;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,oBAAoB;IAiDhF,OAAO,CAAC,aAAa;IA0BrB,OAAO,CAAC,kBAAkB;IAgC1B,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,eAAe;CAaxB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GACpC,aAAa,CAEf"} \ No newline at end of file diff --git a/oracle/dist/claims/fraud-detector.js b/oracle/dist/claims/fraud-detector.js new file mode 100644 index 00000000..541f7b09 --- /dev/null +++ b/oracle/dist/claims/fraud-detector.js @@ -0,0 +1,152 @@ +/** + * Fraud Detector + * + * Heuristic fraud detection for insurance claims. + * + * Checks: + * 1. Velocity — too many claims from same address in a sliding window. + * 2. Amount anomaly — claim >> historical average for that asset. + * 3. Suspicious timing — claim submitted very soon after coverage purchase. + * 4. Duplicate claim — identical (address, asset, lossTimestamp) tuple. + */ +import { FraudSeverity, FraudSignalType } from './types.js'; +import { logger } from '../utils/logger.js'; +const DEFAULT_CONFIG = { + velocityThreshold: 3, + velocityWindowSeconds: 3600, + minCoverageAgeSeconds: 300, + amountAnomalyMultiplier: 5, + anomalyMinDataPoints: 3, + fraudRiskThreshold: 60, +}; +/** Scoring weights per signal severity (0–100 scale). */ +const SEVERITY_SCORE = { + [FraudSeverity.LOW]: 10, + [FraudSeverity.MEDIUM]: 25, + [FraudSeverity.HIGH]: 50, + [FraudSeverity.CRITICAL]: 100, +}; +/** + * Fraud detector. + * + * Receives the full current claim list so it can compute per-address + * and per-asset statistics without an external database dependency. + */ +export class FraudDetector { + config; + constructor(config = {}) { + this.config = { ...DEFAULT_CONFIG, ...config }; + } + /** + * Analyse a claim for fraud signals. + * + * @param claim - The claim being evaluated (not yet persisted). + * @param allClaims - All existing claims in the repository. + */ + detect(claim, allClaims) { + const signals = []; + const now = Math.floor(Date.now() / 1000); + // 1. Velocity check + const velocitySignal = this.checkVelocity(claim, allClaims, now); + if (velocitySignal) + signals.push(velocitySignal); + // 2. Amount anomaly check + const anomalySignal = this.checkAmountAnomaly(claim, allClaims); + if (anomalySignal) + signals.push(anomalySignal); + // 3. Suspicious timing check + const timingSignal = this.checkSuspiciousTiming(claim, now); + if (timingSignal) + signals.push(timingSignal); + // 4. Duplicate claim check + const duplicateSignal = this.checkDuplicate(claim, allClaims); + if (duplicateSignal) + signals.push(duplicateSignal); + // Aggregate risk score (capped at 100) + const riskScore = Math.min(100, signals.reduce((sum, s) => sum + SEVERITY_SCORE[s.severity], 0)); + const maxSeverity = this.highestSeverity(signals); + const isFraudulent = riskScore >= this.config.fraudRiskThreshold; + if (isFraudulent) { + logger.warn('Fraud detected for claim', { + claimId: claim.id, + claimant: claim.claimantAddress, + riskScore, + signals: signals.map((s) => s.type), + }); + } + else if (signals.length > 0) { + logger.info('Fraud signals detected (below threshold)', { + claimId: claim.id, + riskScore, + signals: signals.map((s) => s.type), + }); + } + return { isFraudulent, signals, maxSeverity, riskScore }; + } + // ── Private Checks ────────────────────────────────────────────────────────── + checkVelocity(claim, allClaims, now) { + const windowStart = now - this.config.velocityWindowSeconds; + const recentClaims = allClaims.filter((c) => c.claimantAddress === claim.claimantAddress && + c.submittedAt >= windowStart && + c.id !== claim.id); + if (recentClaims.length >= this.config.velocityThreshold) { + return this.makeSignal(FraudSignalType.VELOCITY, FraudSeverity.HIGH, `Address submitted ${recentClaims.length + 1} claims within ${this.config.velocityWindowSeconds}s`, { count: recentClaims.length + 1, windowSeconds: this.config.velocityWindowSeconds }); + } + return null; + } + checkAmountAnomaly(claim, allClaims) { + const assetClaims = allClaims.filter((c) => c.asset === claim.asset && c.id !== claim.id); + if (assetClaims.length < this.config.anomalyMinDataPoints) { + return null; // Not enough data to establish a baseline + } + const total = assetClaims.reduce((sum, c) => sum + c.claimedAmount, 0n); + const avg = total / BigInt(assetClaims.length); + const threshold = avg * BigInt(this.config.amountAnomalyMultiplier); + if (claim.claimedAmount > threshold) { + return this.makeSignal(FraudSignalType.AMOUNT_ANOMALY, FraudSeverity.MEDIUM, `Claim amount (${claim.claimedAmount}) is ${this.config.amountAnomalyMultiplier}× above asset average (${avg})`, { + claimedAmount: claim.claimedAmount.toString(), + assetAverage: avg.toString(), + threshold: threshold.toString(), + }); + } + return null; + } + checkSuspiciousTiming(claim, now) { + const coverageAge = claim.submittedAt - claim.coveragePurchasedAt; + if (coverageAge < this.config.minCoverageAgeSeconds) { + return this.makeSignal(FraudSignalType.SUSPICIOUS_TIMING, FraudSeverity.MEDIUM, `Claim submitted only ${coverageAge}s after coverage was purchased (min: ${this.config.minCoverageAgeSeconds}s)`, { coverageAge, minCoverageAgeSeconds: this.config.minCoverageAgeSeconds }); + } + return null; + } + checkDuplicate(claim, allClaims) { + const duplicate = allClaims.find((c) => c.id !== claim.id && + c.claimantAddress === claim.claimantAddress && + c.asset === claim.asset && + c.lossTimestamp === claim.lossTimestamp); + if (duplicate) { + return this.makeSignal(FraudSignalType.DUPLICATE_CLAIM, FraudSeverity.CRITICAL, `Duplicate claim detected — same claimant, asset, and loss timestamp as claim ${duplicate.id}`, { duplicateClaimId: duplicate.id }); + } + return null; + } + makeSignal(type, severity, message, details) { + return { type, severity, message, details, detectedAt: Math.floor(Date.now() / 1000) }; + } + highestSeverity(signals) { + if (signals.length === 0) + return undefined; + const order = [ + FraudSeverity.LOW, + FraudSeverity.MEDIUM, + FraudSeverity.HIGH, + FraudSeverity.CRITICAL, + ]; + return signals.reduce((max, s) => order.indexOf(s.severity) > order.indexOf(max) ? s.severity : max, signals[0].severity); + } +} +/** + * Factory function. + */ +export function createFraudDetector(config) { + return new FraudDetector(config); +} +//# sourceMappingURL=fraud-detector.js.map \ No newline at end of file diff --git a/oracle/dist/claims/fraud-detector.js.map b/oracle/dist/claims/fraud-detector.js.map new file mode 100644 index 00000000..8f18e63b --- /dev/null +++ b/oracle/dist/claims/fraud-detector.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fraud-detector.js","sourceRoot":"","sources":["../../src/claims/fraud-detector.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/claims/index.d.ts b/oracle/dist/claims/index.d.ts new file mode 100644 index 00000000..9f8517e2 --- /dev/null +++ b/oracle/dist/claims/index.d.ts @@ -0,0 +1,11 @@ +/** + * Claims Module Barrel Export + */ +export * from './types.js'; +export * from './claim-repository.js'; +export * from './claim-verifier.js'; +export * from './payout-calculator.js'; +export * from './fraud-detector.js'; +export * from './dispute-manager.js'; +export * from './claims-service.js'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/oracle/dist/claims/index.d.ts.map b/oracle/dist/claims/index.d.ts.map new file mode 100644 index 00000000..d4ba3d7d --- /dev/null +++ b/oracle/dist/claims/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/claims/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"} \ No newline at end of file diff --git a/oracle/dist/claims/index.js b/oracle/dist/claims/index.js new file mode 100644 index 00000000..b51a120a --- /dev/null +++ b/oracle/dist/claims/index.js @@ -0,0 +1,11 @@ +/** + * Claims Module Barrel Export + */ +export * from './types.js'; +export * from './claim-repository.js'; +export * from './claim-verifier.js'; +export * from './payout-calculator.js'; +export * from './fraud-detector.js'; +export * from './dispute-manager.js'; +export * from './claims-service.js'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/oracle/dist/claims/index.js.map b/oracle/dist/claims/index.js.map new file mode 100644 index 00000000..26d03dab --- /dev/null +++ b/oracle/dist/claims/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/claims/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"} \ No newline at end of file diff --git a/oracle/dist/claims/payout-calculator.d.ts b/oracle/dist/claims/payout-calculator.d.ts new file mode 100644 index 00000000..015f73fa --- /dev/null +++ b/oracle/dist/claims/payout-calculator.d.ts @@ -0,0 +1,62 @@ +/** + * Payout Calculator + * + * Calculates insurance claim payouts using the oracle-verified + * asset price. Applies coverage cap, deductible, and a + * confidence-weighted discount for low-certainty oracle data. + * + * All amounts use 7-decimal fixed-point arithmetic (Stellar standard). + * SCALE = 10_000_000n represents 1.0. + */ +import type { InsuranceClaim, PayoutResult, OracleVerificationData } from './types.js'; +/** 7-decimal fixed-point scale factor (Stellar standard). */ +export declare const SCALE = 10000000n; +/** + * Payout calculator configuration. + */ +export interface PayoutCalculatorConfig { + /** + * Deductible as a percentage (0–100). + * e.g. 5 → 5 % of the capped amount is subtracted. + * Default: 5 + */ + deductiblePercent: number; + /** + * Oracle confidence threshold below which a discount is applied. + * Default: 80 + */ + minOracleConfidence: number; + /** + * Confidence discount rate. + * For each percentage point below minOracleConfidence the payout is + * reduced by (confidenceDiscountRate / 100). + * Default: 0.5 → 0.5 % per point below threshold + */ + confidenceDiscountRate: number; +} +/** + * Calculates insurance payouts. + */ +export declare class PayoutCalculator { + private config; + constructor(config?: Partial); + /** + * Calculate the payout for an approved claim. + * + * @param claim - The verified claim. + * @param oracle - Oracle data captured during verification. + * @returns PayoutResult with full breakdown. + */ + calculate(claim: InsuranceClaim, oracle: OracleVerificationData): PayoutResult; + private computeDeductible; + /** + * Returns a discount multiplier [0.0, 1.0]. + * 0 → no discount; 1 → full discount (zero payout). + */ + private computeConfidenceDiscount; +} +/** + * Factory function. + */ +export declare function createPayoutCalculator(config?: Partial): PayoutCalculator; +//# sourceMappingURL=payout-calculator.d.ts.map \ No newline at end of file diff --git a/oracle/dist/claims/payout-calculator.d.ts.map b/oracle/dist/claims/payout-calculator.d.ts.map new file mode 100644 index 00000000..9f78ba79 --- /dev/null +++ b/oracle/dist/claims/payout-calculator.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"payout-calculator.d.ts","sourceRoot":"","sources":["../../src/claims/payout-calculator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGvF,6DAA6D;AAC7D,eAAO,MAAM,KAAK,YAAc,CAAC;AAEjC;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAQD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,GAAE,OAAO,CAAC,sBAAsB,CAAM;IAIxD;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,sBAAsB,GAAG,YAAY;IAoD9E,OAAO,CAAC,iBAAiB;IAKzB;;;OAGG;IACH,OAAO,CAAC,yBAAyB;CAQlC;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,GACvC,gBAAgB,CAElB"} \ No newline at end of file diff --git a/oracle/dist/claims/payout-calculator.js b/oracle/dist/claims/payout-calculator.js new file mode 100644 index 00000000..da3f6c73 --- /dev/null +++ b/oracle/dist/claims/payout-calculator.js @@ -0,0 +1,99 @@ +/** + * Payout Calculator + * + * Calculates insurance claim payouts using the oracle-verified + * asset price. Applies coverage cap, deductible, and a + * confidence-weighted discount for low-certainty oracle data. + * + * All amounts use 7-decimal fixed-point arithmetic (Stellar standard). + * SCALE = 10_000_000n represents 1.0. + */ +import { logger } from '../utils/logger.js'; +/** 7-decimal fixed-point scale factor (Stellar standard). */ +export const SCALE = 10000000n; +const DEFAULT_CONFIG = { + deductiblePercent: 5, + minOracleConfidence: 80, + confidenceDiscountRate: 0.5, +}; +/** + * Calculates insurance payouts. + */ +export class PayoutCalculator { + config; + constructor(config = {}) { + this.config = { ...DEFAULT_CONFIG, ...config }; + } + /** + * Calculate the payout for an approved claim. + * + * @param claim - The verified claim. + * @param oracle - Oracle data captured during verification. + * @returns PayoutResult with full breakdown. + */ + calculate(claim, oracle) { + const now = Math.floor(Date.now() / 1000); + // 1. Gross amount — what was claimed + const grossAmount = claim.claimedAmount; + // 2. Apply coverage cap + const cappedAmount = grossAmount > claim.coverageLimit ? claim.coverageLimit : grossAmount; + // 3. Apply deductible + const deductibleAmount = this.computeDeductible(cappedAmount); + const afterDeductible = cappedAmount > deductibleAmount + ? cappedAmount - deductibleAmount + : 0n; + // 4. Apply confidence discount + const confidenceDiscount = this.computeConfidenceDiscount(oracle.confidence); + const discountFactor = BigInt(Math.round((1 - confidenceDiscount) * 1_000_000)); + const netPayoutAmount = (afterDeductible * discountFactor) / 1000000n; + // 5. Convert to USD value (oracle price is 7-decimal fixed-point) + // usdValue = netPayoutAmount * oraclePrice / SCALE + const usdValue = (netPayoutAmount * oracle.priceAtVerification) / SCALE; + const result = { + grossAmount, + cappedAmount, + deductibleAmount, + netPayoutAmount, + oraclePriceUsed: oracle.priceAtVerification, + usdValue, + confidenceDiscount, + calculatedAt: now, + }; + logger.info('Payout calculated', { + claimId: claim.id, + grossAmount: grossAmount.toString(), + cappedAmount: cappedAmount.toString(), + deductibleAmount: deductibleAmount.toString(), + netPayoutAmount: netPayoutAmount.toString(), + usdValue: usdValue.toString(), + confidenceDiscount, + oracleConfidence: oracle.confidence, + }); + return result; + } + // ── Private Helpers ───────────────────────────────────────────────────────── + computeDeductible(cappedAmount) { + if (this.config.deductiblePercent <= 0) + return 0n; + return (cappedAmount * BigInt(Math.round(this.config.deductiblePercent * 1_000))) / 100000n; + } + /** + * Returns a discount multiplier [0.0, 1.0]. + * 0 → no discount; 1 → full discount (zero payout). + */ + computeConfidenceDiscount(confidence) { + if (confidence >= this.config.minOracleConfidence) + return 0; + const pointsBelow = this.config.minOracleConfidence - confidence; + const discount = (pointsBelow * this.config.confidenceDiscountRate) / 100; + // Clamp to [0, 0.5] — never wipe out more than half the payout automatically + return Math.min(0.5, Math.max(0, discount)); + } +} +/** + * Factory function. + */ +export function createPayoutCalculator(config) { + return new PayoutCalculator(config); +} +//# sourceMappingURL=payout-calculator.js.map \ No newline at end of file diff --git a/oracle/dist/claims/payout-calculator.js.map b/oracle/dist/claims/payout-calculator.js.map new file mode 100644 index 00000000..232a58ad --- /dev/null +++ b/oracle/dist/claims/payout-calculator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"payout-calculator.js","sourceRoot":"","sources":["../../src/claims/payout-calculator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/claims/types.d.ts b/oracle/dist/claims/types.d.ts new file mode 100644 index 00000000..432aed64 --- /dev/null +++ b/oracle/dist/claims/types.d.ts @@ -0,0 +1,310 @@ +/** + * Insurance Claims Module — Type Definitions + * + * All domain types, enums, and interfaces for the automated + * insurance claim lifecycle in StellarLend. + */ +/** + * Lifecycle status of an insurance claim. + */ +export declare enum ClaimStatus { + /** Claim submitted, awaiting oracle verification. */ + PENDING = "PENDING", + /** Oracle verification in progress. */ + VERIFYING = "VERIFYING", + /** Verified and awaiting payout processing. */ + APPROVED = "APPROVED", + /** Claim rejected (failed verification, fraud detected, etc.). */ + REJECTED = "REJECTED", + /** Payout dispatched to the claimant. */ + PAID_OUT = "PAID_OUT", + /** Claim is under dispute review. */ + DISPUTED = "DISPUTED", + /** Oracle temporarily unavailable — pending manual review. */ + PENDING_MANUAL = "PENDING_MANUAL", + /** Claim cancelled by the claimant before resolution. */ + CANCELLED = "CANCELLED" +} +/** + * Reasons a claim can be rejected. + */ +export declare enum RejectionReason { + FRAUD_DETECTED = "FRAUD_DETECTED", + ORACLE_PRICE_UNAVAILABLE = "ORACLE_PRICE_UNAVAILABLE", + INSUFFICIENT_COVERAGE = "INSUFFICIENT_COVERAGE", + INVALID_AMOUNT = "INVALID_AMOUNT", + UNSUPPORTED_ASSET = "UNSUPPORTED_ASSET", + STALE_ORACLE_PRICE = "STALE_ORACLE_PRICE", + CLAIM_EXPIRED = "CLAIM_EXPIRED", + DUPLICATE_CLAIM = "DUPLICATE_CLAIM", + POLICY_NOT_ACTIVE = "POLICY_NOT_ACTIVE" +} +/** + * Severity level of a fraud signal. + */ +export declare enum FraudSeverity { + LOW = "LOW", + MEDIUM = "MEDIUM", + HIGH = "HIGH", + CRITICAL = "CRITICAL" +} +/** + * Type of fraud detected. + */ +export declare enum FraudSignalType { + VELOCITY = "VELOCITY", + AMOUNT_ANOMALY = "AMOUNT_ANOMALY", + SUSPICIOUS_TIMING = "SUSPICIOUS_TIMING", + DUPLICATE_CLAIM = "DUPLICATE_CLAIM", + BLACKLISTED_ADDRESS = "BLACKLISTED_ADDRESS" +} +/** + * How a dispute was resolved. + */ +export declare enum DisputeResolution { + APPROVED = "APPROVED", + REJECTED = "REJECTED", + ESCALATED = "ESCALATED" +} +/** + * A submitted insurance claim. + */ +export interface InsuranceClaim { + /** Unique claim identifier (UUID-like). */ + id: string; + /** Stellar address of the claimant. */ + claimantAddress: string; + /** Asset symbol the claim is denominated in (e.g. 'XLM', 'USDC'). */ + asset: string; + /** Claimed loss amount in the asset's smallest unit (7 decimal places). */ + claimedAmount: bigint; + /** Maximum coverage the policy provides in asset units. */ + coverageLimit: bigint; + /** Description of the loss event. */ + lossDescription: string; + /** Unix timestamp when the loss occurred. */ + lossTimestamp: number; + /** Unix timestamp when the claim was submitted. */ + submittedAt: number; + /** Unix timestamp when the coverage policy was purchased. */ + coveragePurchasedAt: number; + /** Current lifecycle status. */ + status: ClaimStatus; + /** Oracle verification snapshot (populated after verification). */ + verificationResult?: ClaimVerificationResult; + /** Calculated payout (populated after APPROVED). */ + payoutResult?: PayoutResult; + /** Rejection reason (populated after REJECTED). */ + rejectionReason?: RejectionReason; + /** Rejection details (human-readable). */ + rejectionDetails?: string; + /** Fraud signals detected (may be empty). */ + fraudSignals: FraudSignal[]; + /** Full audit trail of status transitions. */ + history: ClaimHistoryEntry[]; + /** On-chain transaction hash after payout (populated after PAID_OUT). */ + payoutTransactionHash?: string; +} +/** + * Input required to submit a new claim. + */ +export interface ClaimSubmissionRequest { + claimantAddress: string; + asset: string; + claimedAmount: bigint; + coverageLimit: bigint; + lossDescription: string; + lossTimestamp: number; + coveragePurchasedAt: number; + /** Optional: external policy ID for reference. */ + policyId?: string; +} +/** + * Data captured from oracle at the time of claim verification. + */ +export interface OracleVerificationData { + /** Oracle price in 7-decimal fixed-point (e.g. 1_0000000 = 1.0 USD). */ + priceAtVerification: bigint; + /** Unix timestamp of the oracle price. */ + priceTimestamp: number; + /** Oracle confidence score (0–100). */ + confidence: number; + /** Oracle data sources used. */ + sources: string[]; + /** Unix timestamp when verification was performed. */ + verifiedAt: number; +} +/** + * Result of the oracle-based claim verification step. + */ +export interface ClaimVerificationResult { + isValid: boolean; + oracleData?: OracleVerificationData; + errors: ClaimVerificationError[]; +} +/** + * A single verification failure. + */ +export interface ClaimVerificationError { + code: VerificationErrorCode; + message: string; + details?: Record; +} +/** + * Verification error codes. + */ +export declare enum VerificationErrorCode { + ORACLE_UNAVAILABLE = "ORACLE_UNAVAILABLE", + PRICE_STALE = "PRICE_STALE", + AMOUNT_EXCEEDS_COVERAGE = "AMOUNT_EXCEEDS_COVERAGE", + INVALID_AMOUNT = "INVALID_AMOUNT", + UNSUPPORTED_ASSET = "UNSUPPORTED_ASSET", + LOSS_TIMESTAMP_IN_FUTURE = "LOSS_TIMESTAMP_IN_FUTURE", + LOSS_BEFORE_COVERAGE = "LOSS_BEFORE_COVERAGE", + LOW_ORACLE_CONFIDENCE = "LOW_ORACLE_CONFIDENCE" +} +/** + * Detailed payout calculation breakdown. + */ +export interface PayoutResult { + /** Gross claim amount in asset units (before caps / deductible). */ + grossAmount: bigint; + /** Amount after applying the coverage cap. */ + cappedAmount: bigint; + /** Deductible amount subtracted. */ + deductibleAmount: bigint; + /** Final net payout in asset units. */ + netPayoutAmount: bigint; + /** Oracle price used (7-decimal fixed-point). */ + oraclePriceUsed: bigint; + /** Payout value in USD (7-decimal fixed-point). */ + usdValue: bigint; + /** Confidence discount applied (0.0 – 1.0). */ + confidenceDiscount: number; + /** Unix timestamp of calculation. */ + calculatedAt: number; +} +/** + * A single fraud indicator. + */ +export interface FraudSignal { + type: FraudSignalType; + severity: FraudSeverity; + message: string; + details?: Record; + detectedAt: number; +} +/** + * Aggregated fraud detection result. + */ +export interface FraudDetectionResult { + isFraudulent: boolean; + signals: FraudSignal[]; + /** Highest severity across all signals (undefined if no signals). */ + maxSeverity?: FraudSeverity; + /** Overall fraud risk score (0–100). */ + riskScore: number; +} +/** + * A dispute record attached to a claim. + */ +export interface DisputeRecord { + claimId: string; + /** Stellar address of the party opening the dispute. */ + disputantAddress: string; + /** Human-readable reason for dispute. */ + reason: string; + /** Supporting evidence (URLs, hashes, descriptions). */ + evidence: string[]; + /** Unix timestamp when dispute was opened. */ + openedAt: number; + /** Resolution outcome (undefined while pending). */ + resolution?: DisputeResolution; + /** Admin Stellar address who resolved the dispute. */ + resolvedBy?: string; + /** Admin notes on resolution. */ + resolutionNotes?: string; + /** Unix timestamp of resolution. */ + resolvedAt?: number; +} +/** + * A single immutable audit entry in the claim's history. + */ +export interface ClaimHistoryEntry { + /** Previous status (undefined for initial submission). */ + fromStatus?: ClaimStatus; + /** New status after this transition. */ + toStatus: ClaimStatus; + /** Actor that triggered the transition ('oracle', 'system', or a Stellar address). */ + actor: string; + /** Human-readable description of the event. */ + description: string; + /** Unix timestamp of the event. */ + timestamp: number; + /** Optional structured metadata. */ + metadata?: Record; +} +/** + * Configuration for the ClaimsService. + */ +export interface ClaimsServiceConfig { + /** + * Maximum number of claims to keep in-memory before LRU eviction. + * Default: 10_000 + */ + maxClaimsInMemory: number; + /** + * Oracle confidence threshold below which a low-confidence discount is applied. + * Default: 80 + */ + minOracleConfidence: number; + /** + * How many seconds old an oracle price can be before it's considered stale. + * Default: 300 + */ + maxPriceAgeSeconds: number; + /** + * Deductible percentage applied to payouts (0–100). + * Default: 5 + */ + deductiblePercent: number; + /** + * Number of claims per address per window that triggers velocity fraud. + * Default: 3 + */ + velocityThreshold: number; + /** + * Time window (seconds) for velocity check. + * Default: 3600 (1 hour) + */ + velocityWindowSeconds: number; + /** + * Minimum seconds between coverage purchase and claim before + * "suspicious timing" fraud signal fires. + * Default: 300 (5 minutes) + */ + minCoverageAgeSeconds: number; + /** + * Multiplier on historical average — claims larger than + * (averageAmount * anomalyMultiplier) trigger amount anomaly. + * Default: 5 + */ + amountAnomalyMultiplier: number; + /** + * If true, oracle failures put the claim in PENDING_MANUAL rather than REJECTED. + * Default: true + */ + fallbackToPendingManual: boolean; +} +/** + * Aggregate statistics reported by ClaimsService.getStats(). + */ +export interface ClaimsStats { + totalClaims: number; + byStatus: Record; + totalPayoutAmount: bigint; + fraudDetectionRate: number; + averageVerificationTimeMs: number; + disputeRate: number; +} +//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/oracle/dist/claims/types.d.ts.map b/oracle/dist/claims/types.d.ts.map new file mode 100644 index 00000000..d2189ab5 --- /dev/null +++ b/oracle/dist/claims/types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/claims/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/claims/types.js b/oracle/dist/claims/types.js new file mode 100644 index 00000000..5e289262 --- /dev/null +++ b/oracle/dist/claims/types.js @@ -0,0 +1,91 @@ +/** + * Insurance Claims Module — Type Definitions + * + * All domain types, enums, and interfaces for the automated + * insurance claim lifecycle in StellarLend. + */ +// ───────────────────────────────────────────────────────────────────────────── +// Enumerations +// ───────────────────────────────────────────────────────────────────────────── +/** + * Lifecycle status of an insurance claim. + */ +export var ClaimStatus; +(function (ClaimStatus) { + /** Claim submitted, awaiting oracle verification. */ + ClaimStatus["PENDING"] = "PENDING"; + /** Oracle verification in progress. */ + ClaimStatus["VERIFYING"] = "VERIFYING"; + /** Verified and awaiting payout processing. */ + ClaimStatus["APPROVED"] = "APPROVED"; + /** Claim rejected (failed verification, fraud detected, etc.). */ + ClaimStatus["REJECTED"] = "REJECTED"; + /** Payout dispatched to the claimant. */ + ClaimStatus["PAID_OUT"] = "PAID_OUT"; + /** Claim is under dispute review. */ + ClaimStatus["DISPUTED"] = "DISPUTED"; + /** Oracle temporarily unavailable — pending manual review. */ + ClaimStatus["PENDING_MANUAL"] = "PENDING_MANUAL"; + /** Claim cancelled by the claimant before resolution. */ + ClaimStatus["CANCELLED"] = "CANCELLED"; +})(ClaimStatus || (ClaimStatus = {})); +/** + * Reasons a claim can be rejected. + */ +export var RejectionReason; +(function (RejectionReason) { + RejectionReason["FRAUD_DETECTED"] = "FRAUD_DETECTED"; + RejectionReason["ORACLE_PRICE_UNAVAILABLE"] = "ORACLE_PRICE_UNAVAILABLE"; + RejectionReason["INSUFFICIENT_COVERAGE"] = "INSUFFICIENT_COVERAGE"; + RejectionReason["INVALID_AMOUNT"] = "INVALID_AMOUNT"; + RejectionReason["UNSUPPORTED_ASSET"] = "UNSUPPORTED_ASSET"; + RejectionReason["STALE_ORACLE_PRICE"] = "STALE_ORACLE_PRICE"; + RejectionReason["CLAIM_EXPIRED"] = "CLAIM_EXPIRED"; + RejectionReason["DUPLICATE_CLAIM"] = "DUPLICATE_CLAIM"; + RejectionReason["POLICY_NOT_ACTIVE"] = "POLICY_NOT_ACTIVE"; +})(RejectionReason || (RejectionReason = {})); +/** + * Severity level of a fraud signal. + */ +export var FraudSeverity; +(function (FraudSeverity) { + FraudSeverity["LOW"] = "LOW"; + FraudSeverity["MEDIUM"] = "MEDIUM"; + FraudSeverity["HIGH"] = "HIGH"; + FraudSeverity["CRITICAL"] = "CRITICAL"; +})(FraudSeverity || (FraudSeverity = {})); +/** + * Type of fraud detected. + */ +export var FraudSignalType; +(function (FraudSignalType) { + FraudSignalType["VELOCITY"] = "VELOCITY"; + FraudSignalType["AMOUNT_ANOMALY"] = "AMOUNT_ANOMALY"; + FraudSignalType["SUSPICIOUS_TIMING"] = "SUSPICIOUS_TIMING"; + FraudSignalType["DUPLICATE_CLAIM"] = "DUPLICATE_CLAIM"; + FraudSignalType["BLACKLISTED_ADDRESS"] = "BLACKLISTED_ADDRESS"; +})(FraudSignalType || (FraudSignalType = {})); +/** + * How a dispute was resolved. + */ +export var DisputeResolution; +(function (DisputeResolution) { + DisputeResolution["APPROVED"] = "APPROVED"; + DisputeResolution["REJECTED"] = "REJECTED"; + DisputeResolution["ESCALATED"] = "ESCALATED"; +})(DisputeResolution || (DisputeResolution = {})); +/** + * Verification error codes. + */ +export var VerificationErrorCode; +(function (VerificationErrorCode) { + VerificationErrorCode["ORACLE_UNAVAILABLE"] = "ORACLE_UNAVAILABLE"; + VerificationErrorCode["PRICE_STALE"] = "PRICE_STALE"; + VerificationErrorCode["AMOUNT_EXCEEDS_COVERAGE"] = "AMOUNT_EXCEEDS_COVERAGE"; + VerificationErrorCode["INVALID_AMOUNT"] = "INVALID_AMOUNT"; + VerificationErrorCode["UNSUPPORTED_ASSET"] = "UNSUPPORTED_ASSET"; + VerificationErrorCode["LOSS_TIMESTAMP_IN_FUTURE"] = "LOSS_TIMESTAMP_IN_FUTURE"; + VerificationErrorCode["LOSS_BEFORE_COVERAGE"] = "LOSS_BEFORE_COVERAGE"; + VerificationErrorCode["LOW_ORACLE_CONFIDENCE"] = "LOW_ORACLE_CONFIDENCE"; +})(VerificationErrorCode || (VerificationErrorCode = {})); +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/oracle/dist/claims/types.js.map b/oracle/dist/claims/types.js.map new file mode 100644 index 00000000..bfebc4fb --- /dev/null +++ b/oracle/dist/claims/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/claims/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,gFAAgF;AAChF,eAAe;AACf,gFAAgF;AAEhF;;GAEG;AACH,MAAM,CAAN,IAAY,WAiBX;AAjBD,WAAY,WAAW;IACrB,qDAAqD;IACrD,kCAAmB,CAAA;IACnB,uCAAuC;IACvC,sCAAuB,CAAA;IACvB,+CAA+C;IAC/C,oCAAqB,CAAA;IACrB,kEAAkE;IAClE,oCAAqB,CAAA;IACrB,yCAAyC;IACzC,oCAAqB,CAAA;IACrB,qCAAqC;IACrC,oCAAqB,CAAA;IACrB,8DAA8D;IAC9D,gDAAiC,CAAA;IACjC,yDAAyD;IACzD,sCAAuB,CAAA;AACzB,CAAC,EAjBW,WAAW,KAAX,WAAW,QAiBtB;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,eAUX;AAVD,WAAY,eAAe;IACzB,oDAAiC,CAAA;IACjC,wEAAqD,CAAA;IACrD,kEAA+C,CAAA;IAC/C,oDAAiC,CAAA;IACjC,0DAAuC,CAAA;IACvC,4DAAyC,CAAA;IACzC,kDAA+B,CAAA;IAC/B,sDAAmC,CAAA;IACnC,0DAAuC,CAAA;AACzC,CAAC,EAVW,eAAe,KAAf,eAAe,QAU1B;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,aAKX;AALD,WAAY,aAAa;IACvB,4BAAW,CAAA;IACX,kCAAiB,CAAA;IACjB,8BAAa,CAAA;IACb,sCAAqB,CAAA;AACvB,CAAC,EALW,aAAa,KAAb,aAAa,QAKxB;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,eAMX;AAND,WAAY,eAAe;IACzB,wCAAqB,CAAA;IACrB,oDAAiC,CAAA;IACjC,0DAAuC,CAAA;IACvC,sDAAmC,CAAA;IACnC,8DAA2C,CAAA;AAC7C,CAAC,EANW,eAAe,KAAf,eAAe,QAM1B;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,iBAIX;AAJD,WAAY,iBAAiB;IAC3B,0CAAqB,CAAA;IACrB,0CAAqB,CAAA;IACrB,4CAAuB,CAAA;AACzB,CAAC,EAJW,iBAAiB,KAAjB,iBAAiB,QAI5B;AAmGD;;GAEG;AACH,MAAM,CAAN,IAAY,qBASX;AATD,WAAY,qBAAqB;IAC/B,kEAAyC,CAAA;IACzC,oDAA2B,CAAA;IAC3B,4EAAmD,CAAA;IACnD,0DAAiC,CAAA;IACjC,gEAAuC,CAAA;IACvC,8EAAqD,CAAA;IACrD,sEAA6C,CAAA;IAC7C,wEAA+C,CAAA;AACjD,CAAC,EATW,qBAAqB,KAArB,qBAAqB,QAShC"} \ No newline at end of file diff --git a/oracle/dist/config.d.ts b/oracle/dist/config.d.ts new file mode 100644 index 00000000..aeb9b242 --- /dev/null +++ b/oracle/dist/config.d.ts @@ -0,0 +1,38 @@ +/** + * Oracle Service Configuration + * + * Handles loading and validating environment variables and + * provides typed configuration for the oracle service. + */ +import type { OracleServiceConfig, AssetMapping, SupportedAsset } from './types/index.js'; +export type { OracleServiceConfig } from './types/index.js'; +/** + * Asset mappings for different providers + */ +export declare const ASSET_MAPPINGS: AssetMapping[]; +/** + * Get asset mapping by symbol + */ +export declare function getAssetMapping(symbol: SupportedAsset): AssetMapping | undefined; +/** + * Check if an asset is supported + */ +export declare function isSupportedAsset(symbol: string): symbol is SupportedAsset; +export declare function loadConfig(): OracleServiceConfig; +/** + * Masks a secret key for safe logging. + * Shows first 2 and last 2 characters only. + * Handles edge cases: empty string, very short keys. + */ +export declare function maskSecret(key: string): string; +/** + * Returns a safe (redacted) version of the config for logging. + * Strips adminSecretKey entirely. + */ +export declare function getSafeConfig(config: OracleServiceConfig): Omit & { + adminSecretKey: string; +}; +export declare const PRICE_SCALE = 1000000n; +export declare function scalePrice(price: number): bigint; +export declare function unscalePrice(price: bigint): number; +//# sourceMappingURL=config.d.ts.map \ No newline at end of file diff --git a/oracle/dist/config.d.ts.map b/oracle/dist/config.d.ts.map new file mode 100644 index 00000000..3449f19d --- /dev/null +++ b/oracle/dist/config.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,mBAAmB,EAEnB,YAAY,EACZ,cAAc,EACf,MAAM,kBAAkB,CAAC;AAK1B,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAoK5D;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,YAAY,EA+BxC,CAAC;AAEF;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,SAAS,CAEhF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,cAAc,CAEzE;AAgBD,wBAAgB,UAAU,IAAI,mBAAmB,CA+BhD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,mBAAmB,GAC1B,IAAI,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAK1E;AAED,eAAO,MAAM,WAAW,WAAa,CAAC;AAEtC,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD"} \ No newline at end of file diff --git a/oracle/dist/config.js b/oracle/dist/config.js new file mode 100644 index 00000000..80cf0df4 --- /dev/null +++ b/oracle/dist/config.js @@ -0,0 +1,272 @@ +/** + * Oracle Service Configuration + * + * Handles loading and validating environment variables and + * provides typed configuration for the oracle service. + */ +import { z } from 'zod'; +import dotenv from 'dotenv'; +import { configureLogger, logger } from './utils/logger.js'; +import { developmentOverrides } from './config/development.js'; +import { productionOverrides } from './config/production.js'; +dotenv.config(); +const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error']); +const MIN_STELLAR_FEE = 100; +const DEFAULT_MAX_FEE = 1_000_000; +function getBootstrapLogLevel() { + const level = process.env.LOG_LEVEL; + if (level && VALID_LOG_LEVELS.has(level)) { + return level; + } + return 'info'; +} +configureLogger(getBootstrapLogLevel(), process.env.NODE_ENV === 'production'); +const booleanFlagSchema = z + .union([z.boolean(), z.string()]) + .optional() + .transform((value, ctx) => { + if (value === undefined) { + return false; + } + if (typeof value === 'boolean') { + return value; + } + const normalized = value.trim().toLowerCase(); + if (['true', '1', 'yes', 'on'].includes(normalized)) { + return true; + } + if (['false', '0', 'no', 'off', ''].includes(normalized)) { + return false; + } + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Expected a boolean value like true/false', + }); + return z.NEVER; +}); +/** + * Network-specific defaults + */ +const NETWORK_DEFAULTS = { + testnet: { + rpcUrl: 'https://soroban-testnet.stellar.org', + baseFee: 100000, + }, + mainnet: { + rpcUrl: 'https://soroban.stellar.org', + baseFee: 200000, + }, +}; +/** + * Environment variable validation schema + */ +const envSchema = z + .object({ + STELLAR_NETWORK: z.enum(['testnet', 'mainnet']).default('testnet'), + STELLAR_RPC_URL: z.string().url().optional(), + STELLAR_BASE_FEE: z.coerce.number().int().min(MIN_STELLAR_FEE).optional(), + STELLAR_MAX_FEE: z.coerce.number().int().min(MIN_STELLAR_FEE).default(DEFAULT_MAX_FEE), + CONTRACT_ID: z.string().min(1, 'CONTRACT_ID is required'), + ADMIN_SECRET_KEY: z.string().min(1, 'ADMIN_SECRET_KEY is required'), + COINGECKO_API_KEY: z.string().optional(), + COINMARKETCAP_API_KEY: z.string().optional(), + REDIS_URL: z.string().url().optional().or(z.literal('')), + CACHE_TTL_SECONDS: z.coerce.number().positive().default(30), + UPDATE_INTERVAL_MS: z.coerce.number().positive().default(60000), + DRY_RUN: booleanFlagSchema, + MAX_PRICE_DEVIATION_PERCENT: z.coerce.number().positive().default(10), + PRICE_STALENESS_THRESHOLD_SECONDS: z.coerce.number().positive().default(300), + CIRCUIT_BREAKER_FAILURE_THRESHOLD: z.coerce.number().int().positive().default(3), + CIRCUIT_BREAKER_BACKOFF_MS: z.coerce.number().positive().default(30_000), + LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'), +}) + .superRefine((env, ctx) => { + const networkDefaults = NETWORK_DEFAULTS[env.STELLAR_NETWORK]; + const baseFee = env.STELLAR_BASE_FEE ?? networkDefaults.baseFee; + if (baseFee > env.STELLAR_MAX_FEE) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'STELLAR_MAX_FEE must be greater than or equal to STELLAR_BASE_FEE', + path: ['STELLAR_MAX_FEE'], + }); + } +}); +/** + * Parse and validate environment variables + */ +function parseEnv() { + const result = envSchema.safeParse(process.env); + if (!result.success) { + logger.error('Environment validation failed', { + issues: result.error.issues.map((issue) => ({ + path: issue.path.join('.'), + message: issue.message, + })), + }); + throw new Error('Invalid environment configuration'); + } + return result.data; +} +/** + * Default provider configurations + */ +function getProviderConfigs(env) { + return [ + { + name: 'coingecko', + enabled: true, + priority: 1, + weight: 0.4, + apiKey: env.COINGECKO_API_KEY, + baseUrl: env.COINGECKO_API_KEY + ? 'https://pro-api.coingecko.com/api/v3' + : 'https://api.coingecko.com/api/v3', + rateLimit: { + maxRequests: env.COINGECKO_API_KEY ? 500 : 10, + windowMs: 60000, + }, + }, + { + name: 'coinmarketcap', + enabled: !!env.COINMARKETCAP_API_KEY, + priority: 2, + weight: 0.35, + apiKey: env.COINMARKETCAP_API_KEY, + baseUrl: 'https://pro-api.coinmarketcap.com/v2', + rateLimit: { + maxRequests: 30, + windowMs: 60000, + }, + }, + { + name: 'binance', + enabled: true, + priority: 3, + weight: 0.25, + baseUrl: 'https://api.binance.com/api/v3', + rateLimit: { + maxRequests: 1200, + windowMs: 60000, + }, + }, + ]; +} +/** + * Asset mappings for different providers + */ +export const ASSET_MAPPINGS = [ + { + symbol: 'XLM', + coingeckoId: 'stellar', + coinmarketcapId: 512, + binanceSymbol: 'XLMUSDT', + }, + { + symbol: 'USDC', + coingeckoId: 'usd-coin', + coinmarketcapId: 3408, + binanceSymbol: 'USDCUSDT', + }, + { + symbol: 'USDT', + coingeckoId: 'tether', + coinmarketcapId: 825, + binanceSymbol: 'USDTBUSD', + }, + { + symbol: 'BTC', + coingeckoId: 'bitcoin', + coinmarketcapId: 1, + binanceSymbol: 'BTCUSDT', + }, + { + symbol: 'ETH', + coingeckoId: 'ethereum', + coinmarketcapId: 1027, + binanceSymbol: 'ETHUSDT', + }, +]; +/** + * Get asset mapping by symbol + */ +export function getAssetMapping(symbol) { + return ASSET_MAPPINGS.find((m) => m.symbol === symbol); +} +/** + * Check if an asset is supported + */ +export function isSupportedAsset(symbol) { + return ASSET_MAPPINGS.some((m) => m.symbol === symbol); +} +/** + * Build and export the service configuration + */ +function getEnvOverrides() { + const nodeEnv = process.env.NODE_ENV || 'development'; + switch (nodeEnv) { + case 'production': + return productionOverrides; + case 'development': + default: + return developmentOverrides; + } +} +export function loadConfig() { + const env = parseEnv(); + const envOverrides = getEnvOverrides(); + // Get network-specific defaults + const networkDefaults = NETWORK_DEFAULTS[env.STELLAR_NETWORK]; + // Use env vars if provided, otherwise use network defaults + const stellarRpcUrl = env.STELLAR_RPC_URL || networkDefaults.rpcUrl; + const baseFee = env.STELLAR_BASE_FEE || networkDefaults.baseFee; + return { + stellarNetwork: env.STELLAR_NETWORK, + stellarRpcUrl, + baseFee, + maxFee: env.STELLAR_MAX_FEE, + contractId: env.CONTRACT_ID, + adminSecretKey: env.ADMIN_SECRET_KEY, + dryRun: envOverrides.dryRun ?? env.DRY_RUN, + updateIntervalMs: envOverrides.updateIntervalMs ?? env.UPDATE_INTERVAL_MS, + maxPriceDeviationPercent: envOverrides.maxPriceDeviationPercent ?? env.MAX_PRICE_DEVIATION_PERCENT, + priceStaleThresholdSeconds: envOverrides.priceStaleThresholdSeconds ?? env.PRICE_STALENESS_THRESHOLD_SECONDS, + cacheTtlSeconds: envOverrides.cacheTtlSeconds ?? env.CACHE_TTL_SECONDS, + redisUrl: env.REDIS_URL || undefined, + logLevel: envOverrides.logLevel ?? env.LOG_LEVEL, + providers: getProviderConfigs(env), + circuitBreaker: envOverrides.circuitBreaker ?? { + failureThreshold: env.CIRCUIT_BREAKER_FAILURE_THRESHOLD, + backoffMs: env.CIRCUIT_BREAKER_BACKOFF_MS, + }, + }; +} +/** + * Masks a secret key for safe logging. + * Shows first 2 and last 2 characters only. + * Handles edge cases: empty string, very short keys. + */ +export function maskSecret(key) { + if (!key || key.length === 0) + return '****'; + if (key.length <= 8) + return '****'; + return key.slice(0, 2) + '*'.repeat(key.length - 4) + key.slice(-2); +} +/** + * Returns a safe (redacted) version of the config for logging. + * Strips adminSecretKey entirely. + */ +export function getSafeConfig(config) { + return { + ...config, + adminSecretKey: maskSecret(config.adminSecretKey), + }; +} +export const PRICE_SCALE = 1000000n; +export function scalePrice(price) { + return BigInt(Math.round(price * Number(PRICE_SCALE))); +} +export function unscalePrice(price) { + return Number(price) / Number(PRICE_SCALE); +} +//# sourceMappingURL=config.js.map \ No newline at end of file diff --git a/oracle/dist/config.js.map b/oracle/dist/config.js.map new file mode 100644 index 00000000..6af47622 --- /dev/null +++ b/oracle/dist/config.js.map @@ -0,0 +1 @@ +{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/config/development.d.ts b/oracle/dist/config/development.d.ts new file mode 100644 index 00000000..16b651e1 --- /dev/null +++ b/oracle/dist/config/development.d.ts @@ -0,0 +1,3 @@ +import type { OracleServiceConfig } from '../types/index.js'; +export declare const developmentOverrides: Partial; +//# sourceMappingURL=development.d.ts.map \ No newline at end of file diff --git a/oracle/dist/config/development.d.ts.map b/oracle/dist/config/development.d.ts.map new file mode 100644 index 00000000..f4981d77 --- /dev/null +++ b/oracle/dist/config/development.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"development.d.ts","sourceRoot":"","sources":["../../src/config/development.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,eAAO,MAAM,oBAAoB,EAAE,OAAO,CAAC,mBAAmB,CAW7D,CAAC"} \ No newline at end of file diff --git a/oracle/dist/config/development.js b/oracle/dist/config/development.js new file mode 100644 index 00000000..c31881fc --- /dev/null +++ b/oracle/dist/config/development.js @@ -0,0 +1,13 @@ +export const developmentOverrides = { + logLevel: 'debug', + dryRun: true, + cacheTtlSeconds: 60, + updateIntervalMs: 120000, + maxPriceDeviationPercent: 15, + priceStaleThresholdSeconds: 600, + circuitBreaker: { + failureThreshold: 5, + backoffMs: 60000, + }, +}; +//# sourceMappingURL=development.js.map \ No newline at end of file diff --git a/oracle/dist/config/development.js.map b/oracle/dist/config/development.js.map new file mode 100644 index 00000000..1f492813 --- /dev/null +++ b/oracle/dist/config/development.js.map @@ -0,0 +1 @@ +{"version":3,"file":"development.js","sourceRoot":"","sources":["../../src/config/development.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,oBAAoB,GAAiC;IAChE,QAAQ,EAAE,OAAO;IACjB,MAAM,EAAE,IAAI;IACZ,eAAe,EAAE,EAAE;IACnB,gBAAgB,EAAE,MAAM;IACxB,wBAAwB,EAAE,EAAE;IAC5B,0BAA0B,EAAE,GAAG;IAC/B,cAAc,EAAE;QACd,gBAAgB,EAAE,CAAC;QACnB,SAAS,EAAE,KAAK;KACjB;CACF,CAAC"} \ No newline at end of file diff --git a/oracle/dist/config/production.d.ts b/oracle/dist/config/production.d.ts new file mode 100644 index 00000000..0e70ce52 --- /dev/null +++ b/oracle/dist/config/production.d.ts @@ -0,0 +1,3 @@ +import type { OracleServiceConfig } from '../types/index.js'; +export declare const productionOverrides: Partial; +//# sourceMappingURL=production.d.ts.map \ No newline at end of file diff --git a/oracle/dist/config/production.d.ts.map b/oracle/dist/config/production.d.ts.map new file mode 100644 index 00000000..31f593a8 --- /dev/null +++ b/oracle/dist/config/production.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"production.d.ts","sourceRoot":"","sources":["../../src/config/production.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,mBAAmB,CAW5D,CAAC"} \ No newline at end of file diff --git a/oracle/dist/config/production.js b/oracle/dist/config/production.js new file mode 100644 index 00000000..a3fc4551 --- /dev/null +++ b/oracle/dist/config/production.js @@ -0,0 +1,13 @@ +export const productionOverrides = { + logLevel: 'warn', + dryRun: false, + cacheTtlSeconds: 15, + updateIntervalMs: 15000, + maxPriceDeviationPercent: 5, + priceStaleThresholdSeconds: 120, + circuitBreaker: { + failureThreshold: 3, + backoffMs: 30000, + }, +}; +//# sourceMappingURL=production.js.map \ No newline at end of file diff --git a/oracle/dist/config/production.js.map b/oracle/dist/config/production.js.map new file mode 100644 index 00000000..b0847efe --- /dev/null +++ b/oracle/dist/config/production.js.map @@ -0,0 +1 @@ +{"version":3,"file":"production.js","sourceRoot":"","sources":["../../src/config/production.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mBAAmB,GAAiC;IAC/D,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,KAAK;IACb,eAAe,EAAE,EAAE;IACnB,gBAAgB,EAAE,KAAK;IACvB,wBAAwB,EAAE,CAAC;IAC3B,0BAA0B,EAAE,GAAG;IAC/B,cAAc,EAAE;QACd,gBAAgB,EAAE,CAAC;QACnB,SAAS,EAAE,KAAK;KACjB;CACF,CAAC"} \ No newline at end of file diff --git a/oracle/dist/devtools/trace-analysis.d.ts b/oracle/dist/devtools/trace-analysis.d.ts new file mode 100644 index 00000000..329e3b3b --- /dev/null +++ b/oracle/dist/devtools/trace-analysis.d.ts @@ -0,0 +1,71 @@ +export interface ContractStateChange { + contractId?: string; + key: string; + operation: 'create' | 'update' | 'delete' | 'extend'; + before?: string | null; + after?: string | null; +} +export interface ContractInvocationTrace { + contractId: string; + functionName: string; + gasUsed: number; + cpuInstructions?: number; + memoryBytes?: number; + events?: string[]; + stateChanges?: ContractStateChange[]; + children?: ContractInvocationTrace[]; +} +export interface ContractTraceSnapshot { + transactionHash?: string; + network?: string; + ledger?: number; + elapsedMs?: number; + tracingElapsedMs?: number; + invocations: ContractInvocationTrace[]; +} +export interface TraceCallFrame { + depth: number; + path: string; + contractId: string; + functionName: string; + gasUsed: number; + cumulativeGasUsed: number; + cpuInstructions: number; + memoryBytes: number; + stateChangeCount: number; +} +export interface TraceHotPath { + path: string; + gasUsed: number; + percentageOfTotalGas: number; +} +export interface TraceOverhead { + baselineMs: number; + tracingMs: number; + deltaMs: number; + overheadPercent: number; +} +export interface TraceAnalysis { + transactionHash?: string; + network?: string; + ledger?: number; + totalGasUsed: number; + totalCpuInstructions: number; + totalMemoryBytes: number; + maxDepth: number; + totalInvocations: number; + totalStateChanges: number; + callFrames: TraceCallFrame[]; + hotPaths: TraceHotPath[]; + stateChanges: ContractStateChange[]; + warnings: string[]; + traceOverhead?: TraceOverhead; +} +interface AnalyzeTraceOptions { + hotPathLimit?: number; + largeTraceThreshold?: number; +} +export declare function calculateTraceOverhead(baselineMs: number | undefined, tracingMs: number | undefined): TraceOverhead | undefined; +export declare function analyzeTrace(snapshot: ContractTraceSnapshot, options?: AnalyzeTraceOptions): TraceAnalysis; +export {}; +//# sourceMappingURL=trace-analysis.d.ts.map \ No newline at end of file diff --git a/oracle/dist/devtools/trace-analysis.d.ts.map b/oracle/dist/devtools/trace-analysis.d.ts.map new file mode 100644 index 00000000..e38c1369 --- /dev/null +++ b/oracle/dist/devtools/trace-analysis.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"trace-analysis.d.ts","sourceRoot":"","sources":["../../src/devtools/trace-analysis.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACrD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,QAAQ,CAAC,EAAE,uBAAuB,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,uBAAuB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,YAAY,EAAE,mBAAmB,EAAE,CAAC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,UAAU,mBAAmB;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AA2CD,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,aAAa,GAAG,SAAS,CAiB3B;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,GAAE,mBAAwB,GAChC,aAAa,CAkFf"} \ No newline at end of file diff --git a/oracle/dist/devtools/trace-analysis.js b/oracle/dist/devtools/trace-analysis.js new file mode 100644 index 00000000..4bb74a4f --- /dev/null +++ b/oracle/dist/devtools/trace-analysis.js @@ -0,0 +1,109 @@ +function collectFrames(invocation, depth, ancestry, frames, stateChanges) { + const pathParts = [...ancestry, `${invocation.contractId}.${invocation.functionName}`]; + const path = pathParts.join(' > '); + const localStateChanges = (invocation.stateChanges ?? []).map((change) => ({ + ...change, + contractId: change.contractId ?? invocation.contractId, + })); + stateChanges.push(...localStateChanges); + let cumulativeGasUsed = invocation.gasUsed; + let maxDepth = depth; + let totalInvocations = 1; + for (const child of invocation.children ?? []) { + const childResult = collectFrames(child, depth + 1, pathParts, frames, stateChanges); + cumulativeGasUsed += childResult.cumulativeGasUsed; + maxDepth = Math.max(maxDepth, childResult.maxDepth); + totalInvocations += childResult.totalInvocations; + } + frames.push({ + depth, + path, + contractId: invocation.contractId, + functionName: invocation.functionName, + gasUsed: invocation.gasUsed, + cumulativeGasUsed, + cpuInstructions: invocation.cpuInstructions ?? 0, + memoryBytes: invocation.memoryBytes ?? 0, + stateChangeCount: localStateChanges.length, + }); + return { cumulativeGasUsed, maxDepth, totalInvocations }; +} +export function calculateTraceOverhead(baselineMs, tracingMs) { + if (baselineMs === undefined || + tracingMs === undefined || + baselineMs <= 0 || + tracingMs < baselineMs) { + return undefined; + } + const deltaMs = tracingMs - baselineMs; + return { + baselineMs, + tracingMs, + deltaMs, + overheadPercent: Number(((deltaMs / baselineMs) * 100).toFixed(2)), + }; +} +export function analyzeTrace(snapshot, options = {}) { + const hotPathLimit = options.hotPathLimit ?? 5; + const largeTraceThreshold = options.largeTraceThreshold ?? 250; + const callFrames = []; + const stateChanges = []; + let totalGasUsed = 0; + let maxDepth = 0; + let totalInvocations = 0; + for (const invocation of snapshot.invocations) { + const result = collectFrames(invocation, 0, [], callFrames, stateChanges); + totalGasUsed += result.cumulativeGasUsed; + maxDepth = Math.max(maxDepth, result.maxDepth); + totalInvocations += result.totalInvocations; + } + callFrames.sort((left, right) => { + if (left.depth !== right.depth) { + return left.depth - right.depth; + } + return left.path.localeCompare(right.path); + }); + const totalCpuInstructions = callFrames.reduce((sum, frame) => sum + frame.cpuInstructions, 0); + const totalMemoryBytes = callFrames.reduce((sum, frame) => sum + frame.memoryBytes, 0); + const hotPaths = [...callFrames] + .sort((left, right) => right.cumulativeGasUsed - left.cumulativeGasUsed) + .slice(0, hotPathLimit) + .map((frame) => ({ + path: frame.path, + gasUsed: frame.cumulativeGasUsed, + percentageOfTotalGas: totalGasUsed === 0 + ? 0 + : Number(((frame.cumulativeGasUsed / totalGasUsed) * 100).toFixed(2)), + })); + const warnings = []; + if (totalInvocations > largeTraceThreshold) { + warnings.push(`Trace contains ${totalInvocations} invocations; consider filtering by contract or function before storing it long term.`); + } + if (stateChanges.length > 500) { + warnings.push('Trace contains more than 500 state changes; archive the raw trace instead of logging it inline.'); + } + if (callFrames.some((frame) => frame.gasUsed === 0 && frame.cumulativeGasUsed > 0)) { + warnings.push('One or more frames reported zero local gas. Check the RPC simulator payload before using this trace for regression analysis.'); + } + const traceOverhead = calculateTraceOverhead(snapshot.elapsedMs, snapshot.tracingElapsedMs); + if (traceOverhead && traceOverhead.overheadPercent > 25) { + warnings.push(`Trace overhead is ${traceOverhead.overheadPercent}%; disable tracing for load tests and keep it to focused debugging sessions.`); + } + return { + transactionHash: snapshot.transactionHash, + network: snapshot.network, + ledger: snapshot.ledger, + totalGasUsed, + totalCpuInstructions, + totalMemoryBytes, + maxDepth, + totalInvocations, + totalStateChanges: stateChanges.length, + callFrames, + hotPaths, + stateChanges, + warnings, + traceOverhead, + }; +} +//# sourceMappingURL=trace-analysis.js.map \ No newline at end of file diff --git a/oracle/dist/devtools/trace-analysis.js.map b/oracle/dist/devtools/trace-analysis.js.map new file mode 100644 index 00000000..c65aac27 --- /dev/null +++ b/oracle/dist/devtools/trace-analysis.js.map @@ -0,0 +1 @@ +{"version":3,"file":"trace-analysis.js","sourceRoot":"","sources":["../../src/devtools/trace-analysis.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/devtools/trace-report.d.ts b/oracle/dist/devtools/trace-report.d.ts new file mode 100644 index 00000000..2c7cf16f --- /dev/null +++ b/oracle/dist/devtools/trace-report.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=trace-report.d.ts.map \ No newline at end of file diff --git a/oracle/dist/devtools/trace-report.d.ts.map b/oracle/dist/devtools/trace-report.d.ts.map new file mode 100644 index 00000000..fe3f6835 --- /dev/null +++ b/oracle/dist/devtools/trace-report.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"trace-report.d.ts","sourceRoot":"","sources":["../../src/devtools/trace-report.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/oracle/dist/devtools/trace-report.js b/oracle/dist/devtools/trace-report.js new file mode 100644 index 00000000..3fd2fe90 --- /dev/null +++ b/oracle/dist/devtools/trace-report.js @@ -0,0 +1,20 @@ +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { analyzeTrace } from './trace-analysis.js'; +async function main() { + const traceFile = process.argv[2]; + if (!traceFile) { + console.error('Usage: npm run trace:analyze -- '); + process.exitCode = 1; + return; + } + const rawTrace = await readFile(resolve(traceFile), 'utf8'); + const snapshot = JSON.parse(rawTrace); + const analysis = analyzeTrace(snapshot); + console.log(JSON.stringify(analysis, null, 2)); +} +main().catch((error) => { + console.error('Failed to analyze trace', error); + process.exitCode = 1; +}); +//# sourceMappingURL=trace-report.js.map \ No newline at end of file diff --git a/oracle/dist/devtools/trace-report.js.map b/oracle/dist/devtools/trace-report.js.map new file mode 100644 index 00000000..37e5fff8 --- /dev/null +++ b/oracle/dist/devtools/trace-report.js.map @@ -0,0 +1 @@ +{"version":3,"file":"trace-report.js","sourceRoot":"","sources":["../../src/devtools/trace-report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAA8B,MAAM,qBAAqB,CAAC;AAE/E,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAElC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACnE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAA0B,CAAC;IAC/D,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IAExC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/oracle/dist/index.d.ts b/oracle/dist/index.d.ts new file mode 100644 index 00000000..b8d89732 --- /dev/null +++ b/oracle/dist/index.d.ts @@ -0,0 +1,92 @@ +/** + * StellarLend Oracle Service + * Off-chain oracle integration service that fetches price data from + * multiple sources (CoinGecko, Binance) + * @see https://github.com/stellarlend/stellarlend-contracts + */ +import { type OracleServiceConfig } from './config.js'; +/** + * Oracle Service + */ +export declare class OracleService { + private config; + private aggregator; + private contractUpdater; + private providers; + private intervalId?; + private isRunning; + private lastSuccessfulUpdate; + constructor(config: OracleServiceConfig); + /** + * Start the oracle service + */ + start(assets?: string[]): Promise; + /** + * Stop the oracle service + */ + stop(): void; + /** + * Fetch and update prices for specified assets + */ + updatePrices(assets: string[]): Promise; + /** + * Get current service status (safe for logging — secret key is masked) + */ + getStatus(): { + isRunning: boolean; + network: "testnet" | "mainnet"; + contractId: string; + adminSecretKey: string; + providers: { + name: string; + enabled: boolean; + priority: number; + weight: number; + apiKey?: string; + baseUrl: string; + rateLimit: { + maxRequests: number; + windowMs: number; + }; + concurrencyLimit?: number; + }[]; + aggregatorStats: { + enabledProviders: number; + cacheStats: { + size: number; + hits: number; + misses: number; + hitRate: number; + evictions: number; + }; + priceHistoryStats: { + trackedAssets: number; + totalEntries: number; + maxEntriesPerAsset: number; + assets: string[]; + }; + circuitBreakerMetrics: (import("./services/circuit-breaker.js").CircuitBreakerMetrics & { + providerName: string; + state: import("./services/circuit-breaker.js").CircuitState; + })[]; + circuitBreakers: (import("./services/circuit-breaker.js").CircuitBreakerMetrics & { + providerName: string; + state: import("./services/circuit-breaker.js").CircuitState; + })[]; + }; + circuitBreakers: (import("./services/circuit-breaker.js").CircuitBreakerMetrics & { + providerName: string; + state: import("./services/circuit-breaker.js").CircuitState; + })[]; + }; + /** + * Manually fetch price for a single asset (for testing) + */ + fetchPrice(asset: string): Promise; + private validateConfig; + private normalizeProviders; + private createRuntimeProviders; +} +export { loadConfig, maskSecret, getSafeConfig } from './config.js'; +export type { OracleServiceConfig } from './config.js'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/oracle/dist/index.d.ts.map b/oracle/dist/index.d.ts.map new file mode 100644 index 00000000..714d837e --- /dev/null +++ b/oracle/dist/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAyClF;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,UAAU,CAAC,CAAiC;IACpD,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,oBAAoB,CAAuB;gBAEvC,MAAM,EAAE,mBAAmB;IA8CvC;;OAEG;IACG,KAAK,CAAC,MAAM,GAAE,MAAM,EAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB7D;;OAEG;IACH,IAAI,IAAI,IAAI;IAeZ;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAwEnD;;OAEG;IACH,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAaT;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM;IAI9B,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,sBAAsB;CAoB/B;AAuDD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"} \ No newline at end of file diff --git a/oracle/dist/index.js b/oracle/dist/index.js new file mode 100644 index 00000000..91d878f9 --- /dev/null +++ b/oracle/dist/index.js @@ -0,0 +1,271 @@ +/** + * StellarLend Oracle Service + * Off-chain oracle integration service that fetches price data from + * multiple sources (CoinGecko, Binance) + * @see https://github.com/stellarlend/stellarlend-contracts + */ +import { fileURLToPath } from 'node:url'; +import { loadConfig, getSafeConfig } from './config.js'; +import { configureLogger, logger, logStalenessAlert } from './utils/logger.js'; +import { createCoinGeckoProvider, createBinanceProvider, } from './providers/index.js'; +import { createValidator, createPriceCache, createPriceHistoryService, createAggregator, createContractUpdater, } from './services/index.js'; +/** + * Default assets to fetch prices for + */ +const DEFAULT_ASSETS = ['XLM', 'USDC', 'BTC', 'ETH', 'SOL']; +function serializePricesForLog(prices) { + return prices.map((price) => ({ + asset: price.asset, + price: price.price.toString(), + timestamp: price.timestamp, + confidence: price.confidence, + sources: price.sources.map((source) => source.source), + })); +} +/** + * Oracle Service + */ +export class OracleService { + config; + aggregator; + contractUpdater; + providers; + intervalId; + isRunning = false; + lastSuccessfulUpdate = null; + constructor(config) { + this.validateConfig(config); + // Store config but never log adminSecretKey directly + this.config = config; + this.providers = this.normalizeProviders(config.providers); + // Configure logging + configureLogger(config.logLevel); + // Create runtime providers for supported integrations only. + const providers = this.createRuntimeProviders(this.providers); + // Create services + const validator = createValidator({ + maxDeviationPercent: config.maxPriceDeviationPercent, + maxStalenessSeconds: config.priceStaleThresholdSeconds, + }); + const cache = createPriceCache(config.cacheTtlSeconds); + const priceHistory = createPriceHistoryService(); + this.aggregator = createAggregator(providers, validator, cache, priceHistory, { + circuitBreaker: config.circuitBreaker, + }); + this.contractUpdater = createContractUpdater({ + network: config.stellarNetwork, + rpcUrl: config.stellarRpcUrl, + contractId: config.contractId, + adminSecretKey: config.adminSecretKey, + baseFee: config.baseFee, + maxFee: config.maxFee, + maxRetries: 3, + retryDelayMs: 1000, + }); + logger.info('Oracle service initialized', { + network: config.stellarNetwork, + contractId: config.contractId, + dryRun: !!config.dryRun, + updateInterval: config.updateIntervalMs, + providers: this.providers.map((provider) => provider.name), + }); + } + /** + * Start the oracle service + */ + async start(assets = DEFAULT_ASSETS) { + if (this.isRunning) { + logger.warn('Oracle service is already running'); + return; + } + this.isRunning = true; + logger.info('Starting oracle service', { assets }); + // Run immediately on start + await this.updatePrices(assets); + // Schedule periodic updates + this.intervalId = setInterval(async () => { + await this.updatePrices(assets); + }, this.config.updateIntervalMs); + logger.info('Oracle service started', { + intervalMs: this.config.updateIntervalMs, + }); + } + /** + * Stop the oracle service + */ + stop() { + if (!this.isRunning) { + logger.warn('Oracle service is not running'); + return; + } + if (this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = undefined; + } + this.isRunning = false; + logger.info('Oracle service stopped'); + } + /** + * Fetch and update prices for specified assets + */ + async updatePrices(assets) { + const startTime = Date.now(); + logger.info('Starting price update cycle', { assets }); + // Check for staleness + if (this.lastSuccessfulUpdate) { + const ageSeconds = (Date.now() - this.lastSuccessfulUpdate) / 1000; + const thresholdSeconds = this.config.priceStaleThresholdSeconds; + if (ageSeconds > thresholdSeconds) { + logStalenessAlert(ageSeconds, thresholdSeconds, this.lastSuccessfulUpdate); + } + } + try { + // Fetch aggregated prices + const prices = await this.aggregator.getPrices(assets); + if (prices.size === 0) { + logger.error('No prices fetched from any provider'); + return; + } + logger.info(`Fetched ${prices.size} prices`, { + assets: Array.from(prices.keys()), + }); + const priceArray = Array.from(prices.values()); + const serializedPrices = serializePricesForLog(priceArray); + if (this.config.dryRun) { + this.lastSuccessfulUpdate = Date.now(); + logger.info('DRY RUN: Would update prices on contract', { + assets: serializedPrices.map((price) => price.asset), + prices: serializedPrices, + durationMs: Date.now() - startTime, + contractId: this.config.contractId, + dryRun: true, + }); + return; + } + // Update contract + const results = await this.contractUpdater.updatePrices(priceArray); + // Log results + const successful = results.filter((r) => r.success); + const failed = results.filter((r) => !r.success); + logger.info('Price update cycle complete', { + successful: successful.length, + failed: failed.length, + durationMs: Date.now() - startTime, + }); + if (successful.length > 0) { + this.lastSuccessfulUpdate = Date.now(); + } + if (failed.length > 0) { + logger.warn('Some price updates failed', { + failedAssets: failed.map((f) => f.asset), + }); + } + } + catch (error) { + logger.error('Price update cycle failed', { error }); + } + } + /** + * Get current service status (safe for logging — secret key is masked) + */ + getStatus() { + const safe = getSafeConfig(this.config); + return { + isRunning: this.isRunning, + network: safe.stellarNetwork, + contractId: safe.contractId, + adminSecretKey: safe.adminSecretKey, // masked value + providers: this.providers.map((provider) => ({ ...provider })), + aggregatorStats: this.aggregator.getStats(), + circuitBreakers: this.aggregator.getCircuitBreakerMetrics?.() ?? [], + }; + } + /** + * Manually fetch price for a single asset (for testing) + */ + async fetchPrice(asset) { + return this.aggregator.getPrice(asset); + } + validateConfig(config) { + if (config.stellarNetwork !== 'testnet' && config.stellarNetwork !== 'mainnet') { + throw new Error(`Invalid stellar network: ${String(config.stellarNetwork)}`); + } + try { + new URL(config.stellarRpcUrl); + } + catch { + throw new Error('Invalid stellar RPC URL'); + } + if (!config.contractId?.trim()) { + throw new Error('Contract ID is required'); + } + } + normalizeProviders(providers) { + if (providers.length === 1) { + return [{ ...providers[0], weight: 1 }]; + } + return providers.map((provider) => ({ ...provider })); + } + createRuntimeProviders(configuredProviders) { + const runtimeProviders = []; + for (const provider of configuredProviders) { + if (!provider.enabled) { + continue; + } + if (provider.name === 'coingecko') { + runtimeProviders.push(createCoinGeckoProvider(provider.apiKey)); + continue; + } + if (provider.name === 'binance') { + runtimeProviders.push(createBinanceProvider()); + } + } + return runtimeProviders; + } +} +/** + * Main entry point + */ +async function main() { + logger.info('Starting StellarLend Oracle Service'); + try { + // Load configuration + const config = loadConfig(); + // Create and start service + const service = new OracleService(config); + // Handle shutdown + process.on('SIGINT', () => { + logger.info('Received SIGINT, shutting down...'); + service.stop(); + process.exit(0); + }); + process.on('SIGTERM', () => { + logger.info('Received SIGTERM, shutting down...'); + service.stop(); + process.exit(0); + }); + // Start service + await service.start(); + } + catch (error) { + logger.error('Failed to start oracle service', { error }); + process.exit(1); + } +} +function isExecutedDirectly() { + const entryFile = process.argv[1]; + if (!entryFile) { + return false; + } + return fileURLToPath(import.meta.url) === entryFile; +} +// Run only when executed as the entrypoint, not when imported by tests/modules +if (isExecutedDirectly()) { + main().catch((error) => { + logger.error('Unhandled oracle service error', { error }); + process.exit(1); + }); +} +// Export for programmatic use +export { loadConfig, maskSecret, getSafeConfig } from './config.js'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/oracle/dist/index.js.map b/oracle/dist/index.js.map new file mode 100644 index 00000000..08cb0769 --- /dev/null +++ b/oracle/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,aAAa,EAA4B,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,EAAqB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GAEtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,yBAAyB,EACzB,gBAAgB,EAChB,qBAAqB,GAGtB,MAAM,qBAAqB,CAAC;AAG7B;;GAEG;AACH,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAE5D,SAAS,qBAAqB,CAC5B,MAMG;IAEH,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE;QAC7B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;KACtD,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,aAAa;IAChB,MAAM,CAAsB;IAC5B,UAAU,CAAkB;IAC5B,eAAe,CAAkB;IACjC,SAAS,CAAmB;IAC5B,UAAU,CAAkC;IAC5C,SAAS,GAAY,KAAK,CAAC;IAC3B,oBAAoB,GAAkB,IAAI,CAAC;IAEnD,YAAY,MAA2B;QACrC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAE5B,qDAAqD;QACrD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAE3D,oBAAoB;QACpB,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAEjC,4DAA4D;QAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAE9D,kBAAkB;QAClB,MAAM,SAAS,GAAG,eAAe,CAAC;YAChC,mBAAmB,EAAE,MAAM,CAAC,wBAAwB;YACpD,mBAAmB,EAAE,MAAM,CAAC,0BAA0B;SACvD,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QACvD,MAAM,YAAY,GAAG,yBAAyB,EAAE,CAAC;QAEjD,IAAI,CAAC,UAAU,GAAG,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE;YAC5E,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,GAAG,qBAAqB,CAAC;YAC3C,OAAO,EAAE,MAAM,CAAC,cAAc;YAC9B,MAAM,EAAE,MAAM,CAAC,aAAa;YAC5B,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,UAAU,EAAE,CAAC;YACb,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE;YACxC,OAAO,EAAE,MAAM,CAAC,cAAc;YAC9B,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM;YACvB,cAAc,EAAE,MAAM,CAAC,gBAAgB;YACvC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;SAC3D,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK,CAAC,SAAmB,cAAc;QAC3C,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;YACjD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAEnD,2BAA2B;QAC3B,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAEhC,4BAA4B;QAC5B,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YACvC,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAEjC,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACpC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB;SACzC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAAC,MAAgB;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAEvD,sBAAsB;QACtB,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC9B,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC;YACnE,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,0BAA0B,CAAC;YAEhE,IAAI,UAAU,GAAG,gBAAgB,EAAE,CAAC;gBAClC,iBAAiB,CAAC,UAAU,EAAE,gBAAgB,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAC7E,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,0BAA0B;YAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAEvD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACtB,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;gBACpD,OAAO;YACT,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,IAAI,SAAS,EAAE;gBAC3C,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;aAClC,CAAC,CAAC;YAEH,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YAC/C,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;YAE3D,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACvB,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAEvC,MAAM,CAAC,IAAI,CAAC,0CAA0C,EAAE;oBACtD,MAAM,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC;oBACpD,MAAM,EAAE,gBAAgB;oBACxB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;oBAClC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;oBAClC,MAAM,EAAE,IAAI;iBACb,CAAC,CAAC;gBAEH,OAAO;YACT,CAAC;YAED,kBAAkB;YAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;YAEpE,cAAc;YACd,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAEjD,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE;gBACzC,UAAU,EAAE,UAAU,CAAC,MAAM;gBAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;aACnC,CAAC,CAAC;YAEH,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzC,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;oBACvC,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;iBACzC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS;QACP,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,IAAI,CAAC,cAAc;YAC5B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe;YACpD,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;YAC9D,eAAe,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE;YAC3C,eAAe,EAAE,IAAI,CAAC,UAAU,CAAC,wBAAwB,EAAE,EAAE,IAAI,EAAE;SACpE,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,KAAa;QAC5B,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;IAEO,cAAc,CAAC,MAA2B;QAChD,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YAC/E,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QAC/E,CAAC;QAED,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,SAA2B;QACpD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAC1C,CAAC;QAED,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,CAAC;IAEO,sBAAsB,CAAC,mBAAqC;QAClE,MAAM,gBAAgB,GAAwB,EAAE,CAAC;QAEjD,KAAK,MAAM,QAAQ,IAAI,mBAAmB,EAAE,CAAC;YAC3C,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACtB,SAAS;YACX,CAAC;YAED,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAClC,gBAAgB,CAAC,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;gBAChE,SAAS;YACX,CAAC;YAED,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAChC,gBAAgB,CAAC,IAAI,CAAC,qBAAqB,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAED,OAAO,gBAAgB,CAAC;IAC1B,CAAC;CACF;AAED;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IAEnD,IAAI,CAAC;QACH,qBAAqB;QACrB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAE5B,2BAA2B;QAC3B,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;QAE1C,kBAAkB;QAClB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YACxB,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;YACjD,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACzB,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QAEH,gBAAgB;QAChB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAElC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;AACtD,CAAC;AAED,+EAA+E;AAC/E,IAAI,kBAAkB,EAAE,EAAE,CAAC;IACzB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACrB,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8BAA8B;AAC9B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"} \ No newline at end of file diff --git a/oracle/dist/providers/base-provider.d.ts b/oracle/dist/providers/base-provider.d.ts new file mode 100644 index 00000000..845dd8ff --- /dev/null +++ b/oracle/dist/providers/base-provider.d.ts @@ -0,0 +1,62 @@ +/** + * Base Price Provider + * + * Abstract base class for all price data providers. + * Implements common functionality like rate limiting and error handling. + */ +import type { RawPriceData, ProviderConfig, HealthStatus } from '../types/index.js'; +/** + * Abstract base class for price providers + */ +export declare abstract class BasePriceProvider { + protected config: ProviderConfig; + protected requestTimestamps: number[]; + private rateLimitChain; + constructor(config: ProviderConfig); + /** + * Get provider name + */ + get name(): string; + /** + * Get provider priority + */ + get priority(): number; + /** + * Get the provider weight for aggregation + */ + get weight(): number; + /** + * Check if the provider is enabled + */ + get isEnabled(): boolean; + /** + * Fetch price for a specific asset + * Must be implemented by each provider + */ + abstract fetchPrice(asset: string): Promise; + /** + * Fetch prices for multiple assets in parallel with a concurrency limit. + * Failed fetches are logged and skipped without blocking successful ones. + */ + fetchPrices(assets: string[]): Promise; + /** + * Check provider health + */ + healthCheck(): Promise; + /** + * Enforce rate limiting + */ + protected enforceRateLimit(): Promise; + private enforceRateLimitInternal; + /** + * Sleep util + */ + protected sleep(ms: number): Promise; + /** + * Make HTTP request using axios with IPv4 forced + */ + protected request(url: string, options?: { + headers?: Record; + }): Promise; +} +//# sourceMappingURL=base-provider.d.ts.map \ No newline at end of file diff --git a/oracle/dist/providers/base-provider.d.ts.map b/oracle/dist/providers/base-provider.d.ts.map new file mode 100644 index 00000000..ef4890b0 --- /dev/null +++ b/oracle/dist/providers/base-provider.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"base-provider.d.ts","sourceRoot":"","sources":["../../src/providers/base-provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAYpF;;GAEG;AACH,8BAAsB,iBAAiB;IACrC,SAAS,CAAC,MAAM,EAAE,cAAc,CAAC;IAGjC,SAAS,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAM;IAC3C,OAAO,CAAC,cAAc,CAAoC;gBAE9C,MAAM,EAAE,cAAc;IAIlC;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAEzD;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAwB5D;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC;IAuB1C;;OAEG;cACa,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;YAQnC,wBAAwB;IA0BtC;;OAEG;IACH,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C;;OAEG;cACa,OAAO,CAAC,CAAC,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAO,GACjD,OAAO,CAAC,CAAC,CAAC;CAYd"} \ No newline at end of file diff --git a/oracle/dist/providers/base-provider.js b/oracle/dist/providers/base-provider.js new file mode 100644 index 00000000..d9a5f25b --- /dev/null +++ b/oracle/dist/providers/base-provider.js @@ -0,0 +1,154 @@ +/** + * Base Price Provider + * + * Abstract base class for all price data providers. + * Implements common functionality like rate limiting and error handling. + */ +import axios from 'axios'; +import https from 'https'; +import { logger } from '../utils/logger.js'; +/** + * HTTPS Agent + */ +const httpsAgent = new https.Agent({ + family: 4, + keepAlive: true, + timeout: 30000, +}); +/** + * Abstract base class for price providers + */ +export class BasePriceProvider { + config; + // Sliding-window rate limit state: store timestamps (ms) for recent requests. + // We keep timestamps for requests that are still within the active window. + requestTimestamps = []; + rateLimitChain = Promise.resolve(); + constructor(config) { + this.config = config; + } + /** + * Get provider name + */ + get name() { + return this.config.name; + } + /** + * Get provider priority + */ + get priority() { + return this.config.priority; + } + /** + * Get the provider weight for aggregation + */ + get weight() { + return this.config.weight; + } + /** + * Check if the provider is enabled + */ + get isEnabled() { + return this.config.enabled; + } + /** + * Fetch prices for multiple assets in parallel with a concurrency limit. + * Failed fetches are logged and skipped without blocking successful ones. + */ + async fetchPrices(assets) { + const concurrency = this.config.concurrencyLimit ?? 5; + const results = []; + for (let i = 0; i < assets.length; i += concurrency) { + const batch = assets.slice(i, i + concurrency); + const settled = await Promise.allSettled(batch.map(async (asset) => { + await this.enforceRateLimit(); + return this.fetchPrice(asset); + })); + for (const outcome of settled) { + if (outcome.status === 'fulfilled') { + results.push(outcome.value); + } + else { + logger.error(`Failed to fetch price from ${this.name}`, { error: outcome.reason }); + } + } + } + return results; + } + /** + * Check provider health + */ + async healthCheck() { + const startTime = Date.now(); + try { + await this.fetchPrice('XLM'); + return { + provider: this.name, + healthy: true, + lastCheck: Date.now(), + latencyMs: Date.now() - startTime, + }; + } + catch (error) { + return { + provider: this.name, + healthy: false, + lastCheck: Date.now(), + latencyMs: Date.now() - startTime, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } + } + /** + * Enforce rate limiting + */ + async enforceRateLimit() { + // Serialize rate-limit state updates so concurrent requests cannot + // all pass the same counter check in parallel. + const run = this.rateLimitChain.then(() => this.enforceRateLimitInternal()); + this.rateLimitChain = run.catch(() => undefined); + await run; + } + async enforceRateLimitInternal() { + const { maxRequests, windowMs } = this.config.rateLimit; + // Loop until the request can be accepted under a moving window. + // We use an inclusive window definition (requests at exactly `windowMs` + // age are still counted) to prevent boundary bursts. + // eslint-disable-next-line no-constant-condition + while (true) { + const now = Date.now(); + // Keep requests that are within the last windowMs (inclusive). + // That means remove only those strictly older than windowMs. + this.requestTimestamps = this.requestTimestamps.filter((t) => now - t <= windowMs); + if (this.requestTimestamps.length < maxRequests) { + this.requestTimestamps.push(now); + return; + } + const earliest = this.requestTimestamps[0]; + const waitTime = Math.max(0, earliest + windowMs - now + 1); + logger.warn(`Rate limit reached for ${this.name}, waiting ${waitTime}ms`); + await this.sleep(waitTime); + } + } + /** + * Sleep util + */ + sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + /** + * Make HTTP request using axios with IPv4 forced + */ + async request(url, options = {}) { + const response = await axios.get(url, { + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + timeout: 30000, + httpsAgent, + }); + return response.data; + } +} +//# sourceMappingURL=base-provider.js.map \ No newline at end of file diff --git a/oracle/dist/providers/base-provider.js.map b/oracle/dist/providers/base-provider.js.map new file mode 100644 index 00000000..b15df4b0 --- /dev/null +++ b/oracle/dist/providers/base-provider.js.map @@ -0,0 +1 @@ +{"version":3,"file":"base-provider.js","sourceRoot":"","sources":["../../src/providers/base-provider.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/providers/binance.d.ts b/oracle/dist/providers/binance.d.ts new file mode 100644 index 00000000..60489f84 --- /dev/null +++ b/oracle/dist/providers/binance.d.ts @@ -0,0 +1,38 @@ +/** + * Binance Price Provider + * + * Fallback price source using Binance's public API. + * No API key required for public market data. + * + * @see https://binance-docs.github.io/apidocs/spot/en/ + */ +import { BasePriceProvider } from './base-provider.js'; +import type { RawPriceData, ProviderConfig } from '../types/index.js'; +/** + * Binance Price Provider + */ +export declare class BinanceProvider extends BasePriceProvider { + constructor(config: ProviderConfig); + /** + * Map asset symbol to Binance trading pair + */ + private getBinanceSymbol; + /** + * Fetch price for a specific asset + */ + fetchPrice(asset: string): Promise; + /** + * Fetch prices for multiple assets + * Uses batch ticker endpoint for efficiency + */ + fetchPrices(assets: string[]): Promise; + /** + * Get supported assets + */ + getSupportedAssets(): string[]; +} +/** + * Create a Binance provider with default configuration + */ +export declare function createBinanceProvider(): BinanceProvider; +//# sourceMappingURL=binance.d.ts.map \ No newline at end of file diff --git a/oracle/dist/providers/binance.d.ts.map b/oracle/dist/providers/binance.d.ts.map new file mode 100644 index 00000000..f05bd467 --- /dev/null +++ b/oracle/dist/providers/binance.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"binance.d.ts","sourceRoot":"","sources":["../../src/providers/binance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAsCtE;;GAEG;AACH,qBAAa,eAAgB,SAAQ,iBAAiB;gBACxC,MAAM,EAAE,cAAc;IAQlC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAQxB;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAsBtD;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAyD5D;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;CAG/B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAcvD"} \ No newline at end of file diff --git a/oracle/dist/providers/binance.js b/oracle/dist/providers/binance.js new file mode 100644 index 00000000..e89f66d8 --- /dev/null +++ b/oracle/dist/providers/binance.js @@ -0,0 +1,145 @@ +/** + * Binance Price Provider + * + * Fallback price source using Binance's public API. + * No API key required for public market data. + * + * @see https://binance-docs.github.io/apidocs/spot/en/ + */ +import { BasePriceProvider } from './base-provider.js'; +import { logger } from '../utils/logger.js'; +/** + * Asset to Binance symbol mapping + * All pairs are quoted against USDT for USD-equivalent pricing + */ +const BINANCE_SYMBOL_MAP = { + XLM: 'XLMUSDT', + USDC: 'USDCUSDT', + BTC: 'BTCUSDT', + ETH: 'ETHUSDT', + SOL: 'SOLUSDT', + AVAX: 'AVAXUSDT', + DOT: 'DOTUSDT', + MATIC: 'MATICUSDT', + LINK: 'LINKUSDT', + ADA: 'ADAUSDT', + DOGE: 'DOGEUSDT', +}; +/** + * Binance Price Provider + */ +export class BinanceProvider extends BasePriceProvider { + constructor(config) { + super(config); + logger.info('Binance provider initialized', { + baseUrl: config.baseUrl, + }); + } + /** + * Map asset symbol to Binance trading pair + */ + getBinanceSymbol(asset) { + const symbol = BINANCE_SYMBOL_MAP[asset.toUpperCase()]; + if (!symbol) { + throw new Error(`Asset ${asset} not mapped for Binance`); + } + return symbol; + } + /** + * Fetch price for a specific asset + */ + async fetchPrice(asset) { + const symbol = this.getBinanceSymbol(asset); + await this.enforceRateLimit(); + const url = `${this.config.baseUrl}/ticker/24hr?symbol=${symbol}`; + try { + const response = await this.request(url); + return { + asset: asset.toUpperCase(), + price: parseFloat(response.lastPrice), + timestamp: Math.floor(response.closeTime / 1000), + source: 'binance', + }; + } + catch (error) { + logger.error(`Binance fetch failed for ${asset}`, { error }); + throw error; + } + } + /** + * Fetch prices for multiple assets + * Uses batch ticker endpoint for efficiency + */ + async fetchPrices(assets) { + const assetToSymbol = new Map(); + const validAssets = []; + for (const asset of assets) { + try { + const symbol = this.getBinanceSymbol(asset); + assetToSymbol.set(asset.toUpperCase(), symbol); + validAssets.push(asset.toUpperCase()); + } + catch { + logger.warn(`Skipping unsupported asset: ${asset}`); + } + } + if (validAssets.length === 0) { + return []; + } + await this.enforceRateLimit(); + const symbols = validAssets.map((a) => assetToSymbol.get(a)); + const symbolsParam = encodeURIComponent(JSON.stringify(symbols)); + const url = `${this.config.baseUrl}/ticker/price?symbols=${symbolsParam}`; + try { + const response = await this.request(url); + // For quick lookup + const symbolToPrice = new Map(); + for (const ticker of response) { + symbolToPrice.set(ticker.symbol, parseFloat(ticker.price)); + } + const results = []; + const now = Math.floor(Date.now() / 1000); + for (const asset of validAssets) { + const symbol = assetToSymbol.get(asset); + const price = symbolToPrice.get(symbol); + if (price !== undefined) { + results.push({ + asset, + price, + timestamp: now, + source: 'binance', + }); + } + } + return results; + } + catch (error) { + logger.error('Binance batch fetch failed', { error }); + throw error; + } + } + /** + * Get supported assets + */ + getSupportedAssets() { + return Object.keys(BINANCE_SYMBOL_MAP); + } +} +/** + * Create a Binance provider with default configuration + */ +export function createBinanceProvider() { + const config = { + name: 'binance', + enabled: true, + priority: 2, // Second priority (after CoinGecko) + weight: 0.4, + baseUrl: 'https://api.binance.com/api/v3', + rateLimit: { + maxRequests: 1200, + windowMs: 60000, + }, + }; + return new BinanceProvider(config); +} +//# sourceMappingURL=binance.js.map \ No newline at end of file diff --git a/oracle/dist/providers/binance.js.map b/oracle/dist/providers/binance.js.map new file mode 100644 index 00000000..6b71bcab --- /dev/null +++ b/oracle/dist/providers/binance.js.map @@ -0,0 +1 @@ +{"version":3,"file":"binance.js","sourceRoot":"","sources":["../../src/providers/binance.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/providers/coingecko.d.ts b/oracle/dist/providers/coingecko.d.ts new file mode 100644 index 00000000..627f0f3f --- /dev/null +++ b/oracle/dist/providers/coingecko.d.ts @@ -0,0 +1,52 @@ +/** + * CoinGecko Price Provider + * + * Fallback price source using CoinGecko's API. + * + * Supports: + * - Free tier (no API key): api.coingecko.com, 10-30 calls/min + * - Demo tier (CG-* key): api.coingecko.com with x-cg-demo-api-key header + * - Pro tier (other key): pro-api.coingecko.com with x-cg-pro-api-key header + * + * @see https://docs.coingecko.com/reference/simple-price + */ +import { BasePriceProvider } from './base-provider.js'; +import type { RawPriceData, ProviderConfig } from '../types/index.js'; +/** + * CoinGecko Price Provider + */ +export declare class CoinGeckoProvider extends BasePriceProvider { + private apiKey?; + private tier; + constructor(config: ProviderConfig); + /** + * Get the correct header name for the API key + */ + private getApiKeyHeader; + /** + * Map asset symbol to CoinGecko ID + */ + private getCoingeckoId; + /** + * Fetch price for a specific asset + */ + fetchPrice(asset: string): Promise; + /** + * Fetch prices for multiple assets (batch API call) + */ + fetchPrices(assets: string[]): Promise; + /** + * Get supported assets + */ + getSupportedAssets(): string[]; +} +/** + * Create a CoinGecko provider with default configuration + * + * API Key Types: + * - No key: Free tier (api.coingecko.com, 10-30 calls/min) + * - CG-* key: Demo tier (api.coingecko.com with demo header) + * - Other key: Pro tier (pro-api.coingecko.com with pro header) + */ +export declare function createCoinGeckoProvider(apiKey?: string): CoinGeckoProvider; +//# sourceMappingURL=coingecko.d.ts.map \ No newline at end of file diff --git a/oracle/dist/providers/coingecko.d.ts.map b/oracle/dist/providers/coingecko.d.ts.map new file mode 100644 index 00000000..3a4d94b6 --- /dev/null +++ b/oracle/dist/providers/coingecko.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"coingecko.d.ts","sourceRoot":"","sources":["../../src/providers/coingecko.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA0CtE;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,iBAAiB;IACtD,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAA0B;gBAE1B,MAAM,EAAE,cAAc;IAWlC;;OAEG;IACH,OAAO,CAAC,eAAe;IAIvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAgCtD;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAuD5D;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;CAG/B;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAqB1E"} \ No newline at end of file diff --git a/oracle/dist/providers/coingecko.js b/oracle/dist/providers/coingecko.js new file mode 100644 index 00000000..04af9888 --- /dev/null +++ b/oracle/dist/providers/coingecko.js @@ -0,0 +1,185 @@ +/** + * CoinGecko Price Provider + * + * Fallback price source using CoinGecko's API. + * + * Supports: + * - Free tier (no API key): api.coingecko.com, 10-30 calls/min + * - Demo tier (CG-* key): api.coingecko.com with x-cg-demo-api-key header + * - Pro tier (other key): pro-api.coingecko.com with x-cg-pro-api-key header + * + * @see https://docs.coingecko.com/reference/simple-price + */ +import { BasePriceProvider } from './base-provider.js'; +import { logger } from '../utils/logger.js'; +/** + * Asset to CoinGecko ID mapping + */ +const COINGECKO_ID_MAP = { + XLM: 'stellar', + USDC: 'usd-coin', + USDT: 'tether', + BTC: 'bitcoin', + ETH: 'ethereum', + SOL: 'solana', + AVAX: 'avalanche-2', + DOT: 'polkadot', + MATIC: 'matic-network', + LINK: 'chainlink', +}; +/** + * Determine API tier from API key + * - No key: Free tier + * - Key starting with CG-: Demo tier + * - Other key: Pro tier + */ +function getApiTier(apiKey) { + if (!apiKey) + return 'free'; + if (apiKey.startsWith('CG-')) + return 'demo'; + return 'pro'; +} +/** + * CoinGecko Price Provider + */ +export class CoinGeckoProvider extends BasePriceProvider { + apiKey; + tier; + constructor(config) { + super(config); + this.apiKey = config.apiKey; + this.tier = getApiTier(config.apiKey); + logger.info('CoinGecko provider initialized', { + tier: this.tier, + baseUrl: config.baseUrl, + }); + } + /** + * Get the correct header name for the API key + */ + getApiKeyHeader() { + return this.tier === 'pro' ? 'x-cg-pro-api-key' : 'x-cg-demo-api-key'; + } + /** + * Map asset symbol to CoinGecko ID + */ + getCoingeckoId(asset) { + const id = COINGECKO_ID_MAP[asset.toUpperCase()]; + if (!id) { + throw new Error(`Asset ${asset} not mapped for CoinGecko`); + } + return id; + } + /** + * Fetch price for a specific asset + */ + async fetchPrice(asset) { + const coinId = this.getCoingeckoId(asset); + await this.enforceRateLimit(); + const url = `${this.config.baseUrl}/simple/price?ids=${coinId}&vs_currencies=usd&include_last_updated_at=true`; + const headers = {}; + if (this.apiKey) { + headers[this.getApiKeyHeader()] = this.apiKey; + } + try { + const response = await this.request(url, { headers }); + const coinData = response[coinId]; + if (!coinData) { + throw new Error(`No price data returned for ${coinId}`); + } + return { + asset: asset.toUpperCase(), + price: coinData.usd, + timestamp: coinData.last_updated_at || Math.floor(Date.now() / 1000), + source: 'coingecko', + }; + } + catch (error) { + logger.error(`CoinGecko fetch failed for ${asset}`, { error }); + throw error; + } + } + /** + * Fetch prices for multiple assets (batch API call) + */ + async fetchPrices(assets) { + // Map all assets to CoinGecko IDs + const assetToId = new Map(); + const validAssets = []; + for (const asset of assets) { + try { + const id = this.getCoingeckoId(asset); + assetToId.set(asset.toUpperCase(), id); + validAssets.push(asset.toUpperCase()); + } + catch { + logger.warn(`Skipping unsupported asset: ${asset}`); + } + } + if (validAssets.length === 0) { + return []; + } + await this.enforceRateLimit(); + const coinIds = validAssets.map((a) => assetToId.get(a)).join(','); + const url = `${this.config.baseUrl}/simple/price?ids=${coinIds}&vs_currencies=usd&include_last_updated_at=true`; + const headers = {}; + if (this.apiKey) { + headers[this.getApiKeyHeader()] = this.apiKey; + } + try { + const response = await this.request(url, { headers }); + const results = []; + for (const asset of validAssets) { + const coinId = assetToId.get(asset); + const coinData = response[coinId]; + if (coinData) { + results.push({ + asset, + price: coinData.usd, + timestamp: coinData.last_updated_at || Math.floor(Date.now() / 1000), + source: 'coingecko', + }); + } + } + return results; + } + catch (error) { + logger.error('CoinGecko batch fetch failed', { error }); + throw error; + } + } + /** + * Get supported assets + */ + getSupportedAssets() { + return Object.keys(COINGECKO_ID_MAP); + } +} +/** + * Create a CoinGecko provider with default configuration + * + * API Key Types: + * - No key: Free tier (api.coingecko.com, 10-30 calls/min) + * - CG-* key: Demo tier (api.coingecko.com with demo header) + * - Other key: Pro tier (pro-api.coingecko.com with pro header) + */ +export function createCoinGeckoProvider(apiKey) { + const tier = getApiTier(apiKey); + // Demo and Free use the same base URL, only Pro uses pro-api + const baseUrl = tier === 'pro' ? 'https://pro-api.coingecko.com/api/v3' : 'https://api.coingecko.com/api/v3'; + const config = { + name: 'coingecko', + enabled: true, + priority: 1, + weight: 0.6, + apiKey, + baseUrl, + rateLimit: { + maxRequests: tier === 'free' ? 10 : 500, + windowMs: 60000, + }, + }; + return new CoinGeckoProvider(config); +} +//# sourceMappingURL=coingecko.js.map \ No newline at end of file diff --git a/oracle/dist/providers/coingecko.js.map b/oracle/dist/providers/coingecko.js.map new file mode 100644 index 00000000..e5676230 --- /dev/null +++ b/oracle/dist/providers/coingecko.js.map @@ -0,0 +1 @@ +{"version":3,"file":"coingecko.js","sourceRoot":"","sources":["../../src/providers/coingecko.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/providers/index.d.ts b/oracle/dist/providers/index.d.ts new file mode 100644 index 00000000..f3accd45 --- /dev/null +++ b/oracle/dist/providers/index.d.ts @@ -0,0 +1,7 @@ +/** + * Exports all price provider implementations and factory functions. + */ +export { BasePriceProvider } from './base-provider.js'; +export { CoinGeckoProvider, createCoinGeckoProvider } from './coingecko.js'; +export { BinanceProvider, createBinanceProvider } from './binance.js'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/oracle/dist/providers/index.d.ts.map b/oracle/dist/providers/index.d.ts.map new file mode 100644 index 00000000..3416e0bb --- /dev/null +++ b/oracle/dist/providers/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"} \ No newline at end of file diff --git a/oracle/dist/providers/index.js b/oracle/dist/providers/index.js new file mode 100644 index 00000000..db6dfd9e --- /dev/null +++ b/oracle/dist/providers/index.js @@ -0,0 +1,7 @@ +/** + * Exports all price provider implementations and factory functions. + */ +export { BasePriceProvider } from './base-provider.js'; +export { CoinGeckoProvider, createCoinGeckoProvider } from './coingecko.js'; +export { BinanceProvider, createBinanceProvider } from './binance.js'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/oracle/dist/providers/index.js.map b/oracle/dist/providers/index.js.map new file mode 100644 index 00000000..8d493385 --- /dev/null +++ b/oracle/dist/providers/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"} \ No newline at end of file diff --git a/oracle/dist/security/monitoring-service.d.ts b/oracle/dist/security/monitoring-service.d.ts new file mode 100644 index 00000000..c8d9122c --- /dev/null +++ b/oracle/dist/security/monitoring-service.d.ts @@ -0,0 +1,7 @@ +import { SecurityAlert } from "./types"; +export declare class MonitoringService { + private engine; + constructor(); + monitor(protocolState: any): Promise; +} +//# sourceMappingURL=monitoring-service.d.ts.map \ No newline at end of file diff --git a/oracle/dist/security/monitoring-service.d.ts.map b/oracle/dist/security/monitoring-service.d.ts.map new file mode 100644 index 00000000..e5fd922c --- /dev/null +++ b/oracle/dist/security/monitoring-service.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"monitoring-service.d.ts","sourceRoot":"","sources":["../../src/security/monitoring-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,aAAa,EAAE,MAAM,SAAS,CAAC;AAEnD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAiB;;IAMzB,OAAO,CAAC,aAAa,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;CAgB5D"} \ No newline at end of file diff --git a/oracle/dist/security/monitoring-service.js b/oracle/dist/security/monitoring-service.js new file mode 100644 index 00000000..e02c09fa --- /dev/null +++ b/oracle/dist/security/monitoring-service.js @@ -0,0 +1,23 @@ +import { SecurityEngine } from "./simulation-engine"; +import { RiskLevel } from "./types"; +export class MonitoringService { + engine; + constructor() { + this.engine = new SecurityEngine(); + } + async monitor(protocolState) { + const score = await this.engine.runSecurityAudit(protocolState); + const alerts = []; + if (score.level === RiskLevel.HIGH || score.level === RiskLevel.CRITICAL) { + alerts.push({ + id: `alert-${Date.now()}`, + level: score.level, + message: `High protocol risk detected: ${score.aggregateScore.toFixed(2)}%`, + data: score, + timestamp: score.timestamp, + }); + } + return alerts; + } +} +//# sourceMappingURL=monitoring-service.js.map \ No newline at end of file diff --git a/oracle/dist/security/monitoring-service.js.map b/oracle/dist/security/monitoring-service.js.map new file mode 100644 index 00000000..a3bcb185 --- /dev/null +++ b/oracle/dist/security/monitoring-service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"monitoring-service.js","sourceRoot":"","sources":["../../src/security/monitoring-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAiB,MAAM,SAAS,CAAC;AAEnD,MAAM,OAAO,iBAAiB;IACpB,MAAM,CAAiB;IAE/B;QACE,IAAI,CAAC,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,aAAkB;QAC9B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;QAChE,MAAM,MAAM,GAAoB,EAAE,CAAC;QAEnC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,QAAQ,EAAE,CAAC;YACzE,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,SAAS,IAAI,CAAC,GAAG,EAAE,EAAE;gBACzB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,OAAO,EAAE,gCAAgC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;gBAC3E,IAAI,EAAE,KAAK;gBACX,SAAS,EAAE,KAAK,CAAC,SAAS;aAC3B,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"} \ No newline at end of file diff --git a/oracle/dist/security/scenarios/liquidation-cascade.d.ts b/oracle/dist/security/scenarios/liquidation-cascade.d.ts new file mode 100644 index 00000000..d952868e --- /dev/null +++ b/oracle/dist/security/scenarios/liquidation-cascade.d.ts @@ -0,0 +1,6 @@ +import { AttackScenario, SimulationResult } from "../types"; +export declare class LiquidationCascadeScenario implements AttackScenario { + name: string; + run(protocolState: any): Promise; +} +//# sourceMappingURL=liquidation-cascade.d.ts.map \ No newline at end of file diff --git a/oracle/dist/security/scenarios/liquidation-cascade.d.ts.map b/oracle/dist/security/scenarios/liquidation-cascade.d.ts.map new file mode 100644 index 00000000..14a746ef --- /dev/null +++ b/oracle/dist/security/scenarios/liquidation-cascade.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"liquidation-cascade.d.ts","sourceRoot":"","sources":["../../../src/security/scenarios/liquidation-cascade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5D,qBAAa,0BAA2B,YAAW,cAAc;IAC/D,IAAI,SAAyB;IAEvB,GAAG,CAAC,aAAa,EAAE,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAuBzD"} \ No newline at end of file diff --git a/oracle/dist/security/scenarios/liquidation-cascade.js b/oracle/dist/security/scenarios/liquidation-cascade.js new file mode 100644 index 00000000..eba45dd9 --- /dev/null +++ b/oracle/dist/security/scenarios/liquidation-cascade.js @@ -0,0 +1,23 @@ +export class LiquidationCascadeScenario { + name = "Liquidation Cascade"; + async run(protocolState) { + const { totalCollateralValue, totalDebtValue, marketVolatility } = protocolState; + // Simulate a 20% drop in market prices leading to cascading liquidations + const dropPercentage = 0.20; + const liquidatedCollateral = totalCollateralValue * dropPercentage; + const slippage = marketVolatility * 0.5; // High volatility increases slippage + const unrecoveredDebt = liquidatedCollateral * slippage; + const insolvencyRisk = (unrecoveredDebt / totalCollateralValue) * 100; + return { + scenarioName: this.name, + success: insolvencyRisk > 5, + impactValue: unrecoveredDebt, + riskScore: Math.min(insolvencyRisk * 5, 100), + recommendations: [ + "Increase liquidation incentives to attract more liquidators", + "Lower LTV for volatile assets", + ], + }; + } +} +//# sourceMappingURL=liquidation-cascade.js.map \ No newline at end of file diff --git a/oracle/dist/security/scenarios/liquidation-cascade.js.map b/oracle/dist/security/scenarios/liquidation-cascade.js.map new file mode 100644 index 00000000..505a4fe3 --- /dev/null +++ b/oracle/dist/security/scenarios/liquidation-cascade.js.map @@ -0,0 +1 @@ +{"version":3,"file":"liquidation-cascade.js","sourceRoot":"","sources":["../../../src/security/scenarios/liquidation-cascade.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,0BAA0B;IACrC,IAAI,GAAG,qBAAqB,CAAC;IAE7B,KAAK,CAAC,GAAG,CAAC,aAAkB;QAC1B,MAAM,EAAE,oBAAoB,EAAE,cAAc,EAAE,gBAAgB,EAAE,GAAG,aAAa,CAAC;QAEjF,yEAAyE;QACzE,MAAM,cAAc,GAAG,IAAI,CAAC;QAC5B,MAAM,oBAAoB,GAAG,oBAAoB,GAAG,cAAc,CAAC;QACnE,MAAM,QAAQ,GAAG,gBAAgB,GAAG,GAAG,CAAC,CAAC,qCAAqC;QAE9E,MAAM,eAAe,GAAG,oBAAoB,GAAG,QAAQ,CAAC;QAExD,MAAM,cAAc,GAAG,CAAC,eAAe,GAAG,oBAAoB,CAAC,GAAG,GAAG,CAAC;QAEtE,OAAO;YACL,YAAY,EAAE,IAAI,CAAC,IAAI;YACvB,OAAO,EAAE,cAAc,GAAG,CAAC;YAC3B,WAAW,EAAE,eAAe;YAC5B,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,cAAc,GAAG,CAAC,EAAE,GAAG,CAAC;YAC5C,eAAe,EAAE;gBACf,6DAA6D;gBAC7D,+BAA+B;aAChC;SACF,CAAC;IACJ,CAAC;CACF"} \ No newline at end of file diff --git a/oracle/dist/security/scenarios/price-manipulation.d.ts b/oracle/dist/security/scenarios/price-manipulation.d.ts new file mode 100644 index 00000000..3ef5b4c1 --- /dev/null +++ b/oracle/dist/security/scenarios/price-manipulation.d.ts @@ -0,0 +1,6 @@ +import { AttackScenario, SimulationResult } from "../types"; +export declare class PriceManipulationScenario implements AttackScenario { + name: string; + run(protocolState: any): Promise; +} +//# sourceMappingURL=price-manipulation.d.ts.map \ No newline at end of file diff --git a/oracle/dist/security/scenarios/price-manipulation.d.ts.map b/oracle/dist/security/scenarios/price-manipulation.d.ts.map new file mode 100644 index 00000000..80065712 --- /dev/null +++ b/oracle/dist/security/scenarios/price-manipulation.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"price-manipulation.d.ts","sourceRoot":"","sources":["../../../src/security/scenarios/price-manipulation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5D,qBAAa,yBAA0B,YAAW,cAAc;IAC9D,IAAI,SAA+B;IAE7B,GAAG,CAAC,aAAa,EAAE,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAwBzD"} \ No newline at end of file diff --git a/oracle/dist/security/scenarios/price-manipulation.js b/oracle/dist/security/scenarios/price-manipulation.js new file mode 100644 index 00000000..d04d9fdd --- /dev/null +++ b/oracle/dist/security/scenarios/price-manipulation.js @@ -0,0 +1,24 @@ +export class PriceManipulationScenario { + name = "Oracle Price Manipulation"; + async run(protocolState) { + const { assets, oracleConfidence } = protocolState; + // Simulate a 30% price deviation in a major asset + const manipulatedAsset = assets[0]; + const originalPrice = manipulatedAsset.price; + const manipulatedPrice = originalPrice * 1.3; + // Calculate potential bad debt if this price was accepted + const potentialBadDebt = (manipulatedPrice - originalPrice) * manipulatedAsset.totalBorrowed; + const riskScore = (potentialBadDebt / protocolState.totalCollateralValue) * 100; + return { + scenarioName: this.name, + success: riskScore > 10, + impactValue: potentialBadDebt, + riskScore: Math.min(riskScore, 100), + recommendations: [ + "Increase oracle heartbeat frequency", + "Implement a price deviation circuit breaker", + ], + }; + } +} +//# sourceMappingURL=price-manipulation.js.map \ No newline at end of file diff --git a/oracle/dist/security/scenarios/price-manipulation.js.map b/oracle/dist/security/scenarios/price-manipulation.js.map new file mode 100644 index 00000000..29c6de69 --- /dev/null +++ b/oracle/dist/security/scenarios/price-manipulation.js.map @@ -0,0 +1 @@ +{"version":3,"file":"price-manipulation.js","sourceRoot":"","sources":["../../../src/security/scenarios/price-manipulation.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,yBAAyB;IACpC,IAAI,GAAG,2BAA2B,CAAC;IAEnC,KAAK,CAAC,GAAG,CAAC,aAAkB;QAC1B,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,aAAa,CAAC;QAEnD,kDAAkD;QAClD,MAAM,gBAAgB,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,CAAC;QAC7C,MAAM,gBAAgB,GAAG,aAAa,GAAG,GAAG,CAAC;QAE7C,0DAA0D;QAC1D,MAAM,gBAAgB,GAAG,CAAC,gBAAgB,GAAG,aAAa,CAAC,GAAG,gBAAgB,CAAC,aAAa,CAAC;QAE7F,MAAM,SAAS,GAAG,CAAC,gBAAgB,GAAG,aAAa,CAAC,oBAAoB,CAAC,GAAG,GAAG,CAAC;QAEhF,OAAO;YACL,YAAY,EAAE,IAAI,CAAC,IAAI;YACvB,OAAO,EAAE,SAAS,GAAG,EAAE;YACvB,WAAW,EAAE,gBAAgB;YAC7B,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC;YACnC,eAAe,EAAE;gBACf,qCAAqC;gBACrC,6CAA6C;aAC9C;SACF,CAAC;IACJ,CAAC;CACF"} \ No newline at end of file diff --git a/oracle/dist/security/simulation-engine.d.ts b/oracle/dist/security/simulation-engine.d.ts new file mode 100644 index 00000000..22f5dada --- /dev/null +++ b/oracle/dist/security/simulation-engine.d.ts @@ -0,0 +1,8 @@ +import { RiskScore } from "./types"; +export declare class SecurityEngine { + private scenarios; + constructor(); + runSecurityAudit(protocolState: any): Promise; + private mapScoreToLevel; +} +//# sourceMappingURL=simulation-engine.d.ts.map \ No newline at end of file diff --git a/oracle/dist/security/simulation-engine.d.ts.map b/oracle/dist/security/simulation-engine.d.ts.map new file mode 100644 index 00000000..04f12807 --- /dev/null +++ b/oracle/dist/security/simulation-engine.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"simulation-engine.d.ts","sourceRoot":"","sources":["../../src/security/simulation-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,SAAS,EAAoB,MAAM,SAAS,CAAC;AAIjF,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAwB;;IAOnC,gBAAgB,CAAC,aAAa,EAAE,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC;IAoB9D,OAAO,CAAC,eAAe;CAMxB"} \ No newline at end of file diff --git a/oracle/dist/security/simulation-engine.js b/oracle/dist/security/simulation-engine.js new file mode 100644 index 00000000..0b57de08 --- /dev/null +++ b/oracle/dist/security/simulation-engine.js @@ -0,0 +1,34 @@ +import { RiskLevel } from "./types"; +import { PriceManipulationScenario } from "./scenarios/price-manipulation"; +import { LiquidationCascadeScenario } from "./scenarios/liquidation-cascade"; +export class SecurityEngine { + scenarios = []; + constructor() { + this.scenarios.push(new PriceManipulationScenario()); + this.scenarios.push(new LiquidationCascadeScenario()); + } + async runSecurityAudit(protocolState) { + const results = await Promise.all(this.scenarios.map((s) => s.run(protocolState))); + const aggregateScore = results.reduce((acc, res) => acc + res.riskScore, 0) / results.length; + return { + aggregateScore, + level: this.mapScoreToLevel(aggregateScore), + breakdown: { + oracleRisk: results.find((r) => r.scenarioName === "Oracle Price Manipulation")?.riskScore || 0, + liquidityRisk: results.find((r) => r.scenarioName === "Liquidation Cascade")?.riskScore || 0, + insolvencyRisk: (aggregateScore * 0.8), // Heuristic + }, + timestamp: Date.now(), + }; + } + mapScoreToLevel(score) { + if (score < 20) + return RiskLevel.LOW; + if (score < 50) + return RiskLevel.MEDIUM; + if (score < 80) + return RiskLevel.HIGH; + return RiskLevel.CRITICAL; + } +} +//# sourceMappingURL=simulation-engine.js.map \ No newline at end of file diff --git a/oracle/dist/security/simulation-engine.js.map b/oracle/dist/security/simulation-engine.js.map new file mode 100644 index 00000000..124b315f --- /dev/null +++ b/oracle/dist/security/simulation-engine.js.map @@ -0,0 +1 @@ +{"version":3,"file":"simulation-engine.js","sourceRoot":"","sources":["../../src/security/simulation-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,SAAS,EAA+B,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E,MAAM,OAAO,cAAc;IACjB,SAAS,GAAqB,EAAE,CAAC;IAEzC;QACE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,yBAAyB,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,0BAA0B,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,aAAkB;QACvC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAChD,CAAC;QAEF,MAAM,cAAc,GAClB,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;QAExE,OAAO;YACL,cAAc;YACd,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC;YAC3C,SAAS,EAAE;gBACT,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,2BAA2B,CAAC,EAAE,SAAS,IAAI,CAAC;gBAC/F,aAAa,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,qBAAqB,CAAC,EAAE,SAAS,IAAI,CAAC;gBAC5F,cAAc,EAAE,CAAC,cAAc,GAAG,GAAG,CAAC,EAAE,YAAY;aACrD;YACD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;IACJ,CAAC;IAEO,eAAe,CAAC,KAAa;QACnC,IAAI,KAAK,GAAG,EAAE;YAAE,OAAO,SAAS,CAAC,GAAG,CAAC;QACrC,IAAI,KAAK,GAAG,EAAE;YAAE,OAAO,SAAS,CAAC,MAAM,CAAC;QACxC,IAAI,KAAK,GAAG,EAAE;YAAE,OAAO,SAAS,CAAC,IAAI,CAAC;QACtC,OAAO,SAAS,CAAC,QAAQ,CAAC;IAC5B,CAAC;CACF"} \ No newline at end of file diff --git a/oracle/dist/security/types.d.ts b/oracle/dist/security/types.d.ts new file mode 100644 index 00000000..ed2cac6b --- /dev/null +++ b/oracle/dist/security/types.d.ts @@ -0,0 +1,35 @@ +export declare enum RiskLevel { + LOW = "LOW", + MEDIUM = "MEDIUM", + HIGH = "HIGH", + CRITICAL = "CRITICAL" +} +export interface SimulationResult { + scenarioName: string; + success: boolean; + impactValue: number; + riskScore: number; + recommendations: string[]; +} +export interface RiskScore { + aggregateScore: number; + level: RiskLevel; + breakdown: { + oracleRisk: number; + liquidityRisk: number; + insolvencyRisk: number; + }; + timestamp: number; +} +export interface SecurityAlert { + id: string; + level: RiskLevel; + message: string; + data: any; + timestamp: number; +} +export interface AttackScenario { + name: string; + run(protocolState: any): Promise; +} +//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/oracle/dist/security/types.d.ts.map b/oracle/dist/security/types.d.ts.map new file mode 100644 index 00000000..42bb5f03 --- /dev/null +++ b/oracle/dist/security/types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/security/types.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,GAAG,QAAQ;IACX,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,QAAQ,aAAa;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,aAAa,EAAE,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpD"} \ No newline at end of file diff --git a/oracle/dist/security/types.js b/oracle/dist/security/types.js new file mode 100644 index 00000000..c9bb96b6 --- /dev/null +++ b/oracle/dist/security/types.js @@ -0,0 +1,8 @@ +export var RiskLevel; +(function (RiskLevel) { + RiskLevel["LOW"] = "LOW"; + RiskLevel["MEDIUM"] = "MEDIUM"; + RiskLevel["HIGH"] = "HIGH"; + RiskLevel["CRITICAL"] = "CRITICAL"; +})(RiskLevel || (RiskLevel = {})); +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/oracle/dist/security/types.js.map b/oracle/dist/security/types.js.map new file mode 100644 index 00000000..bfd3f8c0 --- /dev/null +++ b/oracle/dist/security/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/security/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,SAKX;AALD,WAAY,SAAS;IACnB,wBAAW,CAAA;IACX,8BAAiB,CAAA;IACjB,0BAAa,CAAA;IACb,kCAAqB,CAAA;AACvB,CAAC,EALW,SAAS,KAAT,SAAS,QAKpB"} \ No newline at end of file diff --git a/oracle/dist/services/cache.d.ts b/oracle/dist/services/cache.d.ts new file mode 100644 index 00000000..241e5aa6 --- /dev/null +++ b/oracle/dist/services/cache.d.ts @@ -0,0 +1,125 @@ +/** + * Cache Service + * + * In-memory caching layer with TTL support and LRU eviction. + * Supports Redis with fallback to in-memory when Redis unavailable. + */ +/** + * Cache config + */ +export interface CacheConfig { + defaultTtlSeconds: number; + maxEntries: number; + /** Fraction of entries to evict in a batch when at capacity (0 < x <= 1) */ + evictBatchFraction: number; + /** Redis URL (optional) */ + redisUrl?: string; +} +/** + * In-memory LRU cache implementation with Redis support. + * + * Access order is maintained by deleting and re-inserting keys into the Map + * on every read, so the Map's natural insertion order reflects LRU order + * (oldest = first entry, most-recently-used = last entry). + */ +export declare class Cache { + private config; + private store; + private hits; + private misses; + private evictions; + private redis?; + private usingRedis; + constructor(config?: Partial); + /** + * Initialize Redis connection + */ + private initializeRedis; + /** + * Get a value from cache. + * Moves the accessed entry to the "most recently used" position. + */ + get(key: string): Promise; + /** + * Set a value in cache with optional TTL. + * Performs LRU batch eviction when at capacity. + */ + set(key: string, value: T, ttlSeconds?: number): Promise; + /** + * Delete a specific key + */ + delete(key: string): Promise; + /** + * Clear all entries + */ + clear(): Promise; + /** + * Check if key exists and is not expired + */ + has(key: string): Promise; + /** + * Get cache statistics including hit rate and eviction count. + */ + getStats(): { + size: number; + hits: number; + misses: number; + hitRate: number; + evictions: number; + }; + /** + * Evict a batch of least-recently-used entries. + * + * The Map preserves insertion order and we refresh position on every get, + * so the first N keys are always the least recently used. + * Batch size = ceil(maxEntries * evictBatchFraction), minimum 1. + */ + private evictLRUBatch; + /** + * Clean up expired entries periodically + */ + cleanup(): number; +} +/** + * Price-specific cache wrapper + */ +export declare class PriceCache { + private cache; + private keyPrefix; + constructor(ttlSeconds?: number, redisUrl?: string); + /** + * Get cached price for an asset + */ + getPrice(asset: string): Promise; + /** + * Cache a price for an asset + */ + setPrice(asset: string, price: bigint, ttlSeconds?: number): Promise; + /** + * Check if we have a cached price + */ + hasPrice(asset: string): Promise; + /** + * Get cache statistics + */ + getStats(): { + size: number; + hits: number; + misses: number; + hitRate: number; + evictions: number; + }; + /** + * Clear all cached prices + */ + clear(): Promise; +} +/** + * Create a new cache instance + */ +export declare function createCache(config?: Partial): Cache; +/** + * Create a price-specific cache + */ +export declare function createPriceCache(ttlSeconds?: number, redisUrl?: string): PriceCache; +//# sourceMappingURL=cache.d.ts.map \ No newline at end of file diff --git a/oracle/dist/services/cache.d.ts.map b/oracle/dist/services/cache.d.ts.map new file mode 100644 index 00000000..670591f0 --- /dev/null +++ b/oracle/dist/services/cache.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/services/cache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAWD;;;;;;GAMG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAQ;IACtB,OAAO,CAAC,UAAU,CAAkB;gBAExB,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM;IAiB7C;;OAEG;IACH,OAAO,CAAC,eAAe;IAgCvB;;;OAGG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IA4CjD;;;OAGG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BvE;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc3C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAe5B;;OAEG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAoCxC;;OAEG;IACH,QAAQ,IAAI;QACV,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB;IAqBD;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAoBrB;;OAEG;IACH,OAAO,IAAI,MAAM;CAiBlB;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,SAAS,CAAY;gBAEjB,UAAU,GAAE,MAAW,EAAE,QAAQ,CAAC,EAAE,MAAM;IAQtD;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAI1D;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhF;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI/C;;OAEG;IACH,QAAQ;cAlHA,MAAM;cACN,MAAM;gBACJ,MAAM;iBACL,MAAM;mBACJ,MAAM;;IAkHnB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,KAAK,CAEhE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAEnF"} \ No newline at end of file diff --git a/oracle/dist/services/cache.js b/oracle/dist/services/cache.js new file mode 100644 index 00000000..1fcc8f65 --- /dev/null +++ b/oracle/dist/services/cache.js @@ -0,0 +1,340 @@ +/** + * Cache Service + * + * In-memory caching layer with TTL support and LRU eviction. + * Supports Redis with fallback to in-memory when Redis unavailable. + */ +import { logger } from '../utils/logger.js'; +import Redis from 'ioredis'; +/** + * Default cache configuration + */ +const DEFAULT_CONFIG = { + defaultTtlSeconds: 30, + maxEntries: 1000, + evictBatchFraction: 0.1, +}; +/** + * In-memory LRU cache implementation with Redis support. + * + * Access order is maintained by deleting and re-inserting keys into the Map + * on every read, so the Map's natural insertion order reflects LRU order + * (oldest = first entry, most-recently-used = last entry). + */ +export class Cache { + config; + store = new Map(); + hits = 0; + misses = 0; + evictions = 0; + redis; + usingRedis = false; + constructor(config = {}) { + this.config = { ...DEFAULT_CONFIG, ...config }; + // Initialize Redis if URL is provided + if (this.config.redisUrl) { + this.initializeRedis(); + } + logger.info('Cache initialized', { + defaultTtlSeconds: this.config.defaultTtlSeconds, + maxEntries: this.config.maxEntries, + evictBatchFraction: this.config.evictBatchFraction, + usingRedis: this.usingRedis, + redisUrl: this.config.redisUrl ? 'configured' : 'not configured', + }); + } + /** + * Initialize Redis connection + */ + initializeRedis() { + try { + this.redis = new Redis(this.config.redisUrl, { + maxRetriesPerRequest: 3, + lazyConnect: true, + }); + this.redis.on('connect', () => { + logger.info('Redis connected'); + this.usingRedis = true; + }); + this.redis.on('error', (error) => { + logger.warn('Redis connection failed, falling back to in-memory cache', { error }); + this.usingRedis = false; + this.redis?.disconnect(); + this.redis = undefined; + }); + // Test connection + this.redis.connect().catch(() => { + logger.warn('Redis connection failed during initialization, using in-memory cache'); + this.usingRedis = false; + this.redis = undefined; + }); + } + catch (error) { + logger.warn('Failed to initialize Redis, using in-memory cache', { error }); + this.usingRedis = false; + this.redis = undefined; + } + } + /** + * Get a value from cache. + * Moves the accessed entry to the "most recently used" position. + */ + async get(key) { + // Try Redis first if available + if (this.usingRedis && this.redis) { + try { + const value = await this.redis.get(key); + if (value !== null) { + const parsed = JSON.parse(value); + if (Date.now() <= parsed.expiresAt) { + this.hits++; + return parsed.data; + } + else { + // Expired in Redis, remove it + await this.redis.del(key); + } + } + } + catch (error) { + logger.warn('Redis get failed, falling back to in-memory', { error, key }); + this.usingRedis = false; + } + } + // Fallback to in-memory cache + const entry = this.store.get(key); + if (!entry) { + this.misses++; + return undefined; + } + // Check if expired + if (Date.now() > entry.expiresAt) { + this.store.delete(key); + this.misses++; + return undefined; + } + // Refresh LRU position: delete then re-insert moves key to end of Map + this.store.delete(key); + this.store.set(key, entry); + this.hits++; + return entry.data; + } + /** + * Set a value in cache with optional TTL. + * Performs LRU batch eviction when at capacity. + */ + async set(key, value, ttlSeconds) { + const ttl = ttlSeconds ?? this.config.defaultTtlSeconds; + const now = Date.now(); + const entry = { + data: value, + cachedAt: now, + expiresAt: now + ttl * 1000, + }; + // Try Redis first if available + if (this.usingRedis && this.redis) { + try { + await this.redis.setex(key, ttl, JSON.stringify(entry)); + } + catch (error) { + logger.warn('Redis set failed, falling back to in-memory', { error, key }); + this.usingRedis = false; + } + } + // Always store in memory as fallback + // If key already exists, remove it first so it gets a fresh LRU position + if (this.store.has(key)) { + this.store.delete(key); + } + else if (this.store.size >= this.config.maxEntries) { + this.evictLRUBatch(); + } + this.store.set(key, entry); + } + /** + * Delete a specific key + */ + async delete(key) { + // Try Redis first if available + if (this.usingRedis && this.redis) { + try { + await this.redis.del(key); + } + catch (error) { + logger.warn('Redis delete failed, using in-memory only', { error, key }); + this.usingRedis = false; + } + } + return this.store.delete(key); + } + /** + * Clear all entries + */ + async clear() { + // Try Redis first if available + if (this.usingRedis && this.redis) { + try { + await this.redis.flushdb(); + } + catch (error) { + logger.warn('Redis clear failed, using in-memory only', { error }); + this.usingRedis = false; + } + } + this.store.clear(); + logger.info('Cache cleared'); + } + /** + * Check if key exists and is not expired + */ + async has(key) { + // Try Redis first if available + if (this.usingRedis && this.redis) { + try { + const value = await this.redis.get(key); + if (value !== null) { + const parsed = JSON.parse(value); + if (Date.now() <= parsed.expiresAt) { + return true; + } + else { + // Expired in Redis, remove it + await this.redis.del(key); + return false; + } + } + } + catch (error) { + logger.warn('Redis has check failed, using in-memory only', { error, key }); + this.usingRedis = false; + } + } + // Fallback to in-memory cache + const entry = this.store.get(key); + if (!entry) { + return false; + } + if (Date.now() > entry.expiresAt) { + this.store.delete(key); + return false; + } + return true; + } + /** + * Get cache statistics including hit rate and eviction count. + */ + getStats() { + const total = this.hits + this.misses; + const hitRate = total > 0 ? this.hits / total : 0; + logger.debug('Cache stats', { + size: this.store.size, + hits: this.hits, + misses: this.misses, + hitRate: hitRate.toFixed(4), + evictions: this.evictions, + }); + return { + size: this.store.size, + hits: this.hits, + misses: this.misses, + hitRate, + evictions: this.evictions, + }; + } + /** + * Evict a batch of least-recently-used entries. + * + * The Map preserves insertion order and we refresh position on every get, + * so the first N keys are always the least recently used. + * Batch size = ceil(maxEntries * evictBatchFraction), minimum 1. + */ + evictLRUBatch() { + const batchSize = Math.max(1, Math.ceil(this.config.maxEntries * this.config.evictBatchFraction)); + let evicted = 0; + for (const key of this.store.keys()) { + if (evicted >= batchSize) + break; + this.store.delete(key); + evicted++; + } + this.evictions += evicted; + logger.debug(`LRU batch eviction: removed ${evicted} entries`, { + remaining: this.store.size, + totalEvictions: this.evictions, + }); + } + /** + * Clean up expired entries periodically + */ + cleanup() { + const now = Date.now(); + let cleaned = 0; + for (const [key, entry] of this.store) { + if (now > entry.expiresAt) { + this.store.delete(key); + cleaned++; + } + } + if (cleaned > 0) { + logger.debug(`Cleaned up ${cleaned} expired cache entries`); + } + return cleaned; + } +} +/** + * Price-specific cache wrapper + */ +export class PriceCache { + cache; + keyPrefix = 'price:'; + constructor(ttlSeconds = 30, redisUrl) { + this.cache = new Cache({ + defaultTtlSeconds: ttlSeconds, + maxEntries: 100, + redisUrl, + }); + } + /** + * Get cached price for an asset + */ + async getPrice(asset) { + return await this.cache.get(`${this.keyPrefix}${asset.toUpperCase()}`); + } + /** + * Cache a price for an asset + */ + async setPrice(asset, price, ttlSeconds) { + await this.cache.set(`${this.keyPrefix}${asset.toUpperCase()}`, price, ttlSeconds); + } + /** + * Check if we have a cached price + */ + async hasPrice(asset) { + return await this.cache.has(`${this.keyPrefix}${asset.toUpperCase()}`); + } + /** + * Get cache statistics + */ + getStats() { + return this.cache.getStats(); + } + /** + * Clear all cached prices + */ + async clear() { + await this.cache.clear(); + } +} +/** + * Create a new cache instance + */ +export function createCache(config) { + return new Cache(config); +} +/** + * Create a price-specific cache + */ +export function createPriceCache(ttlSeconds, redisUrl) { + return new PriceCache(ttlSeconds, redisUrl); +} +//# sourceMappingURL=cache.js.map \ No newline at end of file diff --git a/oracle/dist/services/cache.js.map b/oracle/dist/services/cache.js.map new file mode 100644 index 00000000..251f0bd3 --- /dev/null +++ b/oracle/dist/services/cache.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/services/cache.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/services/circuit-breaker.d.ts b/oracle/dist/services/circuit-breaker.d.ts new file mode 100644 index 00000000..1018de31 --- /dev/null +++ b/oracle/dist/services/circuit-breaker.d.ts @@ -0,0 +1,92 @@ +/** + * Circuit Breaker Service + * + * Implements the circuit breaker pattern for per-provider fault tolerance. + * Prevents wasting resources on providers that are consistently failing + * by backing off during outages and testing recovery automatically. + * + * States: + * CLOSED – Normal operation. Requests pass through. + * OPEN – Provider is down. Requests are skipped for a backoff period. + * HALF_OPEN – Backoff expired. A single probe request is allowed to test recovery. + */ +/** + * Circuit breaker states + */ +export declare enum CircuitState { + CLOSED = "CLOSED", + OPEN = "OPEN", + HALF_OPEN = "HALF_OPEN" +} +/** + * Configuration for a circuit breaker instance + */ +export interface CircuitBreakerConfig { + /** Number of consecutive failures before opening the circuit */ + failureThreshold: number; + /** Milliseconds to wait in OPEN state before moving to HALF_OPEN */ + backoffMs: number; + /** Provider name – used for logging */ + providerName: string; +} +/** + * Snapshot of circuit breaker metrics + */ +export interface CircuitBreakerMetrics { + providerName: string; + state: CircuitState; + consecutiveFailures: number; + totalFailures: number; + totalSuccesses: number; + lastFailureTime: number | null; + lastStateChangeTime: number; +} +/** + * Per-provider circuit breaker + */ +export declare class CircuitBreaker { + private state; + private consecutiveFailures; + private totalFailures; + private totalSuccesses; + private lastFailureTime; + private lastStateChangeTime; + private readonly config; + constructor(config: Partial & { + providerName: string; + }); + /** + * Current circuit state + */ + get currentState(): CircuitState; + /** + * Returns true when the circuit allows a request to proceed. + * + * CLOSED → always allow + * OPEN → allow only after backoff has elapsed (transitions to HALF_OPEN) + * HALF_OPEN → allow exactly one probe request + */ + isAllowed(): boolean; + /** + * Record a successful request. + * Resets failure count and closes the circuit. + */ + recordSuccess(): void; + /** + * Record a failed request. + * Opens the circuit once the failure threshold is reached. + */ + recordFailure(): void; + /** + * Snapshot of current metrics + */ + getMetrics(): CircuitBreakerMetrics; + private transitionTo; +} +/** + * Factory – creates one circuit breaker per provider name + */ +export declare function createCircuitBreaker(config: Partial & { + providerName: string; +}): CircuitBreaker; +//# sourceMappingURL=circuit-breaker.d.ts.map \ No newline at end of file diff --git a/oracle/dist/services/circuit-breaker.d.ts.map b/oracle/dist/services/circuit-breaker.d.ts.map new file mode 100644 index 00000000..226e3d67 --- /dev/null +++ b/oracle/dist/services/circuit-breaker.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/services/circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH;;GAEG;AACH,oBAAY,YAAY;IACtB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,SAAS,cAAc;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAOD;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,mBAAmB,CAAa;IACxC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;gBAElC,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE;IAI5E;;OAEG;IACH,IAAI,YAAY,IAAI,YAAY,CAE/B;IAED;;;;;;OAMG;IACH,SAAS,IAAI,OAAO;IAkBpB;;;OAGG;IACH,aAAa,IAAI,IAAI;IAYrB;;;OAGG;IACH,aAAa,IAAI,IAAI;IAmBrB;;OAEG;IACH,UAAU,IAAI,qBAAqB;IAcnC,OAAO,CAAC,YAAY;CAerB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAC/D,cAAc,CAEhB"} \ No newline at end of file diff --git a/oracle/dist/services/circuit-breaker.js b/oracle/dist/services/circuit-breaker.js new file mode 100644 index 00000000..9152a14a --- /dev/null +++ b/oracle/dist/services/circuit-breaker.js @@ -0,0 +1,134 @@ +/** + * Circuit Breaker Service + * + * Implements the circuit breaker pattern for per-provider fault tolerance. + * Prevents wasting resources on providers that are consistently failing + * by backing off during outages and testing recovery automatically. + * + * States: + * CLOSED – Normal operation. Requests pass through. + * OPEN – Provider is down. Requests are skipped for a backoff period. + * HALF_OPEN – Backoff expired. A single probe request is allowed to test recovery. + */ +import { logger } from '../utils/logger.js'; +/** + * Circuit breaker states + */ +export var CircuitState; +(function (CircuitState) { + CircuitState["CLOSED"] = "CLOSED"; + CircuitState["OPEN"] = "OPEN"; + CircuitState["HALF_OPEN"] = "HALF_OPEN"; +})(CircuitState || (CircuitState = {})); +const DEFAULT_CONFIG = { + failureThreshold: 3, + backoffMs: 30_000, +}; +/** + * Per-provider circuit breaker + */ +export class CircuitBreaker { + state = CircuitState.CLOSED; + consecutiveFailures = 0; + totalFailures = 0; + totalSuccesses = 0; + lastFailureTime = null; + lastStateChangeTime = Date.now(); + config; + constructor(config) { + this.config = { ...DEFAULT_CONFIG, ...config }; + } + /** + * Current circuit state + */ + get currentState() { + return this.state; + } + /** + * Returns true when the circuit allows a request to proceed. + * + * CLOSED → always allow + * OPEN → allow only after backoff has elapsed (transitions to HALF_OPEN) + * HALF_OPEN → allow exactly one probe request + */ + isAllowed() { + if (this.state === CircuitState.CLOSED) { + return true; + } + if (this.state === CircuitState.OPEN) { + const elapsed = Date.now() - (this.lastFailureTime ?? 0); + if (elapsed >= this.config.backoffMs) { + this.transitionTo(CircuitState.HALF_OPEN); + return true; // probe request + } + return false; + } + // HALF_OPEN – allow the single probe that triggered the transition + return true; + } + /** + * Record a successful request. + * Resets failure count and closes the circuit. + */ + recordSuccess() { + this.totalSuccesses++; + this.consecutiveFailures = 0; + if (this.state !== CircuitState.CLOSED) { + logger.info(`Circuit breaker CLOSED for provider "${this.config.providerName}" – recovery confirmed`); + this.transitionTo(CircuitState.CLOSED); + } + } + /** + * Record a failed request. + * Opens the circuit once the failure threshold is reached. + */ + recordFailure() { + this.totalFailures++; + this.consecutiveFailures++; + this.lastFailureTime = Date.now(); + if (this.state === CircuitState.HALF_OPEN || + (this.state === CircuitState.CLOSED && + this.consecutiveFailures >= this.config.failureThreshold)) { + logger.warn(`Circuit breaker OPEN for provider "${this.config.providerName}" – ` + + `${this.consecutiveFailures} consecutive failures. ` + + `Backing off for ${this.config.backoffMs}ms.`); + this.transitionTo(CircuitState.OPEN); + } + } + /** + * Snapshot of current metrics + */ + getMetrics() { + return { + providerName: this.config.providerName, + state: this.state, + consecutiveFailures: this.consecutiveFailures, + totalFailures: this.totalFailures, + totalSuccesses: this.totalSuccesses, + lastFailureTime: this.lastFailureTime, + lastStateChangeTime: this.lastStateChangeTime, + }; + } + // ── private ────────────────────────────────────────────────────────────── + transitionTo(next) { + const prev = this.state; + if (prev === next) + return; + this.state = next; + this.lastStateChangeTime = Date.now(); + logger.info(`Circuit breaker state transition for "${this.config.providerName}"`, { + from: prev, + to: next, + consecutiveFailures: this.consecutiveFailures, + totalFailures: this.totalFailures, + totalSuccesses: this.totalSuccesses, + }); + } +} +/** + * Factory – creates one circuit breaker per provider name + */ +export function createCircuitBreaker(config) { + return new CircuitBreaker(config); +} +//# sourceMappingURL=circuit-breaker.js.map \ No newline at end of file diff --git a/oracle/dist/services/circuit-breaker.js.map b/oracle/dist/services/circuit-breaker.js.map new file mode 100644 index 00000000..a5e9b4fd --- /dev/null +++ b/oracle/dist/services/circuit-breaker.js.map @@ -0,0 +1 @@ +{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/services/circuit-breaker.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/services/contract-updater.d.ts b/oracle/dist/services/contract-updater.d.ts new file mode 100644 index 00000000..d8633aa6 --- /dev/null +++ b/oracle/dist/services/contract-updater.d.ts @@ -0,0 +1,71 @@ +/** + * Contract Updater Service + */ +import type { ContractUpdateResult, AggregatedPrice } from '../types/index.js'; +/** + * Contract updater configuration + */ +export interface ContractUpdaterConfig { + network: 'testnet' | 'mainnet'; + rpcUrl: string; + /** StellarLend contract ID */ + contractId: string; + /** Admin secret key for signing */ + adminSecretKey: string; + baseFee: number; + maxFee: number; + maxRetries: number; + retryDelayMs: number; +} +/** + * Contract Updater + */ +export declare class ContractUpdater { + private config; + private server; + private adminKeypair; + private networkPassphrase; + constructor(config: ContractUpdaterConfig); + /** + * Update price for a single asset + */ + updatePrice(asset: string, price: bigint, timestamp: number): Promise; + /** + * Update prices for multiple assets + */ + updatePrices(prices: AggregatedPrice[]): Promise; + /** + * Submit a price update transaction to the contract + */ + private submitPriceUpdate; + /** + * Comprehensive health check with detailed status + */ + healthCheck(): Promise<{ + overall: boolean; + rpc: boolean; + admin: boolean; + contract: boolean; + details: { + rpc?: string; + admin?: { + balance: string; + exists: boolean; + }; + contract?: string; + }; + }>; + /** + * Get the admin public key + */ + getAdminPublicKey(): string; + /** + * Sleep utility + */ + private sleep; +} +/** + * Create a contract updater + */ +export declare function createContractUpdater(config: ContractUpdaterConfig): ContractUpdater; +//# sourceMappingURL=contract-updater.d.ts.map \ No newline at end of file diff --git a/oracle/dist/services/contract-updater.d.ts.map b/oracle/dist/services/contract-updater.d.ts.map new file mode 100644 index 00000000..804e14be --- /dev/null +++ b/oracle/dist/services/contract-updater.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"contract-updater.d.ts","sourceRoot":"","sources":["../../src/services/contract-updater.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAG/E;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAcD;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,iBAAiB,CAAS;gBAEtB,MAAM,EAAE,qBAAqB;IAwBzC;;OAEG;IACG,WAAW,CACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,oBAAoB,CAAC;IAsDhC;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAa9E;;OAEG;YACW,iBAAiB;IA+E/B;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC;QAC3B,OAAO,EAAE,OAAO,CAAC;QACjB,GAAG,EAAE,OAAO,CAAC;QACb,KAAK,EAAE,OAAO,CAAC;QACf,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,EAAE;YACP,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,KAAK,CAAC,EAAE;gBAAE,OAAO,EAAE,MAAM,CAAC;gBAAC,MAAM,EAAE,OAAO,CAAA;aAAE,CAAC;YAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;IAsFF;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,OAAO,CAAC,KAAK;CAGd;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAEpF"} \ No newline at end of file diff --git a/oracle/dist/services/contract-updater.js b/oracle/dist/services/contract-updater.js new file mode 100644 index 00000000..19936ae3 --- /dev/null +++ b/oracle/dist/services/contract-updater.js @@ -0,0 +1,257 @@ +/** + * Contract Updater Service + */ +import { Account, Keypair, Contract, rpc, TransactionBuilder, Networks, xdr, Address, nativeToScVal, } from '@stellar/stellar-sdk'; +import { logger } from '../utils/logger.js'; +/** + * Default configuration + */ +const DEFAULT_CONFIG = { + baseFee: 100000, + maxFee: 1000000, + maxRetries: 3, + retryDelayMs: 1000, +}; +const MIN_TRANSACTION_FEE = 100; +/** + * Contract Updater + */ +export class ContractUpdater { + config; + server; + adminKeypair; + networkPassphrase; + constructor(config) { + this.config = { ...DEFAULT_CONFIG, ...config }; + if (this.config.baseFee < MIN_TRANSACTION_FEE) { + throw new Error(`baseFee must be at least ${MIN_TRANSACTION_FEE} stroops`); + } + if (this.config.baseFee > this.config.maxFee) { + throw new Error('baseFee cannot exceed maxFee'); + } + this.server = new rpc.Server(this.config.rpcUrl); + this.adminKeypair = Keypair.fromSecret(this.config.adminSecretKey); + this.networkPassphrase = this.config.network === 'testnet' ? Networks.TESTNET : Networks.PUBLIC; + logger.info('Contract updater initialized', { + network: this.config.network, + contractId: this.config.contractId, + baseFee: this.config.baseFee, + maxFee: this.config.maxFee, + adminPublicKey: this.adminKeypair.publicKey(), + }); + } + /** + * Update price for a single asset + */ + async updatePrice(asset, price, timestamp) { + const startTime = Date.now(); + let lastError; + for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) { + try { + logger.info(`Updating price for ${asset} (attempt ${attempt})`, { + price: price.toString(), + timestamp, + }); + const txHash = await this.submitPriceUpdate(asset, price, timestamp); + const result = { + success: true, + transactionHash: txHash, + asset, + price, + timestamp, + }; + logger.info(`Price update successful for ${asset}`, { + txHash, + durationMs: Date.now() - startTime, + }); + return result; + } + catch (error) { + lastError = error instanceof Error ? error : new Error(String(error)); + logger.warn(`Price update attempt ${attempt} failed for ${asset}`, { + error: lastError.message, + }); + if (attempt < this.config.maxRetries) { + const delay = this.config.retryDelayMs * Math.pow(2, attempt - 1); + await this.sleep(delay); + } + } + } + logger.error(`All price update attempts failed for ${asset}`, { + error: lastError?.message, + }); + return { + success: false, + asset, + price, + timestamp, + error: lastError?.message || 'Unknown error', + }; + } + /** + * Update prices for multiple assets + */ + async updatePrices(prices) { + const results = []; + for (const price of prices) { + const result = await this.updatePrice(price.asset, price.price, price.timestamp); + results.push(result); + await this.sleep(100); + } + return results; + } + /** + * Submit a price update transaction to the contract + */ + async submitPriceUpdate(asset, price, timestamp) { + const contract = new Contract(this.config.contractId); + const adminAddress = new Address(this.adminKeypair.publicKey()); + const operation = contract.call('set_asset_price', adminAddress.toScVal(), xdr.ScVal.scvSymbol(asset), nativeToScVal(price, { type: 'i128' }), nativeToScVal(timestamp, { type: 'u64' })); + const account = await this.server.getAccount(this.adminKeypair.publicKey()); + const transaction = new TransactionBuilder(account, { + fee: String(this.config.baseFee), + networkPassphrase: this.networkPassphrase, + }) + .addOperation(operation) + .setTimeout(30) + .build(); + const simulated = await this.server.simulateTransaction(transaction); + if (rpc.Api.isSimulationError(simulated)) { + const message = String(simulated.error ?? 'Unknown simulation error'); + throw new Error(message.startsWith('Simulation failed:') ? message : `Simulation failed: ${message}`); + } + if (!rpc.Api.isSimulationSuccess(simulated)) { + throw new Error('Simulation did not succeed'); + } + const prepared = rpc.assembleTransaction(transaction, simulated).build(); + prepared.sign(this.adminKeypair); + const response = await this.server.sendTransaction(prepared); + if (response.status === 'ERROR') { + throw new Error(`Transaction failed: ${response.errorResult}`); + } + const hash = response.hash; + let getResponse = await this.server.getTransaction(hash); + const MAX_POLL_ATTEMPTS = 30; + let attempts = 0; + while (getResponse.status === rpc.Api.GetTransactionStatus.NOT_FOUND && + attempts < MAX_POLL_ATTEMPTS) { + await this.sleep(1000); + attempts++; + getResponse = await this.server.getTransaction(hash); + } + if (attempts >= MAX_POLL_ATTEMPTS) { + logger.error('Transaction polling timed out', { + txHash: hash, + asset, + attempts, + }); + throw new Error(`Transaction polling timed out after ${MAX_POLL_ATTEMPTS} attempts`); + } + if (getResponse.status === rpc.Api.GetTransactionStatus.FAILED) { + throw new Error(`Transaction failed on-chain`); + } + return hash; + } + /** + * Comprehensive health check with detailed status + */ + async healthCheck() { + const startTime = Date.now(); + const result = { + overall: false, + rpc: false, + admin: false, + contract: false, + details: {}, + }; + try { + // 1. Check RPC connectivity + try { + await this.server.getHealth(); + result.rpc = true; + result.details.rpc = 'RPC endpoint reachable'; + } + catch (error) { + result.details.rpc = `RPC unreachable: ${error instanceof Error ? error.message : 'Unknown error'}`; + } + // 2. Check admin account exists and has funds + try { + const adminAccount = (await this.server.getAccount(this.adminKeypair.publicKey())); + result.admin = true; + result.details.admin = { + exists: true, + balance: adminAccount.balances + .filter((balance) => balance.asset_type === 'native') + .map((balance) => balance.balance) + .join('') || '0', + }; + } + catch (error) { + result.details.admin = { + exists: false, + balance: '0', + }; + } + // 3. Check contract is deployed and accessible + try { + const contract = new Contract(this.config.contractId); + // Try to read from contract to verify it's deployed + await this.server.simulateTransaction(new TransactionBuilder(new Account(this.adminKeypair.publicKey(), '1'), { + fee: '100', + networkPassphrase: this.networkPassphrase, + }) + .addOperation(contract.call('get_asset_price', xdr.ScVal.scvSymbol('XLM'))) + .setTimeout(0) + .build()); + result.contract = true; + result.details.contract = 'Contract accessible'; + } + catch (error) { + result.details.contract = `Contract inaccessible: ${error instanceof Error ? error.message : 'Unknown error'}`; + } + // Overall health is true only if all checks pass + result.overall = result.rpc && result.admin && result.contract; + logger.info('Health check completed', { + duration: Date.now() - startTime, + overall: result.overall, + rpc: result.rpc, + admin: result.admin, + contract: result.contract, + }); + return result; + } + catch (error) { + logger.error('Health check failed with unexpected error:', error); + return { + overall: false, + rpc: false, + admin: false, + contract: false, + details: { + rpc: 'Health check failed', + admin: { exists: false, balance: '0' }, + contract: 'Health check failed', + }, + }; + } + } + /** + * Get the admin public key + */ + getAdminPublicKey() { + return this.adminKeypair.publicKey(); + } + /** + * Sleep utility + */ + sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} +/** + * Create a contract updater + */ +export function createContractUpdater(config) { + return new ContractUpdater(config); +} +//# sourceMappingURL=contract-updater.js.map \ No newline at end of file diff --git a/oracle/dist/services/contract-updater.js.map b/oracle/dist/services/contract-updater.js.map new file mode 100644 index 00000000..8c44b577 --- /dev/null +++ b/oracle/dist/services/contract-updater.js.map @@ -0,0 +1 @@ +{"version":3,"file":"contract-updater.js","sourceRoot":"","sources":["../../src/services/contract-updater.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,GAAG,EACH,kBAAkB,EAClB,QAAQ,EACR,GAAG,EACH,OAAO,EACP,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAkB5C;;GAEG;AACH,MAAM,cAAc,GAAmC;IACrD,OAAO,EAAE,MAAM;IACf,MAAM,EAAE,OAAO;IACf,UAAU,EAAE,CAAC;IACb,YAAY,EAAE,IAAI;CACnB,CAAC;AAEF,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC;;GAEG;AACH,MAAM,OAAO,eAAe;IAClB,MAAM,CAAwB;IAC9B,MAAM,CAAa;IACnB,YAAY,CAAU;IACtB,iBAAiB,CAAS;IAElC,YAAY,MAA6B;QACvC,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAA2B,CAAC;QAExE,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,mBAAmB,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,4BAA4B,mBAAmB,UAAU,CAAC,CAAC;QAC7E,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QACnE,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;QAEhG,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE;YAC1C,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;YAClC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;YAC1B,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE;SAC9C,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CACf,KAAa,EACb,KAAa,EACb,SAAiB;QAEjB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,SAA4B,CAAC;QAEjC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;YACnE,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,sBAAsB,KAAK,aAAa,OAAO,GAAG,EAAE;oBAC9D,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE;oBACvB,SAAS;iBACV,CAAC,CAAC;gBAEH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;gBAErE,MAAM,MAAM,GAAyB;oBACnC,OAAO,EAAE,IAAI;oBACb,eAAe,EAAE,MAAM;oBACvB,KAAK;oBACL,KAAK;oBACL,SAAS;iBACV,CAAC;gBAEF,MAAM,CAAC,IAAI,CAAC,+BAA+B,KAAK,EAAE,EAAE;oBAClD,MAAM;oBACN,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;iBACnC,CAAC,CAAC;gBAEH,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,SAAS,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAEtE,MAAM,CAAC,IAAI,CAAC,wBAAwB,OAAO,eAAe,KAAK,EAAE,EAAE;oBACjE,KAAK,EAAE,SAAS,CAAC,OAAO;iBACzB,CAAC,CAAC;gBAEH,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;oBACrC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;oBAClE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,wCAAwC,KAAK,EAAE,EAAE;YAC5D,KAAK,EAAE,SAAS,EAAE,OAAO;SAC1B,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK;YACL,KAAK;YACL,SAAS;YACT,KAAK,EAAE,SAAS,EAAE,OAAO,IAAI,eAAe;SAC7C,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAAC,MAAyB;QAC1C,MAAM,OAAO,GAA2B,EAAE,CAAC;QAE3C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;YACjF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAErB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAC7B,KAAa,EACb,KAAa,EACb,SAAiB;QAEjB,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;QAEhE,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAC7B,iBAAiB,EACjB,YAAY,CAAC,OAAO,EAAE,EACtB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,EAC1B,aAAa,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EACtC,aAAa,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAC1C,CAAC;QAEF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;QAE5E,MAAM,WAAW,GAAG,IAAI,kBAAkB,CAAC,OAAO,EAAE;YAClD,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;YAChC,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;SAC1C,CAAC;aACC,YAAY,CAAC,SAAS,CAAC;aACvB,UAAU,CAAC,EAAE,CAAC;aACd,KAAK,EAAE,CAAC;QAEX,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;QAErE,IAAI,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,SAAS,CAAC,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,0BAA0B,CAAC,CAAC;YACtE,MAAM,IAAI,KAAK,CACb,OAAO,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,OAAO,EAAE,CACrF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAChD,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,CAAC,mBAAmB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC;QACzE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAEjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAE7D,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC3B,IAAI,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAEzD,MAAM,iBAAiB,GAAG,EAAE,CAAC;QAC7B,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,OACE,WAAW,CAAC,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,SAAS;YAC7D,QAAQ,GAAG,iBAAiB,EAC5B,CAAC;YACD,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACvB,QAAQ,EAAE,CAAC;YACX,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QACvD,CAAC;QAED,IAAI,QAAQ,IAAI,iBAAiB,EAAE,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE;gBAC5C,MAAM,EAAE,IAAI;gBACZ,KAAK;gBACL,QAAQ;aACT,CAAC,CAAC;YACH,MAAM,IAAI,KAAK,CAAC,uCAAuC,iBAAiB,WAAW,CAAC,CAAC;QACvF,CAAC;QAED,IAAI,WAAW,CAAC,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,MAAM,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QAWf,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG;YACb,OAAO,EAAE,KAAK;YACd,GAAG,EAAE,KAAK;YACV,KAAK,EAAE,KAAK;YACZ,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,EAAS;SACnB,CAAC;QAEF,IAAI,CAAC;YACH,4BAA4B;YAC5B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;gBAC9B,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC;gBAClB,MAAM,CAAC,OAAO,CAAC,GAAG,GAAG,wBAAwB,CAAC;YAChD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,OAAO,CAAC,GAAG,GAAG,oBAAoB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC;YACtG,CAAC;YAED,8CAA8C;YAC9C,IAAI,CAAC;gBACH,MAAM,YAAY,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAQ,CAAC;gBAC1F,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;gBACpB,MAAM,CAAC,OAAO,CAAC,KAAK,GAAG;oBACrB,MAAM,EAAE,IAAI;oBACZ,OAAO,EACL,YAAY,CAAC,QAAQ;yBAClB,MAAM,CAAC,CAAC,OAAY,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC;yBACzD,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;yBACtC,IAAI,CAAC,EAAE,CAAC,IAAI,GAAG;iBACrB,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,OAAO,CAAC,KAAK,GAAG;oBACrB,MAAM,EAAE,KAAK;oBACb,OAAO,EAAE,GAAG;iBACb,CAAC;YACJ,CAAC;YAED,+CAA+C;YAC/C,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBACtD,oDAAoD;gBACpD,MAAM,IAAI,CAAC,MAAM,CAAC,mBAAmB,CACnC,IAAI,kBAAkB,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,GAAG,CAAC,EAAE;oBACtE,GAAG,EAAE,KAAK;oBACV,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;iBAC1C,CAAC;qBACC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;qBAC1E,UAAU,CAAC,CAAC,CAAC;qBACb,KAAK,EAAE,CACX,CAAC;gBACF,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACvB,MAAM,CAAC,OAAO,CAAC,QAAQ,GAAG,qBAAqB,CAAC;YAClD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,OAAO,CAAC,QAAQ,GAAG,0BAA0B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC;YACjH,CAAC;YAED,iDAAiD;YACjD,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC;YAE/D,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;gBACpC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBAChC,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;aAC1B,CAAC,CAAC;YAEH,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC;YAClE,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,GAAG,EAAE,KAAK;gBACV,KAAK,EAAE,KAAK;gBACZ,QAAQ,EAAE,KAAK;gBACf,OAAO,EAAE;oBACP,GAAG,EAAE,qBAAqB;oBAC1B,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;oBACtC,QAAQ,EAAE,qBAAqB;iBAChC;aACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,iBAAiB;QACf,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;IACvC,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,EAAU;QACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAA6B;IACjE,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"} \ No newline at end of file diff --git a/oracle/dist/services/index.d.ts b/oracle/dist/services/index.d.ts new file mode 100644 index 00000000..a3275483 --- /dev/null +++ b/oracle/dist/services/index.d.ts @@ -0,0 +1,18 @@ +/** + * Services Index + * + * Exports all service implementations. + */ +export { PriceValidator, createValidator } from './price-validator.js'; +export type { ValidatorConfig } from './price-validator.js'; +export { Cache, PriceCache, createCache, createPriceCache } from './cache.js'; +export type { CacheConfig } from './cache.js'; +export { PriceAggregator, createAggregator } from './price-aggregator.js'; +export type { AggregatorConfig } from './price-aggregator.js'; +export { ContractUpdater, createContractUpdater } from './contract-updater.js'; +export type { ContractUpdaterConfig } from './contract-updater.js'; +export { PriceHistoryService, createPriceHistoryService } from './price-history.js'; +export type { PriceHistoryConfig, PriceHistoryEntry, TWAPResult } from './price-history.js'; +export { CircuitBreaker, CircuitState, createCircuitBreaker } from './circuit-breaker.js'; +export type { CircuitBreakerConfig, CircuitBreakerMetrics } from './circuit-breaker.js'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/oracle/dist/services/index.d.ts.map b/oracle/dist/services/index.d.ts.map new file mode 100644 index 00000000..257d446f --- /dev/null +++ b/oracle/dist/services/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1E,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE5F,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"} \ No newline at end of file diff --git a/oracle/dist/services/index.js b/oracle/dist/services/index.js new file mode 100644 index 00000000..41f1edad --- /dev/null +++ b/oracle/dist/services/index.js @@ -0,0 +1,12 @@ +/** + * Services Index + * + * Exports all service implementations. + */ +export { PriceValidator, createValidator } from './price-validator.js'; +export { Cache, PriceCache, createCache, createPriceCache } from './cache.js'; +export { PriceAggregator, createAggregator } from './price-aggregator.js'; +export { ContractUpdater, createContractUpdater } from './contract-updater.js'; +export { PriceHistoryService, createPriceHistoryService } from './price-history.js'; +export { CircuitBreaker, CircuitState, createCircuitBreaker } from './circuit-breaker.js'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/oracle/dist/services/index.js.map b/oracle/dist/services/index.js.map new file mode 100644 index 00000000..0da2494f --- /dev/null +++ b/oracle/dist/services/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG1E,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG/E,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAGpF,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"} \ No newline at end of file diff --git a/oracle/dist/services/price-aggregator.d.ts b/oracle/dist/services/price-aggregator.d.ts new file mode 100644 index 00000000..205fdaa7 --- /dev/null +++ b/oracle/dist/services/price-aggregator.d.ts @@ -0,0 +1,104 @@ +/** + * Price Aggregator Service + * + * Fetches prices from multiple providers and aggregates them + * using weighted median calculation. + */ +import type { AggregatedPrice } from '../types/index.js'; +import { BasePriceProvider } from '../providers/base-provider.js'; +import { PriceValidator } from './price-validator.js'; +import { PriceCache } from './cache.js'; +import { PriceHistoryService } from './price-history.js'; +import { CircuitState } from './circuit-breaker.js'; +import type { CircuitBreakerConfig, CircuitBreakerMetrics } from './circuit-breaker.js'; +/** + * Aggregator configuration + */ +export interface AggregatorConfig { + minSources: number; + useWeightedMedian: boolean; + circuitBreaker?: Partial>; +} +/** + * Price Aggregator + */ +export declare class PriceAggregator { + private providers; + private validator; + private cache; + private priceHistory; + private config; + private circuitBreakers; + constructor(providers: BasePriceProvider[], validator: PriceValidator, cache: PriceCache, priceHistory: PriceHistoryService, config?: Partial); + /** + * Fetch and aggregate price for a single asset + */ + getPrice(asset: string): Promise; + /** + * Fetch prices for multiple assets + */ + getPrices(assets: string[]): Promise>; + /** + * Fetch price from providers with fallback logic + */ + private fetchWithFallback; + /** + * Aggregate prices from multiple sources + */ + private aggregate; + /** + * Calculate weighted median of prices + */ + private weightedMedian; + /** + * Calculate simple median of prices + */ + private simpleMedian; + /** + * Get price history service + */ + getPriceHistory(): PriceHistoryService; + /** + * Get circuit breaker metrics for all providers + */ + getCircuitBreakerMetrics(): Array; + /** + * Get list of enabled providers + */ + getProviders(): string[]; + /** + * Get aggregator statistics + */ + getStats(): { + enabledProviders: number; + cacheStats: { + size: number; + hits: number; + misses: number; + hitRate: number; + evictions: number; + }; + priceHistoryStats: { + trackedAssets: number; + totalEntries: number; + maxEntriesPerAsset: number; + assets: string[]; + }; + circuitBreakerMetrics: (CircuitBreakerMetrics & { + providerName: string; + state: CircuitState; + })[]; + circuitBreakers: (CircuitBreakerMetrics & { + providerName: string; + state: CircuitState; + })[]; + }; +} +/** + * Create a price aggregator + */ +export declare function createAggregator(providers: BasePriceProvider[], validator: PriceValidator, cache: PriceCache, priceHistoryOrConfig?: PriceHistoryService | Partial, config?: Partial): PriceAggregator; +//# sourceMappingURL=price-aggregator.d.ts.map \ No newline at end of file diff --git a/oracle/dist/services/price-aggregator.d.ts.map b/oracle/dist/services/price-aggregator.d.ts.map new file mode 100644 index 00000000..9c2cab78 --- /dev/null +++ b/oracle/dist/services/price-aggregator.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"price-aggregator.d.ts","sourceRoot":"","sources":["../../src/services/price-aggregator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAA2B,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAkB,YAAY,EAAwB,MAAM,sBAAsB,CAAC;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIxF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,cAAc,CAAC,CAAC,CAAC;CACtE;AAUD;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,eAAe,CAA8B;gBAGnD,SAAS,EAAE,iBAAiB,EAAE,EAC9B,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,mBAAmB,EACjC,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM;IA0BxC;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAmC9D;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAexE;;OAEG;YACW,iBAAiB;IA2D/B;;OAEG;IACH,OAAO,CAAC,SAAS;IAqCjB;;OAEG;IACH,OAAO,CAAC,cAAc;IAsBtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAapB;;OAEG;IACH,eAAe,IAAI,mBAAmB;IAItC;;OAEG;IACH,wBAAwB,IAAI,KAAK,CAC/B,qBAAqB,GAAG;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,CACtE;IAUD;;OAEG;IACH,YAAY,IAAI,MAAM,EAAE;IAIxB;;OAEG;IACH,QAAQ;;;;;;;;;;;;;;;;0BArBkC,MAAM;mBAAS,YAAY;;;0BAA3B,MAAM;mBAAS,YAAY;;;CA8BtE;AAcD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,iBAAiB,EAAE,EAC9B,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,UAAU,EACjB,oBAAoB,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,EACtE,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GACjC,eAAe,CAWjB"} \ No newline at end of file diff --git a/oracle/dist/services/price-aggregator.js b/oracle/dist/services/price-aggregator.js new file mode 100644 index 00000000..dcb4adbe --- /dev/null +++ b/oracle/dist/services/price-aggregator.js @@ -0,0 +1,266 @@ +/** + * Price Aggregator Service + * + * Fetches prices from multiple providers and aggregates them + * using weighted median calculation. + */ +import { PriceHistoryService } from './price-history.js'; +import { createCircuitBreaker } from './circuit-breaker.js'; +import { logger } from '../utils/logger.js'; +/** + * Default aggregator configuration + */ +const DEFAULT_CONFIG = { + minSources: 1, + useWeightedMedian: true, +}; +/** + * Price Aggregator + */ +export class PriceAggregator { + providers; + validator; + cache; + priceHistory; + config; + circuitBreakers; + constructor(providers, validator, cache, priceHistory, config = {}) { + this.providers = providers.filter((p) => p.isEnabled).sort((a, b) => a.priority - b.priority); + this.validator = validator; + this.cache = cache; + this.priceHistory = priceHistory; + this.config = { ...DEFAULT_CONFIG, ...config }; + // Create one circuit breaker per provider + this.circuitBreakers = new Map(this.providers.map((p) => [ + p.name, + createCircuitBreaker({ + providerName: p.name, + ...this.config.circuitBreaker, + }), + ])); + logger.info('Price aggregator initialized', { + enabledProviders: this.providers.map((p) => p.name), + minSources: this.config.minSources, + }); + } + /** + * Fetch and aggregate price for a single asset + */ + async getPrice(asset) { + const upperAsset = asset.toUpperCase(); + const cachedPrice = await this.cache.getPrice(upperAsset); + if (cachedPrice !== undefined) { + logger.debug(`Using cached price for ${upperAsset}`); + return { + asset: upperAsset, + price: cachedPrice, + sources: [], + timestamp: Math.floor(Date.now() / 1000), + confidence: 100, + }; + } + const validPrices = await this.fetchWithFallback(upperAsset); + if (validPrices.length < this.config.minSources) { + logger.error(`Not enough valid sources for ${upperAsset}`, { + got: validPrices.length, + required: this.config.minSources, + }); + return null; + } + const aggregated = this.aggregate(upperAsset, validPrices); + this.cache.setPrice(upperAsset, aggregated.price); + // Store in price history + this.priceHistory.addAggregatedPrice(aggregated); + return aggregated; + } + /** + * Fetch prices for multiple assets + */ + async getPrices(assets) { + const results = new Map(); + const promises = assets.map(async (asset) => { + const price = await this.getPrice(asset); + if (price) { + results.set(asset.toUpperCase(), price); + } + }); + await Promise.allSettled(promises); + return results; + } + /** + * Fetch price from providers with fallback logic + */ + async fetchWithFallback(asset) { + const validPrices = []; + const errors = new Map(); + for (const provider of this.providers) { + try { + const circuitBreaker = this.circuitBreakers.get(provider.name); + // Check circuit breaker state + if (circuitBreaker && !circuitBreaker.isAllowed()) { + logger.warn(`Circuit breaker OPEN for ${provider.name}, skipping`); + continue; + } + const rawPrice = await provider.fetchPrice(asset); + const validation = this.validator.validate(rawPrice); + if (validation.isValid && validation.price) { + validPrices.push(validation.price); + // Record success for circuit breaker + if (circuitBreaker) { + circuitBreaker.recordSuccess(); + } + logger.debug(`Got valid price from ${provider.name} for ${asset}`, { + price: validation.price.price.toString(), + }); + } + else { + // Record failure for circuit breaker + if (circuitBreaker) { + circuitBreaker.recordFailure(); + } + logger.warn(`Invalid price from ${provider.name} for ${asset}`, { + errors: validation.errors, + }); + } + } + catch (error) { + // Record failure for circuit breaker + const circuitBreaker = this.circuitBreakers.get(provider.name); + if (circuitBreaker) { + circuitBreaker.recordFailure(); + } + errors.set(provider.name, error instanceof Error ? error : new Error(String(error))); + logger.warn(`Provider ${provider.name} failed for ${asset}`, { error }); + } + } + if (validPrices.length === 0 && errors.size > 0) { + logger.error(`All providers failed for ${asset}`, { + providers: Array.from(errors.keys()), + }); + } + return validPrices; + } + /** + * Aggregate prices from multiple sources + */ + aggregate(asset, prices) { + const now = Math.floor(Date.now() / 1000); + if (prices.length === 1) { + return { + asset, + price: prices[0].price, + sources: prices, + timestamp: now, + confidence: prices[0].confidence, + }; + } + const aggregatedPrice = this.config.useWeightedMedian + ? this.weightedMedian(prices) + : this.simpleMedian(prices); + const totalWeight = this.providers + .filter((p) => prices.some((pr) => pr.source === p.name)) + .reduce((sum, p) => sum + p.weight, 0); + const weightedConfidence = prices.reduce((sum, p) => { + const provider = this.providers.find((pr) => pr.name === p.source); + const weight = provider?.weight ?? 0.1; + return sum + p.confidence * weight; + }, 0) / totalWeight; + return { + asset, + price: aggregatedPrice, + sources: prices, + timestamp: now, + confidence: Math.round(weightedConfidence), + }; + } + /** + * Calculate weighted median of prices + */ + weightedMedian(prices) { + const sorted = [...prices].sort((a, b) => (a.price < b.price ? -1 : a.price > b.price ? 1 : 0)); + const weights = sorted.map((p) => { + const provider = this.providers.find((pr) => pr.name === p.source); + return provider?.weight ?? 0.1; + }); + const totalWeight = weights.reduce((a, b) => a + b, 0); + const halfWeight = totalWeight / 2; + let cumWeight = 0; + for (let i = 0; i < sorted.length; i++) { + cumWeight += weights[i]; + if (cumWeight >= halfWeight) { + return sorted[i].price; + } + } + return sorted[sorted.length - 1].price; + } + /** + * Calculate simple median of prices + */ + simpleMedian(prices) { + const sorted = [...prices].sort((a, b) => (a.price < b.price ? -1 : a.price > b.price ? 1 : 0)); + const mid = Math.floor(sorted.length / 2); + if (sorted.length % 2 === 0) { + const avg = (sorted[mid - 1].price + sorted[mid].price) / 2n; + return avg; + } + return sorted[mid].price; + } + /** + * Get price history service + */ + getPriceHistory() { + return this.priceHistory; + } + /** + * Get circuit breaker metrics for all providers + */ + getCircuitBreakerMetrics() { + const metrics = []; + for (const breaker of this.circuitBreakers.values()) { + metrics.push(breaker.getMetrics()); + } + return metrics; + } + /** + * Get list of enabled providers + */ + getProviders() { + return this.providers.map((p) => p.name); + } + /** + * Get aggregator statistics + */ + getStats() { + return { + enabledProviders: this.providers.length, + cacheStats: this.cache.getStats(), + priceHistoryStats: this.priceHistory.getStats(), + circuitBreakerMetrics: this.getCircuitBreakerMetrics(), + circuitBreakers: this.getCircuitBreakerMetrics(), + }; + } +} +function isAggregatorConfig(value) { + if (!value || typeof value !== 'object') { + return false; + } + return 'minSources' in value || 'useWeightedMedian' in value || 'circuitBreaker' in value; +} +function isPriceHistoryService(value) { + return value instanceof PriceHistoryService; +} +/** + * Create a price aggregator + */ +export function createAggregator(providers, validator, cache, priceHistoryOrConfig, config) { + const priceHistory = isPriceHistoryService(priceHistoryOrConfig) + ? priceHistoryOrConfig + : new PriceHistoryService(); + const resolvedConfig = isPriceHistoryService(priceHistoryOrConfig) + ? config + : isAggregatorConfig(priceHistoryOrConfig) + ? priceHistoryOrConfig + : config; + return new PriceAggregator(providers, validator, cache, priceHistory, resolvedConfig); +} +//# sourceMappingURL=price-aggregator.js.map \ No newline at end of file diff --git a/oracle/dist/services/price-aggregator.js.map b/oracle/dist/services/price-aggregator.js.map new file mode 100644 index 00000000..315a9132 --- /dev/null +++ b/oracle/dist/services/price-aggregator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"price-aggregator.js","sourceRoot":"","sources":["../../src/services/price-aggregator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/services/price-history.d.ts b/oracle/dist/services/price-history.d.ts new file mode 100644 index 00000000..4a70a775 --- /dev/null +++ b/oracle/dist/services/price-history.d.ts @@ -0,0 +1,110 @@ +/** + * Price History Service + * + * Stores historical price data for trend analysis, TWAP calculations, and debugging. + * Uses a circular buffer to maintain memory-bounded storage. + */ +import type { AggregatedPrice } from '../types/index.js'; +/** + * Price history entry + */ +export interface PriceHistoryEntry { + price: bigint; + timestamp: number; +} +/** + * Price history interface + */ +export interface PriceHistory { + entries: PriceHistoryEntry[]; + maxEntries: number; + currentIndex: number; + isFull: boolean; +} +/** + * TWAP calculation result + */ +export interface TWAPResult { + asset: string; + twap: bigint; + periodSeconds: number; + dataPoints: number; + startTime: number; + endTime: number; +} +/** + * Price history configuration + */ +export interface PriceHistoryConfig { + maxEntries: number; +} +/** + * Price History Service + */ +export declare class PriceHistoryService { + private histories; + private config; + constructor(config?: Partial); + /** + * Add a price entry to history + */ + addPriceEntry(asset: string, price: bigint, timestamp: number): void; + /** + * Add aggregated price to history + */ + addAggregatedPrice(price: AggregatedPrice): void; + /** + * Get price history for an asset + */ + getPriceHistory(asset: string, limit?: number): PriceHistoryEntry[]; + /** + * Calculate Time-Weighted Average Price (TWAP) + */ + calculateTWAP(asset: string, periodSeconds: number): TWAPResult | null; + /** + * Get the latest price for an asset + */ + getLatestPrice(asset: string): PriceHistoryEntry | null; + /** + * Get statistics for an asset + */ + getAssetStats(asset: string): { + totalEntries: number; + oldestTimestamp?: number; + newestTimestamp?: number; + priceRange?: { + min: bigint; + max: bigint; + }; + }; + /** + * Clear history for an asset + */ + clearHistory(asset: string): void; + /** + * Clear all history + */ + clearAllHistory(): void; + /** + * Get list of assets with history + */ + getAssets(): string[]; + /** + * Get service statistics + */ + getStats(): { + trackedAssets: number; + totalEntries: number; + maxEntriesPerAsset: number; + assets: string[]; + }; + /** + * Get or create history for an asset + */ + private getOrCreateHistory; +} +/** + * Create a price history service + */ +export declare function createPriceHistoryService(config?: Partial): PriceHistoryService; +//# sourceMappingURL=price-history.d.ts.map \ No newline at end of file diff --git a/oracle/dist/services/price-history.d.ts.map b/oracle/dist/services/price-history.d.ts.map new file mode 100644 index 00000000..7bcf6b0c --- /dev/null +++ b/oracle/dist/services/price-history.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"price-history.d.ts","sourceRoot":"","sources":["../../src/services/price-history.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGzD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB;AASD;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,MAAM,CAAqB;gBAEvB,MAAM,GAAE,OAAO,CAAC,kBAAkB,CAAM;IASpD;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IA0BpE;;OAEG;IACH,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAIhD;;OAEG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAuCnE;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAuEtE;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAcvD;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;QAC5B,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,UAAU,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;KAC3C;IAsBD;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOjC;;OAEG;IACH,eAAe,IAAI,IAAI;IAMvB;;OAEG;IACH,SAAS,IAAI,MAAM,EAAE;IAIrB;;OAEG;IACH,QAAQ;;;;;;IAkBR;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAe3B;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACnC,mBAAmB,CAErB"} \ No newline at end of file diff --git a/oracle/dist/services/price-history.js b/oracle/dist/services/price-history.js new file mode 100644 index 00000000..f59e93d0 --- /dev/null +++ b/oracle/dist/services/price-history.js @@ -0,0 +1,252 @@ +/** + * Price History Service + * + * Stores historical price data for trend analysis, TWAP calculations, and debugging. + * Uses a circular buffer to maintain memory-bounded storage. + */ +import { logger } from '../utils/logger.js'; +/** + * Default configuration + */ +const DEFAULT_CONFIG = { + maxEntries: 100, +}; +/** + * Price History Service + */ +export class PriceHistoryService { + histories; + config; + constructor(config = {}) { + this.config = { ...DEFAULT_CONFIG, ...config }; + this.histories = new Map(); + logger.info('Price history service initialized', { + maxEntries: this.config.maxEntries, + }); + } + /** + * Add a price entry to history + */ + addPriceEntry(asset, price, timestamp) { + const upperAsset = asset.toUpperCase(); + const history = this.getOrCreateHistory(upperAsset); + // Add entry at current index (circular buffer behavior) + history.entries[history.currentIndex] = { + price, + timestamp, + }; + // Move to next position + history.currentIndex = (history.currentIndex + 1) % history.maxEntries; + // Mark as full after we've wrapped around + if (history.currentIndex === 0) { + history.isFull = true; + } + logger.debug(`Added price history entry for ${upperAsset}`, { + price: price.toString(), + timestamp, + currentIndex: history.currentIndex, + isFull: history.isFull, + }); + } + /** + * Add aggregated price to history + */ + addAggregatedPrice(price) { + this.addPriceEntry(price.asset, price.price, price.timestamp); + } + /** + * Get price history for an asset + */ + getPriceHistory(asset, limit) { + const upperAsset = asset.toUpperCase(); + const history = this.histories.get(upperAsset); + if (!history) { + return []; + } + const entries = []; + if (history.isFull) { + // Buffer is full, return entries starting from current index + const startIndex = history.currentIndex; + for (let i = 0; i < history.maxEntries; i++) { + const index = (startIndex + i) % history.maxEntries; + const entry = history.entries[index]; + if (entry) { + entries.push(entry); + if (limit && entries.length >= limit) { + break; + } + } + } + } + else { + // Buffer not full, return entries from start + for (let i = 0; i < history.currentIndex; i++) { + const entry = history.entries[i]; + if (entry) { + entries.push(entry); + if (limit && entries.length >= limit) { + break; + } + } + } + } + return entries; + } + /** + * Calculate Time-Weighted Average Price (TWAP) + */ + calculateTWAP(asset, periodSeconds) { + const upperAsset = asset.toUpperCase(); + const entries = this.getPriceHistory(upperAsset); + if (entries.length < 2) { + logger.warn(`Insufficient data for TWAP calculation for ${upperAsset}`, { + availableEntries: entries.length, + required: 2, + }); + return null; + } + const now = Math.floor(Date.now() / 1000); + const startTime = now - periodSeconds; + // Filter entries within the time period + const periodEntries = entries.filter((entry) => entry.timestamp >= startTime); + if (periodEntries.length < 2) { + logger.warn(`Insufficient data within time period for TWAP calculation for ${upperAsset}`, { + periodSeconds, + availableEntries: periodEntries.length, + required: 2, + }); + return null; + } + // Calculate TWAP using time-weighted average + let totalTime = 0; + let weightedSum = 0n; + for (let i = 0; i < periodEntries.length - 1; i++) { + const current = periodEntries[i]; + const next = periodEntries[i + 1]; + const timeDiff = next.timestamp - current.timestamp; + totalTime += timeDiff; + weightedSum += current.price * BigInt(timeDiff); + } + // Add the last entry's contribution (assume it lasts until now) + const lastEntry = periodEntries[periodEntries.length - 1]; + const lastTimeDiff = now - lastEntry.timestamp; + totalTime += lastTimeDiff; + weightedSum += lastEntry.price * BigInt(lastTimeDiff); + if (totalTime === 0) { + logger.warn(`Zero time duration for TWAP calculation for ${upperAsset}`); + return null; + } + const twap = weightedSum / BigInt(totalTime); + const result = { + asset: upperAsset, + twap, + periodSeconds, + dataPoints: periodEntries.length, + startTime: periodEntries[0].timestamp, + endTime: lastEntry.timestamp, + }; + logger.info(`Calculated TWAP for ${upperAsset}`, { + twap: twap.toString(), + periodSeconds, + dataPoints: periodEntries.length, + }); + return result; + } + /** + * Get the latest price for an asset + */ + getLatestPrice(asset) { + const upperAsset = asset.toUpperCase(); + const history = this.histories.get(upperAsset); + if (!history || (history.currentIndex === 0 && !history.isFull)) { + return null; + } + // Get the most recent entry + const latestIndex = history.currentIndex === 0 ? history.maxEntries - 1 : history.currentIndex - 1; + return history.entries[latestIndex] || null; + } + /** + * Get statistics for an asset + */ + getAssetStats(asset) { + const upperAsset = asset.toUpperCase(); + const entries = this.getPriceHistory(upperAsset); + if (entries.length === 0) { + return { totalEntries: 0 }; + } + const timestamps = entries.map((e) => e.timestamp); + const prices = entries.map((e) => e.price); + return { + totalEntries: entries.length, + oldestTimestamp: Math.min(...timestamps), + newestTimestamp: Math.max(...timestamps), + priceRange: { + min: prices.reduce((a, b) => (a < b ? a : b)), + max: prices.reduce((a, b) => (a > b ? a : b)), + }, + }; + } + /** + * Clear history for an asset + */ + clearHistory(asset) { + const upperAsset = asset.toUpperCase(); + this.histories.delete(upperAsset); + logger.info(`Cleared price history for ${upperAsset}`); + } + /** + * Clear all history + */ + clearAllHistory() { + this.histories.clear(); + logger.info('Cleared all price history'); + } + /** + * Get list of assets with history + */ + getAssets() { + return Array.from(this.histories.keys()); + } + /** + * Get service statistics + */ + getStats() { + const assets = this.getAssets(); + const totalEntries = assets.reduce((sum, asset) => { + const history = this.histories.get(asset); + if (history) { + return sum + (history.isFull ? history.maxEntries : history.currentIndex); + } + return sum; + }, 0); + return { + trackedAssets: assets.length, + totalEntries, + maxEntriesPerAsset: this.config.maxEntries, + assets, + }; + } + /** + * Get or create history for an asset + */ + getOrCreateHistory(asset) { + let history = this.histories.get(asset); + if (!history) { + history = { + entries: new Array(this.config.maxEntries), + maxEntries: this.config.maxEntries, + currentIndex: 0, + isFull: false, + }; + this.histories.set(asset, history); + } + return history; + } +} +/** + * Create a price history service + */ +export function createPriceHistoryService(config) { + return new PriceHistoryService(config); +} +//# sourceMappingURL=price-history.js.map \ No newline at end of file diff --git a/oracle/dist/services/price-history.js.map b/oracle/dist/services/price-history.js.map new file mode 100644 index 00000000..aa6593e1 --- /dev/null +++ b/oracle/dist/services/price-history.js.map @@ -0,0 +1 @@ +{"version":3,"file":"price-history.js","sourceRoot":"","sources":["../../src/services/price-history.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/services/price-validator.d.ts b/oracle/dist/services/price-validator.d.ts new file mode 100644 index 00000000..277944b4 --- /dev/null +++ b/oracle/dist/services/price-validator.d.ts @@ -0,0 +1,54 @@ +/** + * Price Validator Service + * + * Validates and sanitizes price data before it's used for + * contract updates. Implements multiple validation checks: + */ +import type { RawPriceData, ValidationResult } from '../types/index.js'; +/** + * Validator configuration + */ +export interface ValidatorConfig { + maxDeviationPercent: number; + maxStalenessSeconds: number; + minPrice: number; + maxPrice: number; + sourceWeights: Record; +} +/** + * Price Validator + */ +export declare class PriceValidator { + private config; + private cachedPrices; + constructor(config?: Partial); + /** + * Validate raw price data and convert to validated PriceData + */ + validate(raw: RawPriceData): ValidationResult; + /** + * Validate multiple prices + */ + validateMany(prices: RawPriceData[]): ValidationResult[]; + /** + * Calculate confidence score based on various factors + */ + private calculateConfidence; + /** + * Update cached price manually (e.g., after successful contract update) + */ + updateCache(asset: string, price: number): void; + /** + * Clear cached price for an asset + */ + clearCache(asset?: string): void; + /** + * Get current cache state (for debugging) + */ + getCacheState(): Record; +} +/** + * Create a validator with custom configuration + */ +export declare function createValidator(config?: Partial): PriceValidator; +//# sourceMappingURL=price-validator.d.ts.map \ No newline at end of file diff --git a/oracle/dist/services/price-validator.d.ts.map b/oracle/dist/services/price-validator.d.ts.map new file mode 100644 index 00000000..0e7ee951 --- /dev/null +++ b/oracle/dist/services/price-validator.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"price-validator.d.ts","sourceRoot":"","sources":["../../src/services/price-validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,YAAY,EAEZ,gBAAgB,EAGjB,MAAM,mBAAmB,CAAC;AAI3B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAiBD;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,YAAY,CAAkC;gBAE1C,MAAM,GAAE,OAAO,CAAC,eAAe,CAAM;IASjD;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB;IA8E7C;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE;IAIxD;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAqB3B;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI/C;;OAEG;IACH,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAQhC;;OAEG;IACH,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAGxC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,cAAc,CAEjF"} \ No newline at end of file diff --git a/oracle/dist/services/price-validator.js b/oracle/dist/services/price-validator.js new file mode 100644 index 00000000..a8964616 --- /dev/null +++ b/oracle/dist/services/price-validator.js @@ -0,0 +1,159 @@ +/** + * Price Validator Service + * + * Validates and sanitizes price data before it's used for + * contract updates. Implements multiple validation checks: + */ +import { scalePrice } from '../config.js'; +import { logger } from '../utils/logger.js'; +/** + * Default validator configuration + */ +const DEFAULT_CONFIG = { + maxDeviationPercent: 10, + maxStalenessSeconds: 300, + minPrice: 0.0000001, + maxPrice: 1000000000, + sourceWeights: { + coingecko: 1.0, + binance: 0.95, + coinmarketcap: 1.0, + }, +}; +/** + * Price Validator + */ +export class PriceValidator { + config; + cachedPrices = new Map(); + constructor(config = {}) { + this.config = { ...DEFAULT_CONFIG, ...config }; + logger.info('Price validator initialized', { + maxDeviationPercent: this.config.maxDeviationPercent, + maxStalenessSeconds: this.config.maxStalenessSeconds, + }); + } + /** + * Validate raw price data and convert to validated PriceData + */ + validate(raw) { + const errors = []; + if (raw.price <= 0) { + errors.push({ + code: 'PRICE_ZERO', + message: `Price must be positive, got ${raw.price}`, + }); + } + if (raw.price < this.config.minPrice) { + errors.push({ + code: 'PRICE_ZERO', + message: `Price ${raw.price} below minimum ${this.config.minPrice}`, + }); + } + if (raw.price > this.config.maxPrice) { + errors.push({ + code: 'PRICE_DEVIATION_TOO_HIGH', + message: `Price ${raw.price} exceeds maximum ${this.config.maxPrice}`, + }); + } + const now = Math.floor(Date.now() / 1000); + const age = now - raw.timestamp; + if (age > this.config.maxStalenessSeconds) { + errors.push({ + code: 'PRICE_STALE', + message: `Price is ${age}s old, max allowed is ${this.config.maxStalenessSeconds}s`, + details: { age, maxAge: this.config.maxStalenessSeconds }, + }); + } + const cachedPrice = this.cachedPrices.get(raw.asset); + if (cachedPrice !== undefined) { + const deviation = Math.abs((raw.price - cachedPrice) / cachedPrice) * 100; + if (deviation > this.config.maxDeviationPercent) { + errors.push({ + code: 'PRICE_DEVIATION_TOO_HIGH', + message: `Price deviation ${deviation.toFixed(2)}% exceeds max ${this.config.maxDeviationPercent}%`, + details: { + newPrice: raw.price, + cachedPrice, + deviationPercent: deviation, + }, + }); + } + } + if (errors.length === 0) { + const validatedPrice = { + asset: raw.asset.toUpperCase(), + price: scalePrice(raw.price), + timestamp: raw.timestamp, + source: raw.source, + confidence: this.calculateConfidence(raw, cachedPrice), + }; + this.cachedPrices.set(raw.asset, raw.price); + return { + isValid: true, + price: validatedPrice, + errors: [], + }; + } + logger.warn(`Price validation failed for ${raw.asset}`, { errors }); + return { + isValid: false, + errors, + }; + } + /** + * Validate multiple prices + */ + validateMany(prices) { + return prices.map((p) => this.validate(p)); + } + /** + * Calculate confidence score based on various factors + */ + calculateConfidence(raw, cachedPrice) { + let confidence = 100; + const now = Math.floor(Date.now() / 1000); + const age = now - raw.timestamp; + const ageRatio = age / this.config.maxStalenessSeconds; + confidence -= Math.min(20, ageRatio * 20); + if (cachedPrice !== undefined) { + const deviation = Math.abs((raw.price - cachedPrice) / cachedPrice) * 100; + const deviationRatio = deviation / this.config.maxDeviationPercent; + confidence -= Math.min(30, deviationRatio * 30); + } + // Apply configurable source weight + const sourceWeight = this.config.sourceWeights[raw.source] || 1.0; + confidence *= sourceWeight; + return Math.max(0, Math.min(100, confidence)); + } + /** + * Update cached price manually (e.g., after successful contract update) + */ + updateCache(asset, price) { + this.cachedPrices.set(asset.toUpperCase(), price); + } + /** + * Clear cached price for an asset + */ + clearCache(asset) { + if (asset) { + this.cachedPrices.delete(asset.toUpperCase()); + } + else { + this.cachedPrices.clear(); + } + } + /** + * Get current cache state (for debugging) + */ + getCacheState() { + return Object.fromEntries(this.cachedPrices); + } +} +/** + * Create a validator with custom configuration + */ +export function createValidator(config) { + return new PriceValidator(config); +} +//# sourceMappingURL=price-validator.js.map \ No newline at end of file diff --git a/oracle/dist/services/price-validator.js.map b/oracle/dist/services/price-validator.js.map new file mode 100644 index 00000000..d419a9d2 --- /dev/null +++ b/oracle/dist/services/price-validator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"price-validator.js","sourceRoot":"","sources":["../../src/services/price-validator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/types/index.d.ts b/oracle/dist/types/index.d.ts new file mode 100644 index 00000000..1cc95618 --- /dev/null +++ b/oracle/dist/types/index.d.ts @@ -0,0 +1,155 @@ +/** + * Oracle Service Type Definitions + * + * This module contains all TypeScript interfaces and types used across + * the Oracle Integration Service for StellarLend protocol. + */ +/** + * Represents price data fetched from an external source + */ +export interface PriceData { + asset: string; + price: bigint; + timestamp: number; + source: string; + confidence: number; +} +/** + * Raw price data before validation and conversion + */ +export interface RawPriceData { + asset: string; + price: number; + timestamp: number; + source: string; +} +/** + * Aggregated price from multiple sources + */ +export interface AggregatedPrice { + asset: string; + price: bigint; + sources: PriceData[]; + timestamp: number; + confidence: number; +} +/** + * Price validation result + */ +export interface ValidationResult { + isValid: boolean; + price?: PriceData; + errors: ValidationError[]; +} +/** + * Validation error details + */ +export interface ValidationError { + code: ValidationErrorCode; + message: string; + details?: Record; +} +/** + * Validation error codes + */ +export declare enum ValidationErrorCode { + PRICE_ZERO = "PRICE_ZERO", + PRICE_NEGATIVE = "PRICE_NEGATIVE", + PRICE_STALE = "PRICE_STALE", + PRICE_DEVIATION_TOO_HIGH = "PRICE_DEVIATION_TOO_HIGH", + INVALID_ASSET = "INVALID_ASSET", + SOURCE_UNAVAILABLE = "SOURCE_UNAVAILABLE" +} +/** + * Provider configuration + */ +export interface ProviderConfig { + name: string; + enabled: boolean; + priority: number; + weight: number; + apiKey?: string; + baseUrl: string; + rateLimit: { + maxRequests: number; + windowMs: number; + }; + concurrencyLimit?: number; +} +/** + * Cache entry structure + */ +export interface CacheEntry { + data: T; + cachedAt: number; + expiresAt: number; +} +/** + * Contract update result + */ +export interface ContractUpdateResult { + success: boolean; + transactionHash?: string; + asset: string; + price: bigint; + timestamp: number; + error?: string; +} +/** + * Service configuration + */ +export interface OracleServiceConfig { + stellarNetwork: 'testnet' | 'mainnet'; + stellarRpcUrl: string; + baseFee: number; + maxFee: number; + contractId: string; + adminSecretKey: string; + dryRun?: boolean; + updateIntervalMs: number; + maxPriceDeviationPercent: number; + priceStaleThresholdSeconds: number; + cacheTtlSeconds: number; + redisUrl?: string; + logLevel: 'debug' | 'info' | 'warn' | 'error'; + providers: ProviderConfig[]; + circuitBreaker: { + failureThreshold: number; + backoffMs: number; + }; +} +/** + * Supported assets for price fetching + */ +export type SupportedAsset = 'XLM' | 'USDC' | 'USDT' | 'BTC' | 'ETH'; +/** + * Asset mapping for different providers + */ +export interface AssetMapping { + symbol: SupportedAsset; + coingeckoId: string; + coinmarketcapId: number; + binanceSymbol: string; +} +/** + * Health check status + */ +export interface HealthStatus { + provider: string; + healthy: boolean; + lastCheck: number; + latencyMs?: number; + error?: string; +} +/** + * Service metrics for monitoring + */ +export interface ServiceMetrics { + priceUpdatesTotal: number; + priceUpdatesFailed: number; + cacheHits: number; + cacheMisses: number; + providerErrors: Map; + lastUpdateTimestamp: number; +} +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/oracle/dist/types/index.d.ts.map b/oracle/dist/types/index.d.ts.map new file mode 100644 index 00000000..4439f04e --- /dev/null +++ b/oracle/dist/types/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/oracle/dist/types/index.js b/oracle/dist/types/index.js new file mode 100644 index 00000000..ce690172 --- /dev/null +++ b/oracle/dist/types/index.js @@ -0,0 +1,19 @@ +/** + * Oracle Service Type Definitions + * + * This module contains all TypeScript interfaces and types used across + * the Oracle Integration Service for StellarLend protocol. + */ +/** + * Validation error codes + */ +export var ValidationErrorCode; +(function (ValidationErrorCode) { + ValidationErrorCode["PRICE_ZERO"] = "PRICE_ZERO"; + ValidationErrorCode["PRICE_NEGATIVE"] = "PRICE_NEGATIVE"; + ValidationErrorCode["PRICE_STALE"] = "PRICE_STALE"; + ValidationErrorCode["PRICE_DEVIATION_TOO_HIGH"] = "PRICE_DEVIATION_TOO_HIGH"; + ValidationErrorCode["INVALID_ASSET"] = "INVALID_ASSET"; + ValidationErrorCode["SOURCE_UNAVAILABLE"] = "SOURCE_UNAVAILABLE"; +})(ValidationErrorCode || (ValidationErrorCode = {})); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/oracle/dist/types/index.js.map b/oracle/dist/types/index.js.map new file mode 100644 index 00000000..09150a8e --- /dev/null +++ b/oracle/dist/types/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoDH;;GAEG;AACH,MAAM,CAAN,IAAY,mBAOX;AAPD,WAAY,mBAAmB;IAC7B,gDAAyB,CAAA;IACzB,wDAAiC,CAAA;IACjC,kDAA2B,CAAA;IAC3B,4EAAqD,CAAA;IACrD,sDAA+B,CAAA;IAC/B,gEAAyC,CAAA;AAC3C,CAAC,EAPW,mBAAmB,KAAnB,mBAAmB,QAO9B"} \ No newline at end of file diff --git a/oracle/dist/utils/logger.d.ts b/oracle/dist/utils/logger.d.ts new file mode 100644 index 00000000..21725b16 --- /dev/null +++ b/oracle/dist/utils/logger.d.ts @@ -0,0 +1,32 @@ +/** + * Logger Utility + * + * Centralized logging using Winston with configurable levels + * and structured output for the Oracle Service. + */ +import winston from 'winston'; +/** + * Create a configured logger instance + */ +export declare function createLogger(level?: string, useJson?: boolean): winston.Logger; +/** + * Default logger instance (can be reconfigured at runtime) + */ +export declare let logger: winston.Logger; +/** + * Configure the global logger with new settings + */ +export declare function configureLogger(level: string, useJson?: boolean): void; +/** + * Log with additional context for price operations + */ +export declare function logPriceUpdate(asset: string, price: bigint, source: string, success: boolean, details?: Record): void; +/** + * Log provider health status + */ +export declare function logProviderHealth(provider: string, healthy: boolean, latencyMs?: number, error?: string): void; +/** + * Log Oracle price staleness alert + */ +export declare function logStalenessAlert(ageSeconds: number, thresholdSeconds: number, lastUpdateTime?: number): void; +//# sourceMappingURL=logger.d.ts.map \ No newline at end of file diff --git a/oracle/dist/utils/logger.d.ts.map b/oracle/dist/utils/logger.d.ts.map new file mode 100644 index 00000000..039b71dd --- /dev/null +++ b/oracle/dist/utils/logger.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,OAAO,MAAM,SAAS,CAAC;AAwB9B;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,GAAE,MAAe,EAAE,OAAO,GAAE,OAAe,kBAU5E;AAED;;GAEG;AACH,eAAO,IAAI,MAAM,gBAAuB,CAAC;AAEzC;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,OAAe,QAEtE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAelC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,MAAM,QAcf;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,EACxB,cAAc,CAAC,EAAE,MAAM,QAQxB"} \ No newline at end of file diff --git a/oracle/dist/utils/logger.js b/oracle/dist/utils/logger.js new file mode 100644 index 00000000..0301a90f --- /dev/null +++ b/oracle/dist/utils/logger.js @@ -0,0 +1,97 @@ +/** + * Logger Utility + * + * Centralized logging using Winston with configurable levels + * and structured output for the Oracle Service. + */ +import winston from 'winston'; +const { combine, timestamp, printf, colorize, errors } = winston.format; +/** + * Custom log format for console output + */ +const consoleFormat = printf(({ level, message, timestamp, ...meta }) => { + const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; + return `${timestamp} [${level}]: ${message}${metaStr}`; +}); +/** + * Custom log format for JSON output (production) + */ +const jsonFormat = printf(({ level, message, timestamp, ...meta }) => { + return JSON.stringify({ + timestamp, + level, + message, + ...meta, + }); +}); +/** + * Create a configured logger instance + */ +export function createLogger(level = 'info', useJson = false) { + return winston.createLogger({ + level, + format: combine(errors({ stack: true }), timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' })), + transports: [ + new winston.transports.Console({ + format: combine(useJson ? jsonFormat : combine(colorize(), consoleFormat)), + }), + ], + }); +} +/** + * Default logger instance (can be reconfigured at runtime) + */ +export let logger = createLogger('info'); +/** + * Configure the global logger with new settings + */ +export function configureLogger(level, useJson = false) { + logger = createLogger(level, useJson); +} +/** + * Log with additional context for price operations + */ +export function logPriceUpdate(asset, price, source, success, details) { + const logData = { + asset, + price: price.toString(), + source, + success, + ...details, + }; + if (success) { + logger.info('Price update', logData); + } + else { + logger.error('Price update failed', logData); + } +} +/** + * Log provider health status + */ +export function logProviderHealth(provider, healthy, latencyMs, error) { + const logData = { + provider, + healthy, + latencyMs, + error, + }; + if (healthy) { + logger.debug('Provider health check', logData); + } + else { + logger.warn('Provider unhealthy', logData); + } +} +/** + * Log Oracle price staleness alert + */ +export function logStalenessAlert(ageSeconds, thresholdSeconds, lastUpdateTime) { + logger.warn('Oracle price staleness detected', { + ageSeconds, + thresholdSeconds, + lastUpdateTime: lastUpdateTime ? new Date(lastUpdateTime).toISOString() : 'never', + alertType: 'staleness_monitor', + }); +} +//# sourceMappingURL=logger.js.map \ No newline at end of file diff --git a/oracle/dist/utils/logger.js.map b/oracle/dist/utils/logger.js.map new file mode 100644 index 00000000..c89524e6 --- /dev/null +++ b/oracle/dist/utils/logger.js.map @@ -0,0 +1 @@ +{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"} \ No newline at end of file