From 9b16bd9478efe7bcabcd6c046e537fab276bf0a2 Mon Sep 17 00:00:00 2001 From: Redm4x <2829180+Redm4x@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:48:47 -0500 Subject: [PATCH] feat(analytics): add financial endpoint --- .../user-wallet/user-wallet.repository.ts | 7 +- .../financial-stats.service.ts | 105 ++++++++++++++++++ apps/api/src/routers/internalRouter.ts | 3 + apps/api/src/routes/internal/financial.ts | 44 ++++++++ apps/api/src/routes/internal/index.ts | 3 +- apps/api/src/types/rest/index.ts | 1 + 6 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/billing/services/financial-stats/financial-stats.service.ts create mode 100644 apps/api/src/routes/internal/financial.ts diff --git a/apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts b/apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts index b3363d480..6cdf08714 100644 --- a/apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts +++ b/apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts @@ -1,4 +1,4 @@ -import { eq, inArray, lte } from "drizzle-orm"; +import { count, eq, inArray, lte } from "drizzle-orm"; import first from "lodash/first"; import omit from "lodash/omit"; import pick from "lodash/pick"; @@ -73,6 +73,11 @@ export class UserWalletRepository extends BaseRepository(`${apiNodeUrl}/cosmos/bank/v1beta1/balances/${address}?pagination.limit=1000`); + return parseFloat(response.data.balances.find(b => b.denom === denom)?.amount || "0"); + } + + async getAkashPubProviderBalances() { + const ovrclkProviders = await Provider.findAll({ + where: { + hostUri: { [Op.like]: "%akash.pub:8443" } + } + }); + + const balances = await Promise.all( + ovrclkProviders.map(async p => { + const balance = await this.getWalletBalances(p.owner, USDC_IBC_DENOMS.mainnetId); + return { provider: p.hostUri, balanceUsdc: balance / 1_000_000 }; + }) + ); + + return balances; + } + + async getCommunityPoolUsdc() { + const communityPoolData = await axios.get(`${apiNodeUrl}/cosmos/distribution/v1beta1/community_pool`); + return parseFloat(communityPoolData.data.pool.find(x => x.denom === USDC_IBC_DENOMS.mainnetId)?.amount || "0"); + } + + async getProviderRevenues() { + const results = await chainDb.query<{ hostUri: string; usdEarned: string }>( + ` + WITH trial_deployments_ids AS ( + SELECT DISTINCT m."relatedDeploymentId" AS "deployment_id" + FROM "transaction" t + INNER JOIN message m ON m."txId"=t.id + WHERE t."memo"='managed wallet tx' AND m.type='/akash.market.v1beta4.MsgCreateLease' AND t.height > 18515430 AND m.height > 18515430 -- 18515430 is height on trial launch (2024-10-17) + ), + trial_leases AS ( + SELECT + l.owner AS "owner", + l."createdHeight", + l."closedHeight", + l."providerAddress", + l.denom AS denom, + l.price AS price, + LEAST((SELECT MAX(height) FROM block), COALESCE(l."closedHeight",l."predictedClosedHeight")) - l."createdHeight" AS duration + FROM trial_deployments_ids + INNER JOIN deployment d ON d.id="deployment_id" + INNER JOIN lease l ON l."deploymentId"=d."id" + WHERE l.denom='uusdc' + ), + billed_leases AS ( + SELECT + l.owner, + p."hostUri", + ROUND(l.duration * l.price::numeric / 1000000, 2) AS "Spent USD" + FROM trial_leases l + INNER JOIN provider p ON p.owner=l."providerAddress" + ) + SELECT + "hostUri", + SUM("Spent USD") AS "usdEarned" + FROM billed_leases + GROUP BY "hostUri" + ORDER BY SUM("Spent USD") DESC + `, + { type: QueryTypes.SELECT } + ); + + return results.map(p => ({ + provider: p.hostUri, + usdEarned: parseFloat(p.usdEarned) + })); + } +} diff --git a/apps/api/src/routers/internalRouter.ts b/apps/api/src/routers/internalRouter.ts index 8f4502932..0f620735c 100644 --- a/apps/api/src/routers/internalRouter.ts +++ b/apps/api/src/routers/internalRouter.ts @@ -1,6 +1,7 @@ import { swaggerUI } from "@hono/swagger-ui"; import { OpenAPIHono } from "@hono/zod-openapi"; +import { privateMiddleware } from "@src/middlewares/privateMiddleware"; import { env } from "@src/utils/env"; import routes from "../routes/internal"; @@ -20,4 +21,6 @@ const swaggerInstance = swaggerUI({ url: `/internal/doc` }); internalRouter.get(`/swagger`, swaggerInstance); +internalRouter.use("/financial", privateMiddleware); + routes.forEach(route => internalRouter.route(`/`, route)); diff --git a/apps/api/src/routes/internal/financial.ts b/apps/api/src/routes/internal/financial.ts new file mode 100644 index 000000000..74abf3aed --- /dev/null +++ b/apps/api/src/routes/internal/financial.ts @@ -0,0 +1,44 @@ +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; +import { container } from "tsyringe"; + +import { FinancialStatsService } from "@src/billing/services/financial-stats/financial-stats.service"; + +const route = createRoute({ + method: "get", + path: "/financial", + summary: "", + responses: { + 200: { + description: "List of gpu models and their availability.", + content: { + "application/json": { + schema: z.object({}) + } + } + } + } +}); + +export default new OpenAPIHono().openapi(route, async c => { + const financialStatsService = container.resolve(FinancialStatsService); + + const [masterBalanceUsdc, providerRevenues, communityPoolUsdc, ovrclkProviderBalances, payingUserCount] = await Promise.all([ + financialStatsService.getMasterWalletBalanceUsdc(), + financialStatsService.getProviderRevenues(), + financialStatsService.getCommunityPoolUsdc(), + financialStatsService.getAkashPubProviderBalances(), + financialStatsService.getPayingUserCount() + ]); + + const readyToRecycle = ovrclkProviderBalances.map(x => x.balanceUsdc).reduce((a, b) => a + b, 0); + + return c.json({ + date: new Date(), + trialBalanceUsdc: masterBalanceUsdc / 1_000_000, + communityPoolUsdc: communityPoolUsdc / 1_000_000, + readyToRecycle, + payingUserCount, + ovrclkProviderBalances, + providerRevenues: providerRevenues + }); +}); diff --git a/apps/api/src/routes/internal/index.ts b/apps/api/src/routes/internal/index.ts index 6faa28871..824ea1519 100644 --- a/apps/api/src/routes/internal/index.ts +++ b/apps/api/src/routes/internal/index.ts @@ -4,5 +4,6 @@ import gpuPrices from "../v1/gpuPrices"; import leasesDuration from "../v1/leasesDuration"; import providerDashboard from "../v1/providerDashboard"; import providerVersions from "../v1/providerVersions"; +import financial from "./financial"; -export default [providerVersions, gpu, leasesDuration, gpuModels, gpuPrices, providerDashboard]; +export default [providerVersions, gpu, leasesDuration, gpuModels, gpuPrices, providerDashboard, financial]; diff --git a/apps/api/src/types/rest/index.ts b/apps/api/src/types/rest/index.ts index 6739f0e15..a0a5b0d9f 100644 --- a/apps/api/src/types/rest/index.ts +++ b/apps/api/src/types/rest/index.ts @@ -7,5 +7,6 @@ export * from "./cosmosGovProposalResponse"; export * from "./cosmosGovProposalsTallyResponse"; export * from "./cosmosBankBalancesResponse"; export * from "./cosmosStakingDelegationsResponse"; +export * from "./cosmosDistributionCommunityPoolResponse"; export * from "./cosmosDistributionDelegatorsRewardsResponse"; export * from "./cosmosStakingDelegatorsRedelegationsResponse";