From d87d79fbe29350c57c625e61e7f9805c5dc0b6ea Mon Sep 17 00:00:00 2001 From: Iaroslav Gryshaiev Date: Mon, 30 Dec 2024 09:53:02 +0100 Subject: [PATCH] fix(billing): use specific endpoint to check managed wallet grants --- apps/api/mvm.lock | 2 +- apps/deploy-web/mvm.lock | 2 +- ...lowanceService.tsx => useAuthZService.tsx} | 2 +- apps/deploy-web/src/hooks/useWalletBalance.ts | 57 +++++++++---------- .../src/queries/useBalancesQuery.ts | 39 +++++-------- .../queries/useExactDeploymentGrantsQuery.ts | 4 +- .../src/queries/useExactFeeAllowanceQuery.ts | 4 +- apps/deploy-web/src/queries/useGrantsQuery.ts | 42 +++++--------- .../src/queries/useTemplateQuery.tsx | 3 - apps/deploy-web/src/types/address.ts | 5 +- apps/deploy-web/src/utils/apiUtils.ts | 5 +- package-lock.json | 6 +- packages/http-sdk/package.json | 2 +- .../http-sdk/src/authz/authz-http.service.ts | 18 ++++-- 14 files changed, 87 insertions(+), 104 deletions(-) rename apps/deploy-web/src/hooks/{useAllowanceService.tsx => useAuthZService.tsx} (87%) diff --git a/apps/api/mvm.lock b/apps/api/mvm.lock index 731146644..e00d83eae 100644 --- a/apps/api/mvm.lock +++ b/apps/api/mvm.lock @@ -2,7 +2,7 @@ "dependencies": { "@akashnetwork/database": "1.0.0", "@akashnetwork/env-loader": "1.0.1", - "@akashnetwork/http-sdk": "1.1.2", + "@akashnetwork/http-sdk": "1.1.3", "@akashnetwork/logging": "2.0.2" } } diff --git a/apps/deploy-web/mvm.lock b/apps/deploy-web/mvm.lock index a85153249..9d17bd7ce 100644 --- a/apps/deploy-web/mvm.lock +++ b/apps/deploy-web/mvm.lock @@ -1,7 +1,7 @@ { "dependencies": { "@akashnetwork/env-loader": "1.0.1", - "@akashnetwork/http-sdk": "1.1.1", + "@akashnetwork/http-sdk": "1.1.3", "@akashnetwork/logging": "2.0.2", "@akashnetwork/network-store": "1.0.1", "@akashnetwork/ui": "1.0.0" diff --git a/apps/deploy-web/src/hooks/useAllowanceService.tsx b/apps/deploy-web/src/hooks/useAuthZService.tsx similarity index 87% rename from apps/deploy-web/src/hooks/useAllowanceService.tsx rename to apps/deploy-web/src/hooks/useAuthZService.tsx index 2486df947..0e576bd96 100644 --- a/apps/deploy-web/src/hooks/useAllowanceService.tsx +++ b/apps/deploy-web/src/hooks/useAuthZService.tsx @@ -3,7 +3,7 @@ import { AuthzHttpService } from "@akashnetwork/http-sdk"; import { useSettings } from "@src/context/SettingsProvider"; -export const useAllowanceService = () => { +export const useAuthZService = () => { const { settings } = useSettings(); return useMemo(() => new AuthzHttpService({ baseURL: settings.apiEndpoint }), [settings.apiEndpoint]); }; diff --git a/apps/deploy-web/src/hooks/useWalletBalance.ts b/apps/deploy-web/src/hooks/useWalletBalance.ts index 814dac700..68689e3cb 100644 --- a/apps/deploy-web/src/hooks/useWalletBalance.ts +++ b/apps/deploy-web/src/hooks/useWalletBalance.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { UAKT_DENOM } from "@src/config/denom.config"; import { useChainParam } from "@src/context/ChainParamProvider"; @@ -38,6 +38,21 @@ export const useWalletBalance = (): WalletBalanceReturnType => { const { data: balances, isFetching: isLoadingBalances, refetch } = useBalances(address); const [walletBalance, setWalletBalance] = useState(null); + const udenomToUsd = useCallback( + (amount: string, denom: string) => { + let value = 0; + + if (denom === UAKT_DENOM) { + value = uaktToAKT(parseFloat(amount), 6) * (price || 0); + } else if (denom === usdcIbcDenom) { + value = udenomToDenom(parseFloat(amount), 6); + } + + return value; + }, + [price, usdcIbcDenom] + ); + useEffect(() => { if (isLoaded && balances && price) { const aktUsdValue = uaktToAKT(balances.balanceUAKT, 6) * price; @@ -49,44 +64,26 @@ export const useWalletBalance = (): WalletBalanceReturnType => { udenomToUsd(d.escrowAccount.balance.amount, d.escrowAccount.balance.denom), 0 ); - const totalDeploymentGrantsUSD = balances.deploymentGrants.reduce( - (acc, d) => acc + udenomToUsd(d.authorization.spend_limit.amount, d.authorization.spend_limit.denom), - 0 - ); - const totalGrantsUAKT = balances.deploymentGrants - .filter(d => d.authorization.spend_limit.denom === UAKT_DENOM) - .reduce((acc, d) => acc + parseFloat(d.authorization.spend_limit.amount), 0); - const totalGrantsUUSDC = balances.deploymentGrants - .filter(d => d.authorization.spend_limit.denom === usdcIbcDenom) - .reduce((acc, d) => acc + parseFloat(d.authorization.spend_limit.amount), 0); + const { deploymentGrant, deploymentGrantsUAKT, deploymentEscrowUAKT } = balances; + const totalDeploymentGrantsUSD = deploymentGrant + ? udenomToUsd(deploymentGrant.authorization.spend_limit.amount, deploymentGrant.authorization.spend_limit.denom) + : 0; setWalletBalance({ totalUsd: aktUsdValue + totalUsdcValue + totalDeploymentEscrowUSD + totalDeploymentGrantsUSD, - balanceUAKT: balances.balanceUAKT + totalGrantsUAKT, - balanceUUSDC: balances.balanceUUSDC + totalGrantsUUSDC, - totalUAKT: balances.balanceUAKT + balances.deploymentEscrowUAKT + totalGrantsUAKT, - totalUUSDC: balances.balanceUUSDC + balances.deploymentEscrowUUSDC + totalGrantsUUSDC, + balanceUAKT: balances.balanceUAKT + deploymentGrantsUAKT, + balanceUUSDC: balances.balanceUUSDC + deploymentEscrowUAKT, + totalUAKT: balances.balanceUAKT + balances.deploymentEscrowUAKT + deploymentGrantsUAKT, + totalUUSDC: balances.balanceUUSDC + balances.deploymentEscrowUUSDC + deploymentEscrowUAKT, totalDeploymentEscrowUAKT: balances.deploymentEscrowUAKT, totalDeploymentEscrowUUSDC: balances.deploymentEscrowUUSDC, totalDeploymentEscrowUSD: totalDeploymentEscrowUSD, - totalDeploymentGrantsUAKT: totalGrantsUAKT, - totalDeploymentGrantsUUSDC: totalGrantsUUSDC, + totalDeploymentGrantsUAKT: deploymentGrantsUAKT, + totalDeploymentGrantsUUSDC: deploymentEscrowUAKT, totalDeploymentGrantsUSD: totalDeploymentGrantsUSD }); } - }, [isLoaded, price, balances, isManaged]); - - const udenomToUsd = (amount: string, denom: string) => { - let value = 0; - - if (denom === UAKT_DENOM) { - value = uaktToAKT(parseFloat(amount), 6) * (price || 0); - } else if (denom === usdcIbcDenom) { - value = udenomToDenom(parseFloat(amount), 6); - } - - return value; - }; + }, [isLoaded, price, balances, isManaged, udenomToUsd]); return { balance: walletBalance, diff --git a/apps/deploy-web/src/queries/useBalancesQuery.ts b/apps/deploy-web/src/queries/useBalancesQuery.ts index 7be11a868..6816c9767 100644 --- a/apps/deploy-web/src/queries/useBalancesQuery.ts +++ b/apps/deploy-web/src/queries/useBalancesQuery.ts @@ -1,6 +1,8 @@ import { QueryKey, useQuery, UseQueryOptions } from "react-query"; +import { AuthzHttpService } from "@akashnetwork/http-sdk"; import axios from "axios"; +import { browserEnvConfig } from "@src/config/browser-env.config"; import { UAKT_DENOM } from "@src/config/denom.config"; import { getUsdcDenom } from "@src/hooks/useDenom"; import { Balances } from "@src/types"; @@ -11,35 +13,25 @@ import { deploymentToDto } from "@src/utils/deploymentDetailUtils"; import { useSettings } from "../context/SettingsProvider"; import { QueryKeys } from "./queryKeys"; -// Account balances async function getBalances(apiEndpoint: string, address?: string): Promise { if (!address || !apiEndpoint) return undefined; const usdcIbcDenom = getUsdcDenom(); + const authzHttpService = new AuthzHttpService({ baseURL: apiEndpoint }); - const balancePromise = axios.get(ApiUrlService.balance(apiEndpoint, address)); - // const authzBalancePromise = axios.get(ApiUrlService.granteeGrants(apiEndpoint, address)); - const activeDeploymentsPromise = loadWithPagination(ApiUrlService.deploymentList(apiEndpoint, address, true), "deployments", 1000); + const [balanceResponse, deploymentGrant, activeDeploymentsResponse] = await Promise.all([ + axios.get(ApiUrlService.balance(apiEndpoint, address)), + authzHttpService.getDepositDeploymentGrantsForGranterAndGrantee(browserEnvConfig.NEXT_PUBLIC_MASTER_WALLET_ADDRESS, address), + loadWithPagination(ApiUrlService.deploymentList(apiEndpoint, address, true), "deployments", 1000) + ]); - const [balanceResponse, activeDeploymentsResponse] = await Promise.all([balancePromise, activeDeploymentsPromise]); + const deploymentGrantsUAKT = parseFloat( + deploymentGrant?.authorization.spend_limit.denom === UAKT_DENOM ? deploymentGrant.authorization.spend_limit.amount : "0" + ); - // Authz Grants - // const deploymentGrants = authzBalanceResponse.data.grants.filter( - // b => b.authorization["@type"] === "/akash.deployment.v1beta3.DepositDeploymentAuthorization" - // ); - // const deploymentGrants = authzBalanceResponse.data.grants.filter( - // b => b.authorization["@type"] === "/akash.deployment.v1beta3.DepositDeploymentAuthorization" - // ); - // const deploymentGrantsUAKT = parseFloat( - // deploymentGrants.find(b => b.authorization.spend_limit.denom === UAKT_DENOM)?.authorization.spend_limit.amount || "0" - // ); - // - // const deploymentGrantsUUSDC = parseFloat( - // deploymentGrants.find(b => b.authorization.spend_limit.denom === usdcIbcDenom)?.authorization.spend_limit.amount || "0" - // ); - const deploymentGrantsUAKT = 0; + const deploymentGrantsUUSDC = parseFloat( + deploymentGrant && deploymentGrant.authorization.spend_limit.denom === usdcIbcDenom ? deploymentGrant.authorization.spend_limit.amount : "0" + ); - const deploymentGrantsUUSDC = 0; - // Balance const balanceData = balanceResponse.data; const balanceUAKT = balanceData.balances.some(b => b.denom === UAKT_DENOM) || deploymentGrantsUAKT > 0 @@ -50,7 +42,6 @@ async function getBalances(apiEndpoint: string, address?: string): Promise b.denom === usdcIbcDenom)?.amount || "0") : 0; - // Deployment balances const activeDeployments = activeDeploymentsResponse.map(d => deploymentToDto(d)); const aktActiveDeployments = activeDeployments.filter(d => d.denom === UAKT_DENOM); const usdcActiveDeployments = activeDeployments.filter(d => d.denom === usdcIbcDenom); @@ -65,7 +56,7 @@ async function getBalances(apiEndpoint: string, address?: string): Promise allowanceHttpService.getDepositDeploymentGrantsForGranterAndGrantee(granter, grantee), diff --git a/apps/deploy-web/src/queries/useExactFeeAllowanceQuery.ts b/apps/deploy-web/src/queries/useExactFeeAllowanceQuery.ts index 7d6bacb3b..e456b2424 100644 --- a/apps/deploy-web/src/queries/useExactFeeAllowanceQuery.ts +++ b/apps/deploy-web/src/queries/useExactFeeAllowanceQuery.ts @@ -1,10 +1,10 @@ import { useQuery } from "react-query"; -import { useAllowanceService } from "@src/hooks/useAllowanceService"; +import { useAuthZService } from "@src/hooks/useAuthZService"; import { QueryKeys } from "@src/queries/queryKeys"; export function useExactFeeAllowanceQuery(granter: string, grantee: string, { enabled = true } = {}) { - const allowanceHttpService = useAllowanceService(); + const allowanceHttpService = useAuthZService(); return useQuery(QueryKeys.getFeeAllowancesKey(granter, grantee), () => allowanceHttpService.getFeeAllowanceForGranterAndGrantee(granter, grantee), { enabled }); diff --git a/apps/deploy-web/src/queries/useGrantsQuery.ts b/apps/deploy-web/src/queries/useGrantsQuery.ts index d22a69282..f6818e8a0 100644 --- a/apps/deploy-web/src/queries/useGrantsQuery.ts +++ b/apps/deploy-web/src/queries/useGrantsQuery.ts @@ -1,6 +1,7 @@ import { QueryObserverResult, useQuery } from "react-query"; import { useSettings } from "@src/context/SettingsProvider"; +import { useAuthZService } from "@src/hooks/useAuthZService"; import { AllowanceType, GrantType } from "@src/types/grant"; import { ApiUrlService, loadWithPagination } from "@src/utils/apiUtils"; import { QueryKeys } from "./queryKeys"; @@ -9,13 +10,11 @@ async function getGranterGrants(apiEndpoint: string, address: string) { if (!address || !apiEndpoint) return undefined; const grants = await loadWithPagination(ApiUrlService.granterGrants(apiEndpoint, address), "grants", 1000); - const filteredGrants = grants.filter( + return grants.filter( x => x.authorization["@type"] === "/akash.deployment.v1beta2.DepositDeploymentAuthorization" || x.authorization["@type"] === "/akash.deployment.v1beta3.DepositDeploymentAuthorization" ); - - return filteredGrants; } export function useGranterGrants(address: string, options = {}) { @@ -24,34 +23,25 @@ export function useGranterGrants(address: string, options = {}) { return useQuery(QueryKeys.getGranterGrants(address), () => getGranterGrants(settings.apiEndpoint, address), options); } -async function getGranteeGrants(apiEndpoint: string, address: string) { - if (!address || !apiEndpoint) return undefined; - - // const grants = await loadWithPagination(ApiUrlService.granteeGrants(apiEndpoint, address), "grants", 1000); - const grants: GrantType[] = []; - const filteredGrants = grants.filter( - x => - // TODO: this is not working - // Only the v1beta3 authorization are working - // x.authorization["@type"] === "/akash.deployment.v1beta2.DepositDeploymentAuthorization" || - x.authorization["@type"] === "/akash.deployment.v1beta3.DepositDeploymentAuthorization" - ); - - return filteredGrants; -} - -export function useGranteeGrants(address: string, options = {}) { +export function useGranteeGrants(address?: string, options: { enabled?: boolean; refetchInterval?: number } = { enabled: true }) { + const allowanceHttpService = useAuthZService(); const { settings } = useSettings(); - return useQuery(QueryKeys.getGranteeGrants(address), () => getGranteeGrants(settings.apiEndpoint, address), options); + // TODO: ensure app is not loaded till settings are fetched + // Issue: https://github.com/akash-network/console/issues/600 + options.enabled = !!address && !!settings.apiEndpoint; + + return useQuery( + QueryKeys.getGranteeGrants(address || "UNDEFINED"), + () => (address ? allowanceHttpService.getAllDepositDeploymentGrants({ grantee: address, limit: 1000 }) : []), + options + ); } async function getAllowancesIssued(apiEndpoint: string, address: string) { if (!address || !apiEndpoint) return undefined; - const allowances = await loadWithPagination(ApiUrlService.allowancesIssued(apiEndpoint, address), "allowances", 1000); - - return allowances; + return await loadWithPagination(ApiUrlService.allowancesIssued(apiEndpoint, address), "allowances", 1000); } export function useAllowancesIssued(address: string, options = {}) { @@ -63,9 +53,7 @@ export function useAllowancesIssued(address: string, options = {}) { async function getAllowancesGranted(apiEndpoint: string, address: string) { if (!address || !apiEndpoint) return undefined; - const allowances = await loadWithPagination(ApiUrlService.allowancesGranted(apiEndpoint, address), "allowances", 1000); - - return allowances; + return await loadWithPagination(ApiUrlService.allowancesGranted(apiEndpoint, address), "allowances", 1000); } export function useAllowancesGranted(address?: string, options = {}): QueryObserverResult { diff --git a/apps/deploy-web/src/queries/useTemplateQuery.tsx b/apps/deploy-web/src/queries/useTemplateQuery.tsx index 4214ccaaa..e8a24cd68 100644 --- a/apps/deploy-web/src/queries/useTemplateQuery.tsx +++ b/apps/deploy-web/src/queries/useTemplateQuery.tsx @@ -1,13 +1,10 @@ import { QueryKey, useMutation, useQuery, useQueryClient, UseQueryOptions } from "react-query"; -import { UseQueryResult } from "react-query/types/react/types"; -import { TemplateOutput } from "@akashnetwork/http-sdk/src/template/template-http.service"; import { Snackbar } from "@akashnetwork/ui/components"; import axios from "axios"; import { useRouter } from "next/navigation"; import { useSnackbar } from "notistack"; import { useCustomUser } from "@src/hooks/useCustomUser"; -import { services } from "@src/services/http/http-browser.service"; import { ITemplate } from "@src/types"; import { ApiUrlService } from "@src/utils/apiUtils"; import { UrlService } from "@src/utils/urlUtils"; diff --git a/apps/deploy-web/src/types/address.ts b/apps/deploy-web/src/types/address.ts index 0b58ba575..1a8596de8 100644 --- a/apps/deploy-web/src/types/address.ts +++ b/apps/deploy-web/src/types/address.ts @@ -1,4 +1,5 @@ -import { Grant } from "./balances"; +import { ExactDepositDeploymentGrant } from "@akashnetwork/http-sdk"; + import { DeploymentDto } from "./deployment"; import { TransactionDetail } from "./transaction"; import { IValidatorAddess } from "./validator"; @@ -45,5 +46,5 @@ export interface Balances { deploymentGrantsUAKT: number; deploymentGrantsUUSDC: number; activeDeployments: DeploymentDto[]; - deploymentGrants: Grant[]; + deploymentGrant?: ExactDepositDeploymentGrant; } diff --git a/apps/deploy-web/src/utils/apiUtils.ts b/apps/deploy-web/src/utils/apiUtils.ts index 57497e13d..c155dfcfa 100644 --- a/apps/deploy-web/src/utils/apiUtils.ts +++ b/apps/deploy-web/src/utils/apiUtils.ts @@ -56,9 +56,6 @@ export class ApiUrlService { static unbonding(apiEndpoint: string, address: string) { return `${apiEndpoint}/cosmos/staking/v1beta1/delegators/${address}/unbonding_delegations`; } - static granteeGrants(apiEndpoint: string, address: string) { - return `${apiEndpoint}/cosmos/authz/v1beta1/grants/grantee/${address}`; - } static granterGrants(apiEndpoint: string, address: string) { return `${apiEndpoint}/cosmos/authz/v1beta1/grants/granter/${address}`; } @@ -128,6 +125,8 @@ export class ApiUrlService { } } +// TODO: implement proper pagination on clients +// Issue: https://github.com/akash-network/console/milestone/7 export async function loadWithPagination(baseUrl: string, dataKey: string, limit: number) { let items = []; let nextKey = null; diff --git a/package-lock.json b/package-lock.json index 52cf74850..a990a32cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ }, "apps/api": { "name": "@akashnetwork/console-api", - "version": "2.41.1", + "version": "2.43.1", "license": "Apache-2.0", "dependencies": { "@akashnetwork/akash-api": "^1.3.0", @@ -227,7 +227,7 @@ }, "apps/deploy-web": { "name": "@akashnetwork/console-web", - "version": "2.25.4", + "version": "2.27.4", "license": "Apache-2.0", "dependencies": { "@akashnetwork/akash-api": "^1.3.0", @@ -39304,7 +39304,7 @@ }, "packages/http-sdk": { "name": "@akashnetwork/http-sdk", - "version": "1.0.8", + "version": "1.1.2", "license": "Apache-2.0", "dependencies": { "axios": "^1.7.2", diff --git a/packages/http-sdk/package.json b/packages/http-sdk/package.json index 67c161512..405ebee67 100644 --- a/packages/http-sdk/package.json +++ b/packages/http-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@akashnetwork/http-sdk", - "version": "1.1.2", + "version": "1.1.3", "description": "Package containing http layer for Akash Network", "keywords": [], "license": "Apache-2.0", diff --git a/packages/http-sdk/src/authz/authz-http.service.ts b/packages/http-sdk/src/authz/authz-http.service.ts index 444f5a8e9..8a5c664cc 100644 --- a/packages/http-sdk/src/authz/authz-http.service.ts +++ b/packages/http-sdk/src/authz/authz-http.service.ts @@ -98,7 +98,7 @@ export class AuthzHttpService extends HttpService { async paginateDepositDeploymentGrants( options: ({ granter: string } | { grantee: string }) & { limit: number }, cb: (page: DepositDeploymentGrantResponse["grants"]) => Promise - ) { + ): Promise { let nextPageKey: string | null = null; const side = "granter" in options ? "granter" : "grantee"; const address = "granter" in options ? options.granter : options.grantee; @@ -107,11 +107,9 @@ export class AuthzHttpService extends HttpService { const response = this.extractData( await this.get( `cosmos/authz/v1beta1/grants/${side}/${address}`, - nextPageKey - ? { + { params: { "pagination.key": nextPageKey, "pagination.limit": options.limit } } - : undefined ) ); nextPageKey = response.pagination.next_key; @@ -120,6 +118,18 @@ export class AuthzHttpService extends HttpService { } while (nextPageKey); } + async getAllDepositDeploymentGrants( + options: ({ granter: string } | { grantee: string }) & { limit: number }, + ): Promise { + const result: DepositDeploymentGrant[] = []; + + await this.paginateDepositDeploymentGrants(options, async (page) => { + result.push(...page); + }) + + return result; + } + private isValidFeeAllowance({ allowance }: FeeAllowance) { return allowance["@type"] === this.FEE_ALLOWANCE_TYPE && (!allowance.expiration || isFuture(new Date(allowance.expiration))); }