diff --git a/modules/network/network-config.ts b/modules/network/network-config.ts index 5bd7df31a..7cc0786da 100644 --- a/modules/network/network-config.ts +++ b/modules/network/network-config.ts @@ -8,6 +8,8 @@ import { gnosisNetworkConfig } from './gnosis'; import { zkevmNetworkConfig } from './zkevm'; import { avalancheNetworkConfig } from './avalanche'; import { baseNetworkConfig } from './base'; +import { Chain } from '@prisma/client'; +import { keyBy, pickBy } from 'lodash'; export const AllNetworkConfigs: { [chainId: string]: NetworkConfig } = { '250': fantomNetworkConfig, @@ -21,5 +23,17 @@ export const AllNetworkConfigs: { [chainId: string]: NetworkConfig } = { '8453': baseNetworkConfig, }; +export const AllNetworkConfigsKeyedOnChain: { [chain in Chain]: NetworkConfig } = { + FANTOM: fantomNetworkConfig, + OPTIMISM: optimismNetworkConfig, + MAINNET: mainnetNetworkConfig, + ARBITRUM: arbitrumNetworkConfig, + POLYGON: polygonNetworkConfig, + GNOSIS: gnosisNetworkConfig, + ZKEVM: zkevmNetworkConfig, + AVALANCHE: avalancheNetworkConfig, + BASE: baseNetworkConfig, +}; + export const BalancerChainIds = ['1', '137', '42161', '100', '1101', '43114', '8453']; export const BeethovenChainIds = ['250', '10']; diff --git a/modules/token/lib/token-price.service.ts b/modules/token/lib/token-price.service.ts index 9ea657969..6a704f541 100644 --- a/modules/token/lib/token-price.service.ts +++ b/modules/token/lib/token-price.service.ts @@ -9,6 +9,7 @@ import { Cache, CacheClass } from 'memory-cache'; import * as Sentry from '@sentry/node'; import { networkContext } from '../../network/network-context.service'; import { TokenHistoricalPrices } from '../../coingecko/coingecko-types'; +import { AllNetworkConfigsKeyedOnChain } from '../../network/network-config'; const TOKEN_HISTORICAL_PRICES_CACHE_KEY = `token-historical-prices`; const NESTED_BPT_HISTORICAL_PRICES_CACHE_KEY = `nested-bpt-historical-prices`; @@ -48,21 +49,21 @@ export class TokenPriceService { return tokenPrices; } - public async getCurrentTokenPrices(): Promise { + public async getCurrentTokenPrices(chain = networkContext.chain): Promise { const tokenPrices = await prisma.prismaTokenCurrentPrice.findMany({ - where: { chain: networkContext.chain }, + where: { chain: chain }, orderBy: { timestamp: 'desc' }, distinct: ['tokenAddress'], }); const wethPrice = tokenPrices.find( - (tokenPrice) => tokenPrice.tokenAddress === networkContext.data.weth.address, + (tokenPrice) => tokenPrice.tokenAddress === AllNetworkConfigsKeyedOnChain[chain].data.weth.address, ); if (wethPrice) { tokenPrices.push({ ...wethPrice, - tokenAddress: networkContext.data.eth.address, + tokenAddress: AllNetworkConfigsKeyedOnChain[chain].data.eth.address, }); } diff --git a/modules/token/token.service.ts b/modules/token/token.service.ts index 1f1c79599..e9c6b3651 100644 --- a/modules/token/token.service.ts +++ b/modules/token/token.service.ts @@ -1,17 +1,13 @@ import { TokenDefinition, TokenPriceItem } from './token-types'; import { prisma } from '../../prisma/prisma-client'; import { TokenPriceService } from './lib/token-price.service'; -import { PrismaToken, PrismaTokenCurrentPrice, PrismaTokenDynamicData, PrismaTokenPrice } from '@prisma/client'; +import { Chain, PrismaToken, PrismaTokenCurrentPrice, PrismaTokenDynamicData, PrismaTokenPrice } from '@prisma/client'; import { CoingeckoDataService } from './lib/coingecko-data.service'; import { Cache, CacheClass } from 'memory-cache'; import { GqlTokenChartDataRange, MutationTokenDeletePriceArgs, MutationTokenDeleteTokenTypeArgs } from '../../schema'; import { coingeckoService } from '../coingecko/coingecko.service'; import { networkContext } from '../network/network-context.service'; -import { getContractAt } from '../web3/contract'; -import ERC20Abi from '../web3/abi/ERC20.json'; -import { BigNumber } from 'ethers'; -import { formatFixed } from '@ethersproject/bignumber'; -import { add } from 'lodash'; +import { Dictionary } from 'lodash'; const TOKEN_PRICES_CACHE_KEY = `token:prices:current`; const TOKEN_PRICES_24H_AGO_CACHE_KEY = `token:prices:24h-ago`; @@ -42,10 +38,10 @@ export class TokenService { } public async getTokens(addresses?: string[]): Promise { - let tokens: PrismaToken[] | null = this.cache.get(`${ALL_TOKENS_CACHE_KEY}:${networkContext.chainId}`); + let tokens: PrismaToken[] | null = this.cache.get(`${ALL_TOKENS_CACHE_KEY}:${networkContext.chain}`); if (!tokens) { tokens = await prisma.prismaToken.findMany({ where: { chain: networkContext.chain } }); - this.cache.put(`${ALL_TOKENS_CACHE_KEY}:${networkContext.chainId}`, tokens, 5 * 60 * 1000); + this.cache.put(`${ALL_TOKENS_CACHE_KEY}:${networkContext.chain}`, tokens, 5 * 60 * 1000); } if (addresses) { return tokens.filter((token) => addresses.includes(token.address)); @@ -85,15 +81,25 @@ export class TokenService { return this.tokenPriceService.updateTokenPrices(); } - public async getTokenPrices(): Promise { - let tokenPrices = this.cache.get(`${TOKEN_PRICES_CACHE_KEY}:${networkContext.chainId}`); + public async getTokenPrices(chain = networkContext.chain): Promise { + let tokenPrices = this.cache.get(`${TOKEN_PRICES_CACHE_KEY}:${chain}`); if (!tokenPrices) { - tokenPrices = await this.tokenPriceService.getCurrentTokenPrices(); - this.cache.put(`${TOKEN_PRICES_CACHE_KEY}:${networkContext.chainId}`, tokenPrices, 30 * 1000); + tokenPrices = await this.tokenPriceService.getCurrentTokenPrices(chain); + this.cache.put(`${TOKEN_PRICES_CACHE_KEY}:${chain}`, tokenPrices, 30 * 1000); } return tokenPrices; } + public async getTokenPricesForChains(chains: Chain[]): Promise> { + const response: Dictionary = {}; + + for (const chain of chains) { + response[chain] = await this.getTokenPrices(chain); + } + + return response; + } + public async getWhiteListedTokenPrices(): Promise { /*const cached = this.cache.get(WHITE_LISTED_TOKEN_PRICES_CACHE_KEY) as PrismaTokenCurrentPrice[] | null; @@ -190,11 +196,11 @@ export class TokenService { } public async getTokenPriceFrom24hAgo(): Promise { - let tokenPrices24hAgo = this.cache.get(`${TOKEN_PRICES_24H_AGO_CACHE_KEY}:${networkContext.chainId}`); + let tokenPrices24hAgo = this.cache.get(`${TOKEN_PRICES_24H_AGO_CACHE_KEY}:${networkContext.chain}`); if (!tokenPrices24hAgo) { tokenPrices24hAgo = await this.tokenPriceService.getTokenPriceFrom24hAgo(); this.cache.put( - `${TOKEN_PRICES_24H_AGO_CACHE_KEY}:${networkContext.chainId}`, + `${TOKEN_PRICES_24H_AGO_CACHE_KEY}:${networkContext.chain}`, tokenPrices24hAgo, 60 * 15 * 1000, ); diff --git a/modules/user/lib/user-balance.service.ts b/modules/user/lib/user-balance.service.ts index 8df29b340..200f7837b 100644 --- a/modules/user/lib/user-balance.service.ts +++ b/modules/user/lib/user-balance.service.ts @@ -3,21 +3,21 @@ import { prisma } from '../../../prisma/prisma-client'; import _ from 'lodash'; import { parseUnits } from 'ethers/lib/utils'; import { formatFixed } from '@ethersproject/bignumber'; -import { PrismaPoolStaking } from '@prisma/client'; +import { Chain, PrismaPoolStaking } from '@prisma/client'; import { networkContext } from '../../network/network-context.service'; export class UserBalanceService { constructor() {} - public async getUserPoolBalances(address: string): Promise { + public async getUserPoolBalances(address: string, chains: Chain[]): Promise { const user = await prisma.prismaUser.findUnique({ where: { address: address.toLowerCase() }, include: { walletBalances: { - where: { chain: networkContext.chain, poolId: { not: null }, balanceNum: { gt: 0 } }, + where: { chain: { in: chains }, poolId: { not: null }, balanceNum: { gt: 0 } }, }, stakedBalances: { - where: { chain: networkContext.chain, poolId: { not: null }, balanceNum: { gt: 0 } }, + where: { chain: { in: chains }, poolId: { not: null }, balanceNum: { gt: 0 } }, }, }, }); @@ -43,6 +43,8 @@ export class UserBalanceService { totalBalance: formatFixed(stakedNum.add(walletNum), 18), stakedBalance: stakedBalance?.balance || '0', walletBalance: walletBalance?.balance || '0', + // the prisma query above ensures that one of these balances exists + chain: (stakedBalance?.chain || walletBalance?.chain)!, }; }); } @@ -68,6 +70,7 @@ export class UserBalanceService { totalBalance: formatFixed(stakedNum.add(walletNum), 18), stakedBalance: stakedBalance?.balance || '0', walletBalance: walletBalance?.balance || '0', + chain: networkContext.chain, }; } diff --git a/modules/user/user-types.ts b/modules/user/user-types.ts index a5165ea70..d177fc092 100644 --- a/modules/user/user-types.ts +++ b/modules/user/user-types.ts @@ -1,5 +1,5 @@ import { AmountHumanReadable } from '../common/global-types'; -import { PrismaPoolStaking, PrismaPoolStakingType } from '@prisma/client'; +import { Chain, PrismaPoolStaking, PrismaPoolStakingType } from '@prisma/client'; import { Relic } from '../subgraphs/reliquary-subgraph/generated/reliquary-subgraph-types'; export interface UserStakedBalanceService { @@ -14,6 +14,7 @@ export interface UserPoolBalance { totalBalance: AmountHumanReadable; walletBalance: AmountHumanReadable; stakedBalance: AmountHumanReadable; + chain: Chain; } export interface UserSyncUserBalanceInput { diff --git a/modules/user/user.gql b/modules/user/user.gql index f2f7fbe00..62803f158 100644 --- a/modules/user/user.gql +++ b/modules/user/user.gql @@ -1,5 +1,5 @@ extend type Query { - userGetPoolBalances: [GqlUserPoolBalance!]! + userGetPoolBalances(chains: [GqlChain!], address: String): [GqlUserPoolBalance!]! userGetStaking: [GqlPoolStaking!]! userGetPoolJoinExits(first: Int = 10, skip: Int = 0, poolId: String!): [GqlPoolJoinExit!]! userGetSwaps(first: Int = 10, skip: Int = 0, poolId: String!): [GqlPoolSwap!]! @@ -24,4 +24,5 @@ type GqlUserPoolBalance { totalBalance: AmountHumanReadable! walletBalance: AmountHumanReadable! stakedBalance: AmountHumanReadable! + chain: GqlChain! } diff --git a/modules/user/user.resolvers.ts b/modules/user/user.resolvers.ts index 781f722a8..6c75f403a 100644 --- a/modules/user/user.resolvers.ts +++ b/modules/user/user.resolvers.ts @@ -2,17 +2,19 @@ import { Resolvers } from '../../schema'; import { userService } from './user.service'; import { getRequiredAccountAddress, isAdminRoute } from '../auth/auth-context'; import { tokenService } from '../token/token.service'; +import { networkContext } from '../network/network-context.service'; const resolvers: Resolvers = { Query: { - userGetPoolBalances: async (parent, {}, context) => { - const accountAddress = getRequiredAccountAddress(context); - const tokenPrices = await tokenService.getTokenPrices(); - const balances = await userService.getUserPoolBalances(accountAddress); + userGetPoolBalances: async (parent, { chains, address }, context) => { + chains = chains && chains.length > 0 ? chains : [networkContext.chain]; + const accountAddress = address || getRequiredAccountAddress(context); + const tokenPrices = await tokenService.getTokenPricesForChains(chains); + const balances = await userService.getUserPoolBalances(accountAddress, chains); return balances.map((balance) => ({ ...balance, - tokenPrice: tokenService.getPriceForToken(tokenPrices, balance.tokenAddress), + tokenPrice: tokenService.getPriceForToken(tokenPrices[balance.chain] || [], balance.tokenAddress), })); }, userGetPoolJoinExits: async (parent, { first, skip, poolId }, context) => { diff --git a/modules/user/user.service.ts b/modules/user/user.service.ts index ad4ff2ea6..fd603906c 100644 --- a/modules/user/user.service.ts +++ b/modules/user/user.service.ts @@ -1,4 +1,4 @@ -import { PrismaPoolStaking, PrismaPoolStakingType } from '@prisma/client'; +import { Chain, PrismaPoolStaking, PrismaPoolStakingType } from '@prisma/client'; import { prisma } from '../../prisma/prisma-client'; import { GqlPoolJoinExit, GqlPoolSwap, GqlUserSnapshotDataRange } from '../../schema'; import { coingeckoService } from '../coingecko/coingecko.service'; @@ -27,8 +27,8 @@ export class UserService { return networkContext.config.userStakedBalanceServices; } - public async getUserPoolBalances(address: string): Promise { - return this.userBalanceService.getUserPoolBalances(address); + public async getUserPoolBalances(address: string, chains: Chain[]): Promise { + return this.userBalanceService.getUserPoolBalances(address, chains); } public async getUserPoolInvestments( @@ -85,7 +85,7 @@ export class UserService { } public async syncUserBalanceAllPools(userAddress: string) { - const allBalances = await this.userBalanceService.getUserPoolBalances(userAddress); + const allBalances = await this.userBalanceService.getUserPoolBalances(userAddress, [networkContext.chain]); for (const userPoolBalance of allBalances) { await this.syncUserBalance(userAddress, userPoolBalance.poolId); }