Skip to content

Commit

Permalink
Add chains array param to user balance query (#482)
Browse files Browse the repository at this point in the history
* Add support for fetching user balances for multiple chains

* Add chain filter to the prisma query

* key token prices on chain instead of chain ID, add function to fetch prices for multiple chains at once

* Properly fetch chain scoped prices

* default to all chains and address filter

* default chain as passed in the header

---------

Co-authored-by: gmbronco <[email protected]>
  • Loading branch information
danielmkm and gmbronco authored Oct 18, 2023
1 parent 25f070e commit 0b0b7ff
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 33 deletions.
14 changes: 14 additions & 0 deletions modules/network/network-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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'];
9 changes: 5 additions & 4 deletions modules/token/lib/token-price.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Expand Down Expand Up @@ -48,21 +49,21 @@ export class TokenPriceService {
return tokenPrices;
}

public async getCurrentTokenPrices(): Promise<PrismaTokenCurrentPrice[]> {
public async getCurrentTokenPrices(chain = networkContext.chain): Promise<PrismaTokenCurrentPrice[]> {
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,
});
}

Expand Down
34 changes: 20 additions & 14 deletions modules/token/token.service.ts
Original file line number Diff line number Diff line change
@@ -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`;
Expand Down Expand Up @@ -42,10 +38,10 @@ export class TokenService {
}

public async getTokens(addresses?: string[]): Promise<PrismaToken[]> {
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));
Expand Down Expand Up @@ -85,15 +81,25 @@ export class TokenService {
return this.tokenPriceService.updateTokenPrices();
}

public async getTokenPrices(): Promise<PrismaTokenCurrentPrice[]> {
let tokenPrices = this.cache.get(`${TOKEN_PRICES_CACHE_KEY}:${networkContext.chainId}`);
public async getTokenPrices(chain = networkContext.chain): Promise<PrismaTokenCurrentPrice[]> {
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<Dictionary<PrismaTokenCurrentPrice[]>> {
const response: Dictionary<PrismaTokenCurrentPrice[]> = {};

for (const chain of chains) {
response[chain] = await this.getTokenPrices(chain);
}

return response;
}

public async getWhiteListedTokenPrices(): Promise<PrismaTokenCurrentPrice[]> {
/*const cached = this.cache.get(WHITE_LISTED_TOKEN_PRICES_CACHE_KEY) as PrismaTokenCurrentPrice[] | null;
Expand Down Expand Up @@ -190,11 +196,11 @@ export class TokenService {
}

public async getTokenPriceFrom24hAgo(): Promise<PrismaTokenCurrentPrice[]> {
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,
);
Expand Down
11 changes: 7 additions & 4 deletions modules/user/lib/user-balance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserPoolBalance[]> {
public async getUserPoolBalances(address: string, chains: Chain[]): Promise<UserPoolBalance[]> {
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 } },
},
},
});
Expand All @@ -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)!,
};
});
}
Expand All @@ -68,6 +70,7 @@ export class UserBalanceService {
totalBalance: formatFixed(stakedNum.add(walletNum), 18),
stakedBalance: stakedBalance?.balance || '0',
walletBalance: walletBalance?.balance || '0',
chain: networkContext.chain,
};
}

Expand Down
3 changes: 2 additions & 1 deletion modules/user/user-types.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -14,6 +14,7 @@ export interface UserPoolBalance {
totalBalance: AmountHumanReadable;
walletBalance: AmountHumanReadable;
stakedBalance: AmountHumanReadable;
chain: Chain;
}

export interface UserSyncUserBalanceInput {
Expand Down
3 changes: 2 additions & 1 deletion modules/user/user.gql
Original file line number Diff line number Diff line change
@@ -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!]!
Expand All @@ -24,4 +24,5 @@ type GqlUserPoolBalance {
totalBalance: AmountHumanReadable!
walletBalance: AmountHumanReadable!
stakedBalance: AmountHumanReadable!
chain: GqlChain!
}
12 changes: 7 additions & 5 deletions modules/user/user.resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
8 changes: 4 additions & 4 deletions modules/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -27,8 +27,8 @@ export class UserService {
return networkContext.config.userStakedBalanceServices;
}

public async getUserPoolBalances(address: string): Promise<UserPoolBalance[]> {
return this.userBalanceService.getUserPoolBalances(address);
public async getUserPoolBalances(address: string, chains: Chain[]): Promise<UserPoolBalance[]> {
return this.userBalanceService.getUserPoolBalances(address, chains);
}

public async getUserPoolInvestments(
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 0b0b7ff

Please sign in to comment.