diff --git a/src/monitoringV2/contract.service.ts b/src/monitoringV2/contract.service.ts index 2490672..bb4fba4 100644 --- a/src/monitoringV2/contract.service.ts +++ b/src/monitoringV2/contract.service.ts @@ -74,7 +74,7 @@ export class ContractService { }, ]; - await this.contractRepo.createMany(coreContracts); + await this.contractRepo.upsertCore(coreContracts); this.logger.log(`Registry initialized with ${coreContracts.length} core contracts`); this.logger.log(`Registered addresses: ${coreContracts.map((c) => c.address).join(', ')}`); } diff --git a/src/monitoringV2/price.service.ts b/src/monitoringV2/price.service.ts index b4963a0..f432e96 100644 --- a/src/monitoringV2/price.service.ts +++ b/src/monitoringV2/price.service.ts @@ -91,7 +91,6 @@ export class PriceService { if (remaining.length === 0) return cached; const btcPrice = await this.getBtcPriceInUsd(); - if (!btcPrice) return cached; const prices: { [key: string]: string } = {}; for (const addr of remaining) { @@ -129,30 +128,30 @@ export class PriceService { return { baseUrl, headers }; } - private async getBtcPriceInUsd(): Promise { + private async getBtcPriceInUsd(): Promise { const cached = this.priceCache.get('btc-usd'); if (cached && Date.now() - cached.timestamp < this.CACHE_TTL_MS) { return cached.value; } - try { - const { baseUrl, headers } = this.resolveCoingeckoEndpoint(); - const response = await axios.get(`${baseUrl}/api/v3/simple/price?ids=bitcoin&vs_currencies=usd`, { - headers, - timeout: 10000, - }); - - const price = String(response.data.bitcoin.usd); - const now = Date.now(); - this.priceCache.set('btc-usd', { value: price, timestamp: now }); - this.btcLastSuccessMs = now; - this.btcStalenessAlertedAt = null; - this.logger.log(`BTC price: $${price}`); - return price; - } catch (error) { - this.logger.error(`Failed to fetch BTC price: ${error.message}`); - return cached?.value ?? null; + const { baseUrl, headers } = this.resolveCoingeckoEndpoint(); + const response = await axios.get(`${baseUrl}/api/v3/simple/price?ids=bitcoin&vs_currencies=usd`, { + headers, + timeout: 10000, + }); + + const priceNum = Number(response.data?.bitcoin?.usd); + if (!Number.isFinite(priceNum) || priceNum <= 0) { + throw new Error(`CoinGecko returned invalid BTC price: bitcoin.usd=${response.data?.bitcoin?.usd}`); } + + const price = String(priceNum); + const now = Date.now(); + this.priceCache.set('btc-usd', { value: price, timestamp: now }); + this.btcLastSuccessMs = now; + this.btcStalenessAlertedAt = null; + this.logger.log(`BTC price: $${price}`); + return price; } /** @@ -185,31 +184,26 @@ export class PriceService { const baseUrl = this.appConfigService.geckoTerminalBaseUrl; - try { - const response = await axios.get( - `${baseUrl}/api/v2/simple/networks/citrea/token_price/${remaining.map((a) => a.toLowerCase()).join(',')}`, - { - headers: { accept: 'application/json' }, - timeout: 10000, - } - ); - - const apiPrices = response.data.data.attributes.token_prices; - const normalizedPrices: { [key: string]: string } = {}; - for (const inputAddress of remaining) { - const price = apiPrices[inputAddress.toLowerCase()]; - if (price) { - normalizedPrices[inputAddress] = price; - this.setCache(inputAddress, price); - } + const response = await axios.get( + `${baseUrl}/api/v2/simple/networks/citrea/token_price/${remaining.map((a) => a.toLowerCase()).join(',')}`, + { + headers: { accept: 'application/json' }, + timeout: 10000, } + ); - this.logger.log(`Fetched prices for ${Object.keys(normalizedPrices).length} tokens from GeckoTerminal`); - return { ...cached, ...normalizedPrices }; - } catch (error) { - this.logger.error('Failed to fetch token prices from GeckoTerminal:', error.message); - return cached; + const apiPrices = response.data.data.attributes.token_prices; + const normalizedPrices: { [key: string]: string } = {}; + for (const inputAddress of remaining) { + const price = apiPrices[inputAddress.toLowerCase()]; + if (price) { + normalizedPrices[inputAddress] = price; + this.setCache(inputAddress, price); + } } + + this.logger.log(`Fetched prices for ${Object.keys(normalizedPrices).length} tokens from GeckoTerminal`); + return { ...cached, ...normalizedPrices }; } private async getEquityPrice(requestedAddresses: string[]): Promise<{ [key: string]: string }> { @@ -219,24 +213,20 @@ export class PriceService { const remaining = requestedAddresses.filter((addr) => !cached[addr]); if (remaining.length === 0) return cached; + const equityContract = new ethers.Contract( + ADDRESS[this.appConfigService.blockchainId].equity, + EquityABI, + this.providerService.provider + ); + const nativePrice = await equityContract.price(); + const formattedPrice = ethers.formatUnits(nativePrice, 18); + const prices: { [key: string]: string } = {}; for (const requestedAddress of remaining) { - try { - const equityContract = new ethers.Contract( - ADDRESS[this.appConfigService.blockchainId].equity, - EquityABI, - this.providerService.provider - ); - const nativePrice = await equityContract.price(); - const formattedPrice = ethers.formatUnits(nativePrice, 18); - - prices[requestedAddress] = formattedPrice; - this.setCache(requestedAddress, formattedPrice); - this.logger.debug(`Fetched equity price: ${formattedPrice}`); - } catch (error) { - this.logger.error(`Failed to fetch equity price: ${error.message}`); - } + prices[requestedAddress] = formattedPrice; + this.setCache(requestedAddress, formattedPrice); } + this.logger.debug(`Fetched equity price: ${formattedPrice}`); return { ...cached, ...prices }; } diff --git a/src/monitoringV2/prisma/repositories/contract.repository.ts b/src/monitoringV2/prisma/repositories/contract.repository.ts index 2d6ca2d..3b724fb 100644 --- a/src/monitoringV2/prisma/repositories/contract.repository.ts +++ b/src/monitoringV2/prisma/repositories/contract.repository.ts @@ -30,6 +30,28 @@ export class ContractRepository { } } + // Core contracts are the canonical, hard-coded protocol addresses from + // @juicedollar/jusd. Their `type` is authoritative and must override any + // earlier classification (e.g. a contract first persisted as generic + // MINTER via MinterApplied and later promoted to a known type when the + // package shipped its address). Use upsert so re-registration corrects + // the type instead of being silently skipped. + async upsertCore(contracts: Contract[]): Promise { + if (contracts.length === 0) return; + + for (const contract of contracts) { + const address = contract.address.toLowerCase(); + const metadata = contract.metadata || {}; + await this.prisma.contract.upsert({ + where: { address }, + create: { address, type: contract.type, timestamp: contract.timestamp, metadata }, + update: { type: contract.type, metadata }, + }); + } + + this.logger.log(`Upserted ${contracts.length} core contracts`); + } + async findAll(): Promise { try { const contracts = await this.prisma.contract.findMany({