Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/monitoringV2/contract.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(', ')}`);
}
Expand Down
102 changes: 46 additions & 56 deletions src/monitoringV2/price.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -129,30 +128,30 @@ export class PriceService {
return { baseUrl, headers };
}

private async getBtcPriceInUsd(): Promise<string | null> {
private async getBtcPriceInUsd(): Promise<string> {
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;
}

/**
Expand Down Expand Up @@ -185,31 +184,26 @@ export class PriceService {

const baseUrl = this.appConfigService.geckoTerminalBaseUrl;

try {
const response = await axios.get<TokenPrice>(
`${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<TokenPrice>(
`${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 }> {
Expand All @@ -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 };
}
Expand Down
22 changes: 22 additions & 0 deletions src/monitoringV2/prisma/repositories/contract.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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<Contract[]> {
try {
const contracts = await this.prisma.contract.findMany({
Expand Down
Loading