From 4b492abdabfeb72c7f7a71e4c7062faee7dfd541 Mon Sep 17 00:00:00 2001 From: Ncookiez Date: Fri, 21 Jun 2024 17:29:10 -0400 Subject: [PATCH 1/6] WIP vault data api endpoint --- .../vault/[chainId]/[vaultAddress]/route.ts | 41 +++ .../vault/[chainId]/[vaultAddress]/utils.ts | 337 ++++++++++++++++++ apps/app/src/app/api/vault/[chainId]/route.ts | 12 + apps/app/src/app/api/vault/route.ts | 8 + .../src/vaults/useVaultPromotionsApr.ts | 15 +- 5 files changed, 405 insertions(+), 8 deletions(-) create mode 100644 apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts create mode 100644 apps/app/src/app/api/vault/[chainId]/[vaultAddress]/utils.ts create mode 100644 apps/app/src/app/api/vault/[chainId]/route.ts create mode 100644 apps/app/src/app/api/vault/route.ts diff --git a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts new file mode 100644 index 00000000..7ad61cd3 --- /dev/null +++ b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from 'next/server' +import { + getChainIdFromParams, + getPrizePool, + getVault, + getVaultAddressFromParams, + getVaultData +} from './utils' + +export interface VaultApiParams { + chainId: string + vaultAddress: string +} + +// TODO: setup basic caching +export async function GET( + _req: NextRequest, + ctx: { params: VaultApiParams } +): Promise { + const chainId = getChainIdFromParams(ctx.params) + const vaultAddress = getVaultAddressFromParams(ctx.params) + + if (!chainId) { + return NextResponse.json({ message: 'Invalid network' }, { status: 400 }) + } + + if (!vaultAddress) { + return NextResponse.json({ message: 'Invalid vault address' }, { status: 400 }) + } + + try { + const vault = getVault(chainId, vaultAddress) + const prizePool = getPrizePool(vault) + const vaultData = await getVaultData(vault, prizePool) + + return NextResponse.json(vaultData, { status: 200 }) + } catch (err) { + console.error(err) + return NextResponse.json({ message: 'Could not fetch vault data' }, { status: 500 }) + } +} diff --git a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/utils.ts b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/utils.ts new file mode 100644 index 00000000..960dd5f5 --- /dev/null +++ b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/utils.ts @@ -0,0 +1,337 @@ +import { PrizePool, Vault } from '@generationsoftware/hyperstructure-client-js' +import { PartialPromotionInfo, Token, TokenWithLogo, TokenWithPrice } from '@shared/types' +import { + getAssetsFromShares, + getPromotionCreatedEvents, + getPromotions, + getSecondsSinceEpoch, + getTokenInfo, + getTokenPrices, + getVaultId, + lower, + PRIZE_POOLS, + SECONDS_PER_YEAR +} from '@shared/utilities' +import defaultVaultList from '@vaultLists/default' +import { + Address, + createPublicClient, + formatEther, + formatUnits, + http, + isAddress, + parseEther, + PublicClient +} from 'viem' +import { RPC_URLS, TWAB_REWARDS_SETTINGS, WAGMI_CHAINS } from '@constants/config' +import { VaultApiParams } from './route' + +export const getChainIdFromParams = (params: VaultApiParams) => { + const rawChainId = + !!params.chainId && typeof params.chainId === 'string' ? parseInt(params.chainId) : undefined + return !!rawChainId && + PRIZE_POOLS.map((pool) => pool.chainId).includes(rawChainId) && + Object.keys(RPC_URLS) + .map((k) => parseInt(k)) + .includes(rawChainId) + ? (rawChainId as keyof typeof RPC_URLS) + : undefined +} + +export const getVaultAddressFromParams = (params: VaultApiParams) => { + const rawAddress = + !!params.vaultAddress && typeof params.vaultAddress === 'string' + ? params.vaultAddress + : undefined + return !!rawAddress && isAddress(params.vaultAddress) ? params.vaultAddress : undefined +} + +export const getVault = ( + chainId: NonNullable>, + address: Address +) => { + const publicClient = createPublicClient({ + chain: WAGMI_CHAINS[chainId], + transport: http(RPC_URLS[chainId]) + }) as PublicClient + + const existingVaultInfo = defaultVaultList.tokens.find( + (t) => getVaultId(t) === getVaultId({ chainId, address }) + ) + + const vault = new Vault(chainId, address, publicClient, { + decimals: existingVaultInfo?.decimals, + logoURI: existingVaultInfo?.logoURI, + name: existingVaultInfo?.name, + tags: existingVaultInfo?.tags, + tokenAddress: existingVaultInfo?.extensions?.underlyingAsset?.address, + tokenLogoURI: existingVaultInfo?.extensions?.underlyingAsset?.logoURI, + yieldSourceName: existingVaultInfo?.extensions?.yieldSource?.name, + yieldSourceURI: existingVaultInfo?.extensions?.yieldSource?.appURI + }) + + return vault +} + +export const getPrizePool = (vault: Vault) => { + const { chainId, address, options } = PRIZE_POOLS.find((pool) => pool.chainId === vault.chainId)! + + const prizePool = new PrizePool(chainId, address, vault.publicClient, options) + + return prizePool +} + +// TODO: use multicall batches for efficiency +export const getVaultData = async (vault: Vault, prizePool: PrizePool) => { + const shareData = await vault.getShareData() + const assetData = await vault.getTotalTokenBalance() + const exchangeRate = await vault.getExchangeRate() + const yieldSourceAddress = await vault.getYieldSource() + const owner = await vault.getOwner() + const liquidationPair = await vault.getLiquidationPair() + const claimer = await vault.getClaimer() + const yieldFees = await vault.getFeeInfo() + + const lastDrawId = (await prizePool.getLastAwardedDrawId()) || 1 + const drawPeriod = await prizePool.getDrawPeriodInSeconds() + const prizeAssetData = await prizePool.getPrizeTokenData() + + const getContributions = async (startDrawId: number) => + (await prizePool.getVaultContributedAmounts([vault.address], startDrawId, lastDrawId))[vault.id] + const dailyContributions = await getContributions(lastDrawId) + const weeklyContributions = await getContributions(Math.max(0, lastDrawId - 6)) + const monthlyContributions = await getContributions(Math.max(0, lastDrawId - 29)) + const allTimeContributions = await getContributions(0) + + const getSupplyTwab = async (numDraws: number) => + (await prizePool.getVaultTotalSupplyTwabs([vault.address], numDraws))[vault.id] + const dailySupplyTwab = await getSupplyTwab(1) + const weeklySupplyTwab = await getSupplyTwab(7) + const monthlySupplyTwab = await getSupplyTwab(30) + const allTimeSupplyTwab = await getSupplyTwab(lastDrawId) + + const prices = await getTokenPrices(vault.chainId, [ + assetData.address, + prizeAssetData.address, + ...TWAB_REWARDS_SETTINGS[vault.chainId]?.tokenAddresses + ]) + + const assetPrice = prices[lower(assetData.address)] + const sharePrice = + !!assetPrice && !!exchangeRate + ? parseFloat( + formatEther( + getAssetsFromShares(parseEther(`${assetPrice}`), exchangeRate, shareData.decimals) + ) + ) + : undefined + const prizeAssetPrice = prices[lower(prizeAssetData.address)] + + const tvl = parseFloat(formatUnits(assetData.amount, assetData.decimals)) * (assetPrice ?? 0) + + const bonusRewards = await getBonusRewardsInfo(vault, drawPeriod, prices, tvl) + + const share: Token & TokenWithPrice & Partial & { amount: string } = { + chainId: shareData.chainId, + address: shareData.address, + symbol: shareData.symbol, + name: vault.name ?? shareData.name, + decimals: shareData.decimals, + amount: shareData.totalSupply.toString(), + logoURI: vault.logoURI, + price: sharePrice + } + + const asset: Token & TokenWithPrice & Partial & { amount: string } = { + chainId: assetData.chainId, + address: assetData.address, + symbol: assetData.symbol, + name: assetData.name, + decimals: assetData.decimals, + amount: assetData.amount.toString(), + logoURI: vault.tokenLogoURI, + price: assetPrice + } + + const prizeAsset: Token & TokenWithPrice = { + chainId: prizeAssetData.chainId, + address: prizeAssetData.address, + symbol: prizeAssetData.symbol, + name: prizeAssetData.name, + decimals: prizeAssetData.decimals, + price: prizeAssetPrice + } + + const yieldSource: { address: Address; name?: string; appURI?: string } = { + address: yieldSourceAddress, + name: vault.yieldSourceName, + appURI: vault.yieldSourceURI + } + + const contributions: { day: number; week: number; month: number; all: number } = { + day: parseFloat(formatUnits(dailyContributions, prizeAsset.decimals)), + week: parseFloat(formatUnits(weeklyContributions, prizeAsset.decimals)), + month: parseFloat(formatUnits(monthlyContributions, prizeAsset.decimals)), + all: parseFloat(formatUnits(allTimeContributions, prizeAsset.decimals)) + } + + const getPrizeYield = (numDraws: number, contributions: bigint, twab: bigint) => { + if (!!prizeAsset.price && !!share.price) { + const yearlyNumDraws = SECONDS_PER_YEAR / drawPeriod + const extrapolatedYearlyContributions = + parseFloat(formatUnits(contributions, prizeAsset.decimals)) * (yearlyNumDraws / numDraws) + const extrapolatedYearlyContributionsValue = + extrapolatedYearlyContributions * prizeAsset.price + const twabTvl = parseFloat(formatUnits(twab, share.decimals)) * share.price + return (extrapolatedYearlyContributionsValue / twabTvl) * 100 + } + } + const dailyPrizeYield = getPrizeYield(1, dailyContributions, dailySupplyTwab) + const weeklyPrizeYield = getPrizeYield( + Math.min(7, lastDrawId), + weeklyContributions, + weeklySupplyTwab + ) + const monthlyPrizeYield = getPrizeYield( + Math.min(30, lastDrawId), + monthlyContributions, + monthlySupplyTwab + ) + const allTimePrizeYield = getPrizeYield(lastDrawId, allTimeContributions, allTimeSupplyTwab) + + const prizeYield: { day?: number; week?: number; month?: number; all?: number } = { + day: dailyPrizeYield, + week: weeklyPrizeYield, + month: monthlyPrizeYield, + all: allTimePrizeYield + } + + return { + share, + asset, + prizeAsset, + tvl, + yieldSource, + owner, + liquidationPair, + claimer, + yieldFees, + contributions, + prizeYield, + bonusRewards + } +} + +const getBonusRewardsInfo = async ( + vault: Vault, + drawPeriod: number, + prices: Awaited>, + tvl: number +): Promise<{ apr: number; tokens: TokenWithPrice[] }> => { + const tokenAddresses = TWAB_REWARDS_SETTINGS[vault.chainId]?.tokenAddresses ?? [] + + if (!tvl || !tokenAddresses.length) return { apr: 0, tokens: [] } + + const promotions = await getVaultPromotions(vault) + + const rewardTokens = await getTokenInfo(vault.publicClient, tokenAddresses) + + const yearlyDraws = SECONDS_PER_YEAR / drawPeriod + const numDraws = 7 + const currentTimestamp = getSecondsSinceEpoch() + const maxTimestamp = currentTimestamp + numDraws * drawPeriod + + const allTokenRewardsValue: { [tokenAddress: Address]: number } = {} + let futureRewards = 0 + + const getToken = (address: Address): TokenWithPrice => ({ + chainId: rewardTokens[address].chainId, + address: rewardTokens[address].address, + symbol: rewardTokens[address].symbol, + name: rewardTokens[address].name, + decimals: rewardTokens[address].decimals, + price: prices[lower(address)] + }) + + tokenAddresses.forEach((tokenAddress) => { + const rewardToken = getToken(tokenAddress) + + if (!!rewardToken.price && rewardToken.decimals !== undefined) { + const matchingPromotions = Object.values(promotions).filter( + (promotion) => lower(promotion.token) === lower(rewardToken.address) + ) + + matchingPromotions.forEach((promotion) => { + const startsAt = Number(promotion.startTimestamp) + const numberOfEpochs = promotion.numberOfEpochs ?? 0 + const epochDuration = promotion.epochDuration + const endsAt = startsAt + numberOfEpochs * epochDuration + const tokensPerEpoch = promotion.tokensPerEpoch + + if ( + !!startsAt && + startsAt < maxTimestamp && + endsAt > currentTimestamp && + startsAt !== endsAt + ) { + let numValidEpochs = 0 + + for (let i = 0; i < numberOfEpochs; i++) { + const epochEndsAt = startsAt + epochDuration * (i + 1) + if (epochEndsAt > currentTimestamp && epochEndsAt < maxTimestamp) { + numValidEpochs++ + } + } + + const tokenRewards = + parseFloat(formatUnits(tokensPerEpoch, rewardToken.decimals)) * numValidEpochs + const tokenRewardsValue = tokenRewards * (rewardToken.price ?? 0) + + allTokenRewardsValue[tokenAddress] = tokenRewardsValue + futureRewards += tokenRewardsValue + } + }) + } + }) + + const yearlyRewards = futureRewards * (yearlyDraws / numDraws) + const apr = (yearlyRewards / tvl) * 100 + + const promotionTokenAddresses = Object.entries(allTokenRewardsValue) + .filter((entry) => !!entry[1]) + .sort((a, b) => b[1] - a[1]) + .map((entry) => entry[0] as Address) + const promotionTokens = promotionTokenAddresses.map((tokenAddress) => getToken(tokenAddress)) + + return { apr, tokens: promotionTokens } +} + +const getVaultPromotions = async (vault: Vault) => { + const promotions: { [id: string]: PartialPromotionInfo } = {} + + const promotionCreatedEvents = await getPromotionCreatedEvents(vault.publicClient, { + vaultAddresses: [vault.address], + tokenAddresses: TWAB_REWARDS_SETTINGS[vault.chainId]?.tokenAddresses, + fromBlock: TWAB_REWARDS_SETTINGS[vault.chainId]?.fromBlock + }) + + const allPromotionInfo = await getPromotions( + vault.publicClient, + promotionCreatedEvents.map((e) => e.args.promotionId) + ) + + promotionCreatedEvents?.forEach((promotionCreatedEvent) => { + const id = promotionCreatedEvent.args.promotionId.toString() + promotions[id] = { + startTimestamp: promotionCreatedEvent.args.startTimestamp, + vault: promotionCreatedEvent.args.vault, + epochDuration: promotionCreatedEvent.args.epochDuration, + createdAtBlockNumber: promotionCreatedEvent.blockNumber, + token: promotionCreatedEvent.args.token, + tokensPerEpoch: promotionCreatedEvent.args.tokensPerEpoch, + ...allPromotionInfo[id] + } + }) + + return promotions +} diff --git a/apps/app/src/app/api/vault/[chainId]/route.ts b/apps/app/src/app/api/vault/[chainId]/route.ts new file mode 100644 index 00000000..d0ed407a --- /dev/null +++ b/apps/app/src/app/api/vault/[chainId]/route.ts @@ -0,0 +1,12 @@ +import { NextRequest, NextResponse } from 'next/server' +import { VaultApiParams } from './[vaultAddress]/route' + +export async function GET( + _req: NextRequest, + ctx: { params: VaultApiParams } +): Promise { + return NextResponse.json( + { message: `Missing in /api/${ctx.params.chainId}/` }, + { status: 400 } + ) +} diff --git a/apps/app/src/app/api/vault/route.ts b/apps/app/src/app/api/vault/route.ts new file mode 100644 index 00000000..f91b4118 --- /dev/null +++ b/apps/app/src/app/api/vault/route.ts @@ -0,0 +1,8 @@ +import { NextResponse } from 'next/server' + +export async function GET(): Promise { + return NextResponse.json( + { message: 'Missing and in /api//' }, + { status: 400 } + ) +} diff --git a/packages/hyperstructure-react-hooks/src/vaults/useVaultPromotionsApr.ts b/packages/hyperstructure-react-hooks/src/vaults/useVaultPromotionsApr.ts index 515352c1..120dba3b 100644 --- a/packages/hyperstructure-react-hooks/src/vaults/useVaultPromotionsApr.ts +++ b/packages/hyperstructure-react-hooks/src/vaults/useVaultPromotionsApr.ts @@ -79,11 +79,13 @@ export const useVaultPromotionsApr = ( const allTokenRewardsValue: { [tokenAddress: Address]: number } = {} let futureRewards = 0 + const getToken = (address: Address): TokenWithPrice => ({ + ...rewardTokenData[address], + price: rewardTokenPrices[lower(address)] + }) + tokenAddresses.forEach((tokenAddress) => { - const rewardToken: TokenWithPrice = { - ...rewardTokenData[tokenAddress], - price: rewardTokenPrices[lower(tokenAddress)] - } + const rewardToken = getToken(tokenAddress) if (!!rewardToken.price && rewardToken.decimals !== undefined) { const promotions = Object.values(vaultPromotions).filter( @@ -132,10 +134,7 @@ export const useVaultPromotionsApr = ( .filter((entry) => !!entry[1]) .sort((a, b) => b[1] - a[1]) .map((entry) => entry[0] as Address) - const promotionTokens = promotionTokenAddresses.map((tokenAddress) => ({ - ...rewardTokenData[tokenAddress], - price: rewardTokenPrices[lower(tokenAddress)] - })) + const promotionTokens = promotionTokenAddresses.map((tokenAddress) => getToken(tokenAddress)) return { apr, tokens: promotionTokens } } From 8edd93ec6f23a6441ad730657e814e2a9394ea52 Mon Sep 17 00:00:00 2001 From: Ncookiez Date: Fri, 21 Jun 2024 18:13:37 -0400 Subject: [PATCH 2/6] multicall efficiency --- .../vault/[chainId]/[vaultAddress]/route.ts | 1 - .../vault/[chainId]/[vaultAddress]/utils.ts | 222 +++++++++++++----- 2 files changed, 161 insertions(+), 62 deletions(-) diff --git a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts index 7ad61cd3..794a46bc 100644 --- a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts +++ b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts @@ -12,7 +12,6 @@ export interface VaultApiParams { vaultAddress: string } -// TODO: setup basic caching export async function GET( _req: NextRequest, ctx: { params: VaultApiParams } diff --git a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/utils.ts b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/utils.ts index 960dd5f5..24a88c28 100644 --- a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/utils.ts +++ b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/utils.ts @@ -1,6 +1,7 @@ import { PrizePool, Vault } from '@generationsoftware/hyperstructure-client-js' import { PartialPromotionInfo, Token, TokenWithLogo, TokenWithPrice } from '@shared/types' import { + erc20ABI, getAssetsFromShares, getPromotionCreatedEvents, getPromotions, @@ -10,7 +11,9 @@ import { getVaultId, lower, PRIZE_POOLS, - SECONDS_PER_YEAR + prizePoolABI, + SECONDS_PER_YEAR, + vaultABI } from '@shared/utilities' import defaultVaultList from '@vaultLists/default' import { @@ -21,7 +24,9 @@ import { http, isAddress, parseEther, - PublicClient + parseUnits, + PublicClient, + zeroAddress } from 'viem' import { RPC_URLS, TWAB_REWARDS_SETTINGS, WAGMI_CHAINS } from '@constants/config' import { VaultApiParams } from './route' @@ -81,93 +86,188 @@ export const getPrizePool = (vault: Vault) => { return prizePool } -// TODO: use multicall batches for efficiency export const getVaultData = async (vault: Vault, prizePool: PrizePool) => { - const shareData = await vault.getShareData() - const assetData = await vault.getTotalTokenBalance() - const exchangeRate = await vault.getExchangeRate() - const yieldSourceAddress = await vault.getYieldSource() - const owner = await vault.getOwner() - const liquidationPair = await vault.getLiquidationPair() - const claimer = await vault.getClaimer() - const yieldFees = await vault.getFeeInfo() - - const lastDrawId = (await prizePool.getLastAwardedDrawId()) || 1 - const drawPeriod = await prizePool.getDrawPeriodInSeconds() - const prizeAssetData = await prizePool.getPrizeTokenData() - - const getContributions = async (startDrawId: number) => - (await prizePool.getVaultContributedAmounts([vault.address], startDrawId, lastDrawId))[vault.id] - const dailyContributions = await getContributions(lastDrawId) - const weeklyContributions = await getContributions(Math.max(0, lastDrawId - 6)) - const monthlyContributions = await getContributions(Math.max(0, lastDrawId - 29)) - const allTimeContributions = await getContributions(0) - - const getSupplyTwab = async (numDraws: number) => - (await prizePool.getVaultTotalSupplyTwabs([vault.address], numDraws))[vault.id] - const dailySupplyTwab = await getSupplyTwab(1) - const weeklySupplyTwab = await getSupplyTwab(7) - const monthlySupplyTwab = await getSupplyTwab(30) - const allTimeSupplyTwab = await getSupplyTwab(lastDrawId) + const firstMulticallResults = await vault.publicClient.multicall({ + contracts: [ + { address: vault.address, abi: vaultABI, functionName: 'asset' }, + { address: vault.address, abi: erc20ABI, functionName: 'symbol' }, + { address: vault.address, abi: erc20ABI, functionName: 'name' }, + { address: vault.address, abi: erc20ABI, functionName: 'decimals' }, + { address: vault.address, abi: erc20ABI, functionName: 'totalSupply' }, + { address: vault.address, abi: vaultABI, functionName: 'totalPreciseAssets' }, + { address: vault.address, abi: vaultABI, functionName: 'totalAssets' }, + { address: vault.address, abi: vaultABI, functionName: 'yieldVault' }, + { address: vault.address, abi: vaultABI, functionName: 'owner' }, + { address: vault.address, abi: vaultABI, functionName: 'liquidationPair' }, + { address: vault.address, abi: vaultABI, functionName: 'claimer' }, + { address: vault.address, abi: vaultABI, functionName: 'yieldFeePercentage' }, + { address: vault.address, abi: vaultABI, functionName: 'yieldFeeRecipient' }, + { address: prizePool.address, abi: prizePoolABI, functionName: 'prizeToken' }, + { address: prizePool.address, abi: prizePoolABI, functionName: 'getLastAwardedDrawId' }, + { address: prizePool.address, abi: prizePoolABI, functionName: 'drawPeriodSeconds' } + ], + batchSize: 1_024 * 1_024 + }) + + const assetAddress = firstMulticallResults[0].result! + const shareSymbol = firstMulticallResults[1].result! + const shareName = firstMulticallResults[2].result! + const shareDecimals = firstMulticallResults[3].result! + const shareTotalSupply = firstMulticallResults[4].result! + const totalAssets = (firstMulticallResults[5].result ?? firstMulticallResults[6].result)! + const yieldSourceAddress = firstMulticallResults[7].result! + const owner = firstMulticallResults[8].result! + const liquidationPair = firstMulticallResults[9].result! + const claimer = firstMulticallResults[10].result! + const yieldFeePercentage = firstMulticallResults[11].result ?? 0 + const yieldFeeRecipient = firstMulticallResults[12].result ?? zeroAddress + const prizeAssetAddress = firstMulticallResults[13].result! + const lastDrawId = firstMulticallResults[14].result || 1 + const drawPeriod = firstMulticallResults[15].result! + + const secondMulticallResults = await vault.publicClient.multicall({ + contracts: [ + { address: assetAddress, abi: erc20ABI, functionName: 'symbol' }, + { address: assetAddress, abi: erc20ABI, functionName: 'name' }, + { address: assetAddress, abi: erc20ABI, functionName: 'decimals' }, + { address: prizeAssetAddress, abi: erc20ABI, functionName: 'symbol' }, + { address: prizeAssetAddress, abi: erc20ABI, functionName: 'name' }, + { address: prizeAssetAddress, abi: erc20ABI, functionName: 'decimals' }, + { + address: vault.address, + abi: vaultABI, + functionName: 'convertToAssets', + args: [parseUnits('1', shareDecimals)] + }, + { + address: prizePool.address, + abi: prizePoolABI, + functionName: 'getContributedBetween', + args: [vault.address, lastDrawId, lastDrawId] + }, + { + address: prizePool.address, + abi: prizePoolABI, + functionName: 'getContributedBetween', + args: [vault.address, Math.max(1, lastDrawId - 6), lastDrawId] + }, + { + address: prizePool.address, + abi: prizePoolABI, + functionName: 'getContributedBetween', + args: [vault.address, Math.max(1, lastDrawId - 29), lastDrawId] + }, + { + address: prizePool.address, + abi: prizePoolABI, + functionName: 'getContributedBetween', + args: [vault.address, 1, lastDrawId] + }, + { + address: prizePool.address, + abi: prizePoolABI, + functionName: 'getVaultUserBalanceAndTotalSupplyTwab', + args: [vault.address, zeroAddress, lastDrawId, lastDrawId] + }, + { + address: prizePool.address, + abi: prizePoolABI, + functionName: 'getVaultUserBalanceAndTotalSupplyTwab', + args: [vault.address, zeroAddress, Math.max(1, lastDrawId - 6), lastDrawId] + }, + { + address: prizePool.address, + abi: prizePoolABI, + functionName: 'getVaultUserBalanceAndTotalSupplyTwab', + args: [vault.address, zeroAddress, Math.max(1, lastDrawId - 29), lastDrawId] + }, + { + address: prizePool.address, + abi: prizePoolABI, + functionName: 'getVaultUserBalanceAndTotalSupplyTwab', + args: [vault.address, zeroAddress, 1, lastDrawId] + } + ], + batchSize: 1_024 * 1_024 + }) + + const assetSymbol = secondMulticallResults[0].result! + const assetName = secondMulticallResults[1].result! + const assetDecimals = secondMulticallResults[2].result! + const prizeAssetSymbol = secondMulticallResults[3].result! + const prizeAssetName = secondMulticallResults[4].result! + const prizeAssetDecimals = secondMulticallResults[5].result! + const exchangeRate = secondMulticallResults[6].result! + const dailyContributions = secondMulticallResults[7].result ?? 0n + const weeklyContributions = secondMulticallResults[8].result ?? 0n + const monthlyContributions = secondMulticallResults[9].result ?? 0n + const allTimeContributions = secondMulticallResults[10].result ?? 0n + const dailySupplyTwab = secondMulticallResults[11].result?.[1] ?? 0n + const weeklySupplyTwab = secondMulticallResults[12].result?.[1] ?? 0n + const monthlySupplyTwab = secondMulticallResults[13].result?.[1] ?? 0n + const allTimeSupplyTwab = secondMulticallResults[14].result?.[1] ?? 0n const prices = await getTokenPrices(vault.chainId, [ - assetData.address, - prizeAssetData.address, + assetAddress, + prizeAssetAddress, ...TWAB_REWARDS_SETTINGS[vault.chainId]?.tokenAddresses ]) - const assetPrice = prices[lower(assetData.address)] + const assetPrice = prices[lower(assetAddress)] const sharePrice = !!assetPrice && !!exchangeRate ? parseFloat( - formatEther( - getAssetsFromShares(parseEther(`${assetPrice}`), exchangeRate, shareData.decimals) - ) + formatEther(getAssetsFromShares(parseEther(`${assetPrice}`), exchangeRate, shareDecimals)) ) : undefined - const prizeAssetPrice = prices[lower(prizeAssetData.address)] - - const tvl = parseFloat(formatUnits(assetData.amount, assetData.decimals)) * (assetPrice ?? 0) - - const bonusRewards = await getBonusRewardsInfo(vault, drawPeriod, prices, tvl) - - const share: Token & TokenWithPrice & Partial & { amount: string } = { - chainId: shareData.chainId, - address: shareData.address, - symbol: shareData.symbol, - name: vault.name ?? shareData.name, - decimals: shareData.decimals, - amount: shareData.totalSupply.toString(), + const prizeAssetPrice = prices[lower(prizeAssetAddress)] + + const share: Token & TokenWithPrice & Partial & { amount: number } = { + chainId: vault.chainId, + address: vault.address, + symbol: shareSymbol, + name: vault.name ?? shareName, + decimals: shareDecimals, + amount: parseFloat(formatUnits(shareTotalSupply, shareDecimals)), logoURI: vault.logoURI, price: sharePrice } - const asset: Token & TokenWithPrice & Partial & { amount: string } = { - chainId: assetData.chainId, - address: assetData.address, - symbol: assetData.symbol, - name: assetData.name, - decimals: assetData.decimals, - amount: assetData.amount.toString(), + const asset: Token & TokenWithPrice & Partial & { amount: number } = { + chainId: vault.chainId, + address: assetAddress, + symbol: assetSymbol, + name: assetName, + decimals: assetDecimals, + amount: parseFloat(formatUnits(totalAssets, assetDecimals)), logoURI: vault.tokenLogoURI, price: assetPrice } const prizeAsset: Token & TokenWithPrice = { - chainId: prizeAssetData.chainId, - address: prizeAssetData.address, - symbol: prizeAssetData.symbol, - name: prizeAssetData.name, - decimals: prizeAssetData.decimals, + chainId: vault.chainId, + address: prizeAssetAddress, + symbol: prizeAssetSymbol, + name: prizeAssetName, + decimals: prizeAssetDecimals, price: prizeAssetPrice } + const tvl = asset.amount * (assetPrice ?? 0) + + const bonusRewards = await getBonusRewardsInfo(vault, drawPeriod, prices, tvl) + const yieldSource: { address: Address; name?: string; appURI?: string } = { address: yieldSourceAddress, name: vault.yieldSourceName, appURI: vault.yieldSourceURI } + const yieldFees: { percent: number; recipient: Address } = { + percent: yieldFeePercentage, + recipient: yieldFeeRecipient + } + const contributions: { day: number; week: number; month: number; all: number } = { day: parseFloat(formatUnits(dailyContributions, prizeAsset.decimals)), week: parseFloat(formatUnits(weeklyContributions, prizeAsset.decimals)), @@ -176,7 +276,7 @@ export const getVaultData = async (vault: Vault, prizePool: PrizePool) => { } const getPrizeYield = (numDraws: number, contributions: bigint, twab: bigint) => { - if (!!prizeAsset.price && !!share.price) { + if (!!prizeAsset.price && !!share.price && !!twab) { const yearlyNumDraws = SECONDS_PER_YEAR / drawPeriod const extrapolatedYearlyContributions = parseFloat(formatUnits(contributions, prizeAsset.decimals)) * (yearlyNumDraws / numDraws) From b9758a4645da89c7223b24e359605731a3aafda7 Mon Sep 17 00:00:00 2001 From: Ncookiez Date: Fri, 21 Jun 2024 18:24:36 -0400 Subject: [PATCH 3/6] added error to response --- apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts index 794a46bc..da2b7332 100644 --- a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts +++ b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts @@ -33,8 +33,7 @@ export async function GET( const vaultData = await getVaultData(vault, prizePool) return NextResponse.json(vaultData, { status: 200 }) - } catch (err) { - console.error(err) - return NextResponse.json({ message: 'Could not fetch vault data' }, { status: 500 }) + } catch (error) { + return NextResponse.json({ message: 'Could not fetch vault data', error }, { status: 500 }) } } From f07612e162451a41df99c4df44ed11c8fafcabd6 Mon Sep 17 00:00:00 2001 From: Ncookiez Date: Fri, 21 Jun 2024 18:38:12 -0400 Subject: [PATCH 4/6] attempt 2 at debugging preview deployment error --- .../app/api/vault/[chainId]/[vaultAddress]/route.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts index da2b7332..f9c5b75b 100644 --- a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts +++ b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts @@ -33,7 +33,14 @@ export async function GET( const vaultData = await getVaultData(vault, prizePool) return NextResponse.json(vaultData, { status: 200 }) - } catch (error) { - return NextResponse.json({ message: 'Could not fetch vault data', error }, { status: 500 }) + } catch (e: any) { + if (!(e instanceof Error)) { + e = new Error(e as string) + } + + return NextResponse.json( + { message: 'Could not fetch vault data', error: e.message }, + { status: 500 } + ) } } From 4e32574a8cb7ced031b9a347b665ec60bd085baa Mon Sep 17 00:00:00 2001 From: Ncookiez Date: Fri, 21 Jun 2024 18:56:02 -0400 Subject: [PATCH 5/6] attempt 3 at debugging preview deployment error --- .../api/vault/[chainId]/[vaultAddress]/route.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts index f9c5b75b..312986a0 100644 --- a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts +++ b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts @@ -38,8 +38,21 @@ export async function GET( e = new Error(e as string) } + const vault = getVault(chainId, vaultAddress) + return NextResponse.json( - { message: 'Could not fetch vault data', error: e.message }, + { + message: 'Could not fetch vault data', + error: e.message, + clientDetails: { + chainId: vault.publicClient.chain?.id, + transport: { + key: vault.publicClient.transport.key, + name: vault.publicClient.transport.name, + type: vault.publicClient.transport.type + } + } + }, { status: 500 } ) } From 0b1c94565861d1d188a05a376659acfef024a700 Mon Sep 17 00:00:00 2001 From: Ncookiez Date: Fri, 21 Jun 2024 19:01:49 -0400 Subject: [PATCH 6/6] cleaned up error messages on api route --- .../vault/[chainId]/[vaultAddress]/route.ts | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts index 312986a0..f2901a24 100644 --- a/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts +++ b/apps/app/src/app/api/vault/[chainId]/[vaultAddress]/route.ts @@ -33,27 +33,7 @@ export async function GET( const vaultData = await getVaultData(vault, prizePool) return NextResponse.json(vaultData, { status: 200 }) - } catch (e: any) { - if (!(e instanceof Error)) { - e = new Error(e as string) - } - - const vault = getVault(chainId, vaultAddress) - - return NextResponse.json( - { - message: 'Could not fetch vault data', - error: e.message, - clientDetails: { - chainId: vault.publicClient.chain?.id, - transport: { - key: vault.publicClient.transport.key, - name: vault.publicClient.transport.name, - type: vault.publicClient.transport.type - } - } - }, - { status: 500 } - ) + } catch { + return NextResponse.json({ message: 'Could not fetch vault data' }, { status: 500 }) } }