diff --git a/api.apollo.config.ts b/api.apollo.config.ts new file mode 100644 index 0000000..6ed2907 --- /dev/null +++ b/api.apollo.config.ts @@ -0,0 +1,47 @@ +import { ApolloClient, ApolloLink, createHttpLink, InMemoryCache } from '@apollo/client/core'; +import { onError } from '@apollo/client/link/error'; +import fetch from 'cross-fetch'; +import { CONFIG } from './api.config'; + +const errorLink = onError(({ graphQLErrors, networkError, operation }) => { + if (graphQLErrors) { + graphQLErrors.forEach((error) => { + console.error(`[GraphQL error in operation: ${operation?.operationName || 'unknown'}]`, { + message: error.message, + locations: error.locations, + path: error.path, + }); + }); + } + if (networkError) { + console.error(`[Network error in operation: ${operation?.operationName || 'unknown'}]`, { + message: networkError.message, + name: networkError.name, + stack: networkError.stack, + }); + } +}); + +const httpLink = createHttpLink({ + uri: CONFIG.indexer, + fetch: (uri: RequestInfo | URL, options?: RequestInit) => { + const controller = new AbortController(); + const timeout = setTimeout(() => { + controller.abort(); + }, 10000); // 10 second timeout + + return fetch(uri, { + ...options, + signal: controller.signal, + }).finally(() => { + clearTimeout(timeout); + }); + }, +}); + +const link = ApolloLink.from([errorLink, httpLink]); + +export const PONDER_CLIENT = new ApolloClient({ + link, + cache: new InMemoryCache(), +}); diff --git a/api.config.ts b/api.config.ts index 5ffc3df..4a03f6f 100644 --- a/api.config.ts +++ b/api.config.ts @@ -1,4 +1,3 @@ -import { ApolloClient, InMemoryCache } from '@apollo/client/core'; import { Chain, createPublicClient, http } from 'viem'; import { mainnet, polygon } from 'viem/chains'; @@ -67,12 +66,6 @@ console.log(CONFIG); // Refer to https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files process.env.NTBA_FIX_350 = 'true'; -// PONDER CLIENT REQUEST -export const PONDER_CLIENT = new ApolloClient({ - uri: CONFIG.indexer, - cache: new InMemoryCache(), -}); - // VIEM CONFIG export const VIEM_CHAIN = CONFIG.chain; export const VIEM_CONFIG = createPublicClient({ diff --git a/api.service.ts b/api.service.ts index ebc68a3..3b718df 100644 --- a/api.service.ts +++ b/api.service.ts @@ -25,6 +25,7 @@ export class ApiService { private indexing: boolean = false; private indexingTimeoutCount: number = 0; private fetchedBlockheight: number = 0; + private isUpdatingWorkflow: boolean = false; constructor( private readonly minter: EcosystemMinterService, @@ -41,45 +42,77 @@ export class ApiService { } async updateWorkflow() { + if (this.isUpdatingWorkflow) { + this.logger.warn(`Skipping updateWorkflow - previous update still in progress at block ${this.fetchedBlockheight}`); + return; + } + + this.isUpdatingWorkflow = true; this.logger.log(`Fetched blockheight: ${this.fetchedBlockheight}`); - const promises = [ - this.minter.updateMinters(), - this.positions.updatePositonV2s(), - this.positions.updateMintingUpdateV2s(), - this.prices.updatePrices(), - this.stablecoin.updateEcosystemKeyValues(), - this.stablecoin.updateEcosystemMintBurnMapping(), - this.deps.updateDepsInfo(), - this.leadrate.updateLeadrateRates(), - this.leadrate.updateLeadrateProposals(), - this.challenges.updateChallengeV2s(), - this.challenges.updateBidV2s(), - this.challenges.updateChallengesPrices(), - this.savings.updateSavingsUserLeaderboard(), - ]; - return Promise.all(promises); + try { + const timeTask = async (name: string, fn: () => Promise) => { + const start = Date.now(); + try { + await fn(); + this.logger.debug(`${name} completed in ${Date.now() - start}ms`); + } catch (err) { + this.logger.error(`Failed to update ${name} after ${Date.now() - start}ms:`, err); + throw err; + } + }; + + const promises = [ + await timeTask('updateMinters', () => this.minter.updateMinters()).catch(() => {}), + await timeTask('updatePositonV2s', () => this.positions.updatePositonV2s()).catch(() => {}), + await timeTask('updateMintingUpdateV2s', () => this.positions.updateMintingUpdateV2s()).catch(() => {}), + await timeTask('updatePrices', () => this.prices.updatePrices()).catch(() => {}), + await timeTask('updateEcosystemKeyValues', () => this.stablecoin.updateEcosystemKeyValues()).catch(() => {}), + await timeTask('updateEcosystemMintBurnMapping', () => this.stablecoin.updateEcosystemMintBurnMapping()).catch(() => {}), + await timeTask('updateDepsInfo', () => this.deps.updateDepsInfo()).catch(() => {}), + await timeTask('updateLeadrateRates', () => this.leadrate.updateLeadrateRates()).catch(() => {}), + await timeTask('updateLeadrateProposals', () => this.leadrate.updateLeadrateProposals()).catch(() => {}), + await timeTask('updateChallengeV2s', () => this.challenges.updateChallengeV2s()).catch(() => {}), + await timeTask('updateBidV2s', () => this.challenges.updateBidV2s()).catch(() => {}), + await timeTask('updateChallengesPrices', () => this.challenges.updateChallengesPrices()).catch(() => {}), + await timeTask('updateSavingsUserLeaderboard', () => this.savings.updateSavingsUserLeaderboard()).catch(() => {}), + ]; + + await Promise.all(promises); + } finally { + this.isUpdatingWorkflow = false; + } } async updateSocialMedia() { - this.socialMediaService.update(); + this.socialMediaService.update().catch((err) => this.logger.error('Failed to update social media:', err)); } @Interval(POLLING_DELAY[CONFIG.chain.id]) async updateBlockheight() { - const tmp: number = parseInt((await VIEM_CONFIG.getBlockNumber()).toString()); - this.indexingTimeoutCount += 1; - if (tmp > this.fetchedBlockheight && !this.indexing) { - this.indexing = true; - await this.updateWorkflow(); - await this.updateSocialMedia(); - this.indexingTimeoutCount = 0; - this.fetchedBlockheight = tmp; - this.indexing = false; - } - if (this.indexingTimeoutCount >= INDEXING_TIMEOUT_COUNT && this.indexing) { - this.indexingTimeoutCount = 0; - this.indexing = false; + try { + const tmp: number = parseInt((await VIEM_CONFIG.getBlockNumber()).toString()); + this.indexingTimeoutCount += 1; + if (tmp > this.fetchedBlockheight && !this.indexing) { + this.indexing = true; + try { + await this.updateWorkflow(); + await this.updateSocialMedia(); + this.indexingTimeoutCount = 0; + this.fetchedBlockheight = tmp; + } catch (error) { + this.logger.error('Error in updateWorkflow:', error); + } finally { + this.indexing = false; + } + } + if (this.indexingTimeoutCount >= INDEXING_TIMEOUT_COUNT && this.indexing) { + this.logger.warn(`Indexing timeout reached after ${INDEXING_TIMEOUT_COUNT} attempts`); + this.indexingTimeoutCount = 0; + this.indexing = false; + } + } catch (error) { + this.logger.error('Error getting block number:', error); } } } diff --git a/bridge/bridge.service.ts b/bridge/bridge.service.ts index 633a4b7..062317c 100644 --- a/bridge/bridge.service.ts +++ b/bridge/bridge.service.ts @@ -1,18 +1,19 @@ import { gql } from '@apollo/client/core'; import { Injectable } from '@nestjs/common'; -import { PONDER_CLIENT } from 'api.config'; +import { PONDER_CLIENT } from 'api.apollo.config'; import { StablecoinBridgeQuery } from './bridge.types'; +import { StablecoinEnum } from './bridge.enum'; + @Injectable() export class BridgeService { - async getBridgedStables(stablecoinParam: string, timestamp: Date): Promise { - const stablecoin = stablecoinParam.toUpperCase(); + async getBridgedStables(stablecoin: StablecoinEnum, timestamp: Date): Promise { const checkTimestamp = Math.trunc(timestamp.getTime() / 1000); const bridgeFetched = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetBridge${stablecoin} { bridge${stablecoin}s( orderBy: "timestamp", orderDirection: "desc" where: { diff --git a/challenges/challenges.service.ts b/challenges/challenges.service.ts index 5c4e6ca..5aac60b 100644 --- a/challenges/challenges.service.ts +++ b/challenges/challenges.service.ts @@ -1,6 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { gql } from '@apollo/client/core'; -import { PONDER_CLIENT, VIEM_CONFIG } from 'api.config'; +import { VIEM_CONFIG } from 'api.config'; +import { PONDER_CLIENT } from 'api.apollo.config'; import { ApiBidsBidders, ApiBidsChallenges, @@ -199,7 +200,7 @@ export class ChallengesService { const challenges = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetChallengesV2 { challengeV2s(orderBy: "status", orderDirection: "asc", limit: 1000) { items { id @@ -244,7 +245,7 @@ export class ChallengesService { const bids = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetChallengeBidsV2 { challengeBidV2s(orderBy: "created", orderDirection: "desc", limit: 1000) { items { id diff --git a/ecosystem/ecosystem.deps.service.ts b/ecosystem/ecosystem.deps.service.ts index 26cc869..1b668ed 100644 --- a/ecosystem/ecosystem.deps.service.ts +++ b/ecosystem/ecosystem.deps.service.ts @@ -1,7 +1,8 @@ import { gql } from '@apollo/client/core'; import { ADDRESS, DecentralizedEUROABI, EquityABI } from '@deuro/eurocoin'; import { Injectable, Logger } from '@nestjs/common'; -import { PONDER_CLIENT, VIEM_CONFIG } from 'api.config'; +import { VIEM_CONFIG } from 'api.config'; +import { PONDER_CLIENT } from 'api.apollo.config'; import { PositionsService } from 'positions/positions.service'; import { formatUnits } from 'viem'; import { ApiEcosystemDepsInfo } from './ecosystem.deps.types'; @@ -52,7 +53,7 @@ export class EcosystemDepsService { const profitLossPonder = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetDEPS { dEPSs(orderBy: "id", limit: 1000) { items { id diff --git a/ecosystem/ecosystem.minter.service.ts b/ecosystem/ecosystem.minter.service.ts index 3fd42c8..2291b4a 100644 --- a/ecosystem/ecosystem.minter.service.ts +++ b/ecosystem/ecosystem.minter.service.ts @@ -1,5 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -import { PONDER_CLIENT } from '../api.config'; +import { PONDER_CLIENT } from '../api.apollo.config'; import { gql } from '@apollo/client/core'; import { ApiMinterListing, ApiMinterMapping, MinterQuery, MinterQueryObjectArray } from './ecosystem.minter.types'; import { Address } from 'viem'; @@ -31,7 +31,7 @@ export class EcosystemMinterService { const { data } = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetMinters { minters(orderBy: "id", limit: 1000) { items { id diff --git a/ecosystem/ecosystem.stablecoin.service.ts b/ecosystem/ecosystem.stablecoin.service.ts index 8771644..89acb74 100644 --- a/ecosystem/ecosystem.stablecoin.service.ts +++ b/ecosystem/ecosystem.stablecoin.service.ts @@ -1,7 +1,8 @@ import { gql } from '@apollo/client/core'; import { ADDRESS } from '@deuro/eurocoin'; import { Injectable, Logger } from '@nestjs/common'; -import { CONFIG, PONDER_CLIENT } from 'api.config'; +import { PONDER_CLIENT } from 'api.apollo.config'; +import { CONFIG } from 'api.config'; import { PricesService } from 'prices/prices.service'; import { Address } from 'viem'; import { EcosystemCollateralService } from './ecosystem.collateral.service'; @@ -72,7 +73,7 @@ export class EcosystemStablecoinService { const ecosystem = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetEcosystems { ecosystems(orderBy: "id", limit: 1000) { items { id @@ -129,7 +130,7 @@ export class EcosystemStablecoinService { const response = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetMintBurnAddressMappers { mintBurnAddressMappers(orderBy: "id", limit: 1000) { items { id diff --git a/frontendcode/frontendcode.service.ts b/frontendcode/frontendcode.service.ts index ec13f95..2ab9fcf 100644 --- a/frontendcode/frontendcode.service.ts +++ b/frontendcode/frontendcode.service.ts @@ -1,6 +1,6 @@ import { gql } from '@apollo/client/core'; import { Injectable } from '@nestjs/common'; -import { PONDER_CLIENT } from 'api.config'; +import { PONDER_CLIENT } from 'api.apollo.config'; import { FrontendCodeRegisteredQuery, FrontendCodeSavingsQuery } from './frontendcode.types'; @Injectable() @@ -11,7 +11,7 @@ export class FrontendCodeService { const frontendCodeFetched = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetFrontendCodeRegistered { frontendCodeRegistereds( orderBy: "created", orderDirection: "desc" where: { created_gt: "${checkTimestamp}" } @@ -35,7 +35,7 @@ export class FrontendCodeService { const savedFetched = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetFrontendCodeSavingsSaved { savingsSaveds( orderBy: "created", orderDirection: "desc" where: { created_gt: "${checkTimestamp}" } diff --git a/package.json b/package.json index be996cc..0314312 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@types/node-telegram-bot-api": "^0.64.7", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "cross-fetch": "^4.1.0", "dotenv": "^16.3.1", "graphql": "^16.8.2", "node-telegram-bot-api": "^0.66.0", diff --git a/positions/positions.service.ts b/positions/positions.service.ts index e71fd2e..d3e7c76 100644 --- a/positions/positions.service.ts +++ b/positions/positions.service.ts @@ -3,7 +3,8 @@ import { ADDRESS, PositionV2ABI, SavingsABI } from '@deuro/eurocoin'; import { Injectable, Logger } from '@nestjs/common'; import { FIVEDAYS_MS } from 'utils/const-helper'; import { Address, erc20Abi, getAddress } from 'viem'; -import { CONFIG, PONDER_CLIENT, VIEM_CONFIG } from '../api.config'; +import { CONFIG, VIEM_CONFIG } from '../api.config'; +import { PONDER_CLIENT } from '../api.apollo.config'; import { ApiMintingUpdateListing, ApiMintingUpdateMapping, @@ -79,7 +80,7 @@ export class PositionsService { const { data } = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetPositionsV2 { positionV2s(orderBy: "availableForClones", orderDirection: "desc", limit: 1000) { items { position @@ -270,7 +271,7 @@ export class PositionsService { const { data } = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetMintingUpdatesV2 { mintingUpdateV2s(orderBy: "created", orderDirection: "desc", limit: 1000) { items { id diff --git a/savings/savings.core.service.ts b/savings/savings.core.service.ts index 895aeb6..05e9b8c 100644 --- a/savings/savings.core.service.ts +++ b/savings/savings.core.service.ts @@ -1,7 +1,8 @@ import { gql } from '@apollo/client/core'; import { ADDRESS, SavingsGatewayABI } from '@deuro/eurocoin'; import { Injectable, Logger } from '@nestjs/common'; -import { PONDER_CLIENT, VIEM_CONFIG } from 'api.config'; +import { PONDER_CLIENT } from 'api.apollo.config'; +import { VIEM_CONFIG } from 'api.config'; import { EcosystemStablecoinService } from 'ecosystem/ecosystem.stablecoin.service'; import { Address, formatUnits, zeroAddress } from 'viem'; import { ApiSavingsInfo, ApiSavingsUserLeaderboard, ApiSavingsUserTable } from './savings.core.types'; @@ -41,6 +42,8 @@ export class SavingsCoreService { } async updateSavingsUserLeaderboard(): Promise { + this.logger.debug('Updating SavingsUserLeaderboard'); + const data = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` @@ -88,7 +91,7 @@ export class SavingsCoreService { const savedFetched = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetSavingsSaved { savingsSaveds( orderBy: "blockheight" orderDirection: "desc" @@ -114,7 +117,7 @@ export class SavingsCoreService { const withdrawnFetched = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetSavingsWithdrawn { savingsWithdrawns( orderBy: "blockheight" orderDirection: "desc" @@ -140,7 +143,7 @@ export class SavingsCoreService { const interestFetched = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetSavingsInterest { savingsInterests( orderBy: "blockheight" orderDirection: "desc" diff --git a/savings/savings.leadrate.service.ts b/savings/savings.leadrate.service.ts index 250ecec..949060c 100644 --- a/savings/savings.leadrate.service.ts +++ b/savings/savings.leadrate.service.ts @@ -1,6 +1,6 @@ import { gql } from '@apollo/client/core'; import { Injectable, Logger } from '@nestjs/common'; -import { PONDER_CLIENT } from 'api.config'; +import { PONDER_CLIENT } from 'api.apollo.config'; import { ApiLeadrateInfo, ApiLeadrateProposed, @@ -64,7 +64,7 @@ export class SavingsLeadrateService { const { data } = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetSavingsRateChanged { savingsRateChangeds(orderBy: "blockheight", orderDirection: "desc") { items { id @@ -107,7 +107,7 @@ export class SavingsLeadrateService { const { data } = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetSavingsRateProposed { savingsRateProposeds(orderBy: "blockheight", orderDirection: "desc") { items { id diff --git a/socialmedia/socialmedia.service.ts b/socialmedia/socialmedia.service.ts index 660b26a..34a0fb6 100644 --- a/socialmedia/socialmedia.service.ts +++ b/socialmedia/socialmedia.service.ts @@ -134,7 +134,7 @@ export class SocialMediaService { try { const checkDate = new Date(this.socialMediaState.bridgeUpdates); - for (const stablecoin in StablecoinEnum) { + for (const stablecoin of Object.values(StablecoinEnum)) { const requestedBridged = await this.bridges.getBridgedStables(stablecoin, checkDate); if (requestedBridged.length > 0) { diff --git a/trades/trade.service.ts b/trades/trade.service.ts index 08e445f..d1cb11b 100644 --- a/trades/trade.service.ts +++ b/trades/trade.service.ts @@ -1,6 +1,6 @@ import { gql } from '@apollo/client/core'; import { Injectable } from '@nestjs/common'; -import { PONDER_CLIENT } from 'api.config'; +import { PONDER_CLIENT } from 'api.apollo.config'; import { TradeQuery } from './trade.types'; @Injectable() @@ -11,7 +11,7 @@ export class TradesService { const tradeFetched = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetTrades { trades( orderBy: "time", orderDirection: "desc" where: { @@ -38,7 +38,7 @@ export class TradesService { const tradeFetched = await PONDER_CLIENT.query({ fetchPolicy: 'no-cache', query: gql` - query { + query GetTraderShares { trades( where: { trader: "${trader}" diff --git a/yarn.lock b/yarn.lock index 8a26855..818a5ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2613,6 +2613,13 @@ cron@3.1.7: "@types/luxon" "~3.4.0" luxon "~3.4.0" +cross-fetch@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.1.0.tgz#8f69355007ee182e47fa692ecbaa37a52e43c3d2" + integrity sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw== + dependencies: + node-fetch "^2.7.0" + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -5058,7 +5065,7 @@ node-emoji@1.11.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.1: +node-fetch@^2.6.1, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==