Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: api v1 move pool lps #634

Merged
merged 14 commits into from
Nov 30, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Improvements

- [#634](https://github.com/alleslabs/celatone-frontend/pull/634) api v1 - move pool info
- [#640](https://github.com/alleslabs/celatone-frontend/pull/640) api v1 - recent blocks list
- [#632](https://github.com/alleslabs/celatone-frontend/pull/632) api v1 - assets info

Expand Down
3 changes: 2 additions & 1 deletion src/lib/app-provider/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export enum CELATONE_QUERY_KEYS {
POOL_INFO_BY_IDS = "CELATONE_QUERY_POOL_INFO_BY_IDS",
POOL_TRANSACTION_BY_ID = "CELATONE_QUERY_POOL_TRANSACTION_BY_ID",
POOL_TRANSACTION_BY_ID_COUNT = "CELATONE_QUERY_POOL_TRANSACTION_BY_ID_COUNT",
POOL_MOVE_LP_SHARE_INFO = "CELATONE_QUERY_POOL_MOVE_LP_SHARE_INFO",
// MOVE
MOVE_POOL_INFOS = "CELATONE_QUERY_MOVE_POOL_INFOS",
// MODULES
ACCOUNT_MODULES = "CELATONE_QUERY_ACCOUNT_MODULES",
MODULE_VERIFICATION = "CELATONE_QUERY_MODULE_VERIFICATION",
Expand Down
3 changes: 3 additions & 0 deletions src/lib/app-provider/hooks/useBaseApiRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const useBaseApiRoute = (
| "cosmwasm"
| "move_modules"
| "staking"
| "move"
): string => {
const {
chainConfig: { chain },
Expand Down Expand Up @@ -57,6 +58,8 @@ export const useBaseApiRoute = (
return `${api}/${chain}/${currentChainId}/move_modules`;
case "staking":
return `${api}/${chain}/${currentChainId}/staking`;
case "move":
return `${api}/v1/${chain}/${currentChainId}/move`;
default:
throw new Error(
"Error retrieving chain, api, or currentChainId from chain config."
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/token/TokenCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const TokenCard = ({
pb={2}
>
<TokenImageRender
logo={userBalance.lpLogo ?? userBalance.assetInfo?.logo}
logo={userBalance.logo ?? userBalance.assetInfo?.logo}
alt={symbol}
boxSize={6}
/>
Expand Down
12 changes: 6 additions & 6 deletions src/lib/components/token/TokenComposition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ export const TokenComposition = ({
<Flex wrap="wrap">
<Text variant="body2">
<Text as="span" mr={1} fontWeight={700}>
{token.lpDetails.coinA.amount}
{token.poolInfo.coinA.amount}
</Text>
{getTokenLabel(
token.lpDetails.coinA.denom,
token.lpDetails.coinA.symbol
token.poolInfo.coinA.denom,
token.poolInfo.coinA.symbol
)}
</Text>
<Text variant="body2" mx={1}>
+
</Text>
<Text variant="body2">
<Text as="span" mr={1} fontWeight={700}>
{token.lpDetails.coinB.amount}
{token.poolInfo.coinB.amount}
</Text>
{getTokenLabel(
token.lpDetails.coinB.denom,
token.lpDetails.coinB.symbol
token.poolInfo.coinB.denom,
token.poolInfo.coinB.symbol
)}
</Text>
</Flex>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/token/TokenImageRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const TokenImage = (props: ImageProps) => (
);

interface TokenImageRenderProps extends ImageProps {
logo: Option<string | string[]>;
logo: Option<string> | Option<string>[];
}

export const TokenImageRender = ({ logo, ...props }: TokenImageRenderProps) =>
Expand Down
43 changes: 25 additions & 18 deletions src/lib/pages/account-details/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
useStakingParams,
useUnbondings,
} from "lib/services/delegationService";
import { useLPShareInfo } from "lib/services/poolService";
import { useMovePoolInfos } from "lib/services/move";
import { useValidators } from "lib/services/validatorService";
import type {
BalanceWithAssetInfo,
Expand All @@ -40,6 +40,7 @@ import {
coinToTokenWithValue,
totalValueTokenWithValue,
compareTokenWithValues,
getTokenLabel,
} from "lib/utils";

import type { UserDelegationsData } from "./types";
Expand Down Expand Up @@ -172,38 +173,43 @@ export const useUserAssetInfos = (
error,
} = useAccountBalances(walletAddress);
const { data: assetInfos } = useAssetInfos({ withPrices: true });
const { data: lpMap } = useLPShareInfo();
const { data: movePoolInfos } = useMovePoolInfos();

const contractBalancesWithAssetInfos = balances
const balancesWithAssetInfos = balances
?.filter((bal) => Number(bal.amount))
.map<BalanceWithAssetInfo>((balance): BalanceWithAssetInfo => {
const assetInfo = assetInfos?.[balance.id];
const lpDetails = lpMap?.[balance.id];
return lpDetails
const movePoolInfo = movePoolInfos?.[balance.id];
return movePoolInfo
? {
isLPToken: true,
balance: {
...balance,
price: lpDetails.lpPricePerShare.toNumber(),
symbol: lpDetails.symbol,
precision: lpDetails.precision,
price: movePoolInfo.lpPricePerShare?.toNumber(),
symbol: `${getTokenLabel(
movePoolInfo.coinA.denom,
movePoolInfo.coinA.symbol
)}-${getTokenLabel(
movePoolInfo.coinB.denom,
movePoolInfo.coinB.symbol
)}`,
songwongtp marked this conversation as resolved.
Show resolved Hide resolved
precision: movePoolInfo.precision,
},
assetInfo,
isLPToken: true,
lpLogo: lpDetails.image,
logo: movePoolInfo.logo,
}
: {
isLPToken: false,
balance,
assetInfo,
isLPToken: false,
};
});

// Supported assets should order by descending value
const supportedAssets = contractBalancesWithAssetInfos
const supportedAssets = balancesWithAssetInfos
?.filter(
(balance) =>
!isUndefined(balance.assetInfo) ||
!isUndefined(lpMap?.[balance.balance.id])
!isUndefined(movePoolInfos?.[balance.balance.id]?.lpPricePerShare)
)
.sort((a, b) =>
!isUndefined(a.balance.price) && !isUndefined(b.balance.price)
Expand All @@ -213,16 +219,17 @@ export const useUserAssetInfos = (
: 1
);

const unsupportedAssets = contractBalancesWithAssetInfos?.filter(
const unsupportedAssets = balancesWithAssetInfos?.filter(
(balance) =>
isUndefined(balance.assetInfo) && isUndefined(lpMap?.[balance.balance.id])
isUndefined(balance.assetInfo) &&
isUndefined(movePoolInfos?.[balance.balance.id]?.lpPricePerShare)
);

return {
supportedAssets,
unsupportedAssets,
isLoading,
totalData: contractBalancesWithAssetInfos?.length,
totalData: balancesWithAssetInfos?.length,
error: error as Error,
};
};
Expand All @@ -248,10 +255,10 @@ const calBonded = (
export const useUserDelegationInfos = (walletAddress: HumanAddr) => {
const { data: rawStakingParams, isLoading: isLoadingRawStakingParams } =
useStakingParams();
const { data: lpMap, isFetching: isLpMapFetching } = useLPShareInfo();
const { data: assetInfos, isLoading: isLoadingAssetInfos } = useAssetInfos({
withPrices: true,
});
const { data: lpMap, isFetching: isLpMapFetching } = useMovePoolInfos();
const { data: validators, isLoading: isLoadingValidators } = useValidators();

const { data: rawDelegations, isLoading: isLoadingRawDelegations } =
Expand Down
4 changes: 2 additions & 2 deletions src/lib/services/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import type { AssetInfo } from "lib/types";
import { AssetInfoSchema } from "lib/types";

export const getAssetInfos = async (
baseApiRoute: string,
endpoint: string,
withPrices: boolean
): Promise<AssetInfo[]> =>
axios
.get(`${baseApiRoute}`, {
.get(`${endpoint}`, {
params: {
with_prices: withPrices,
},
Expand Down
2 changes: 1 addition & 1 deletion src/lib/services/assetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CELATONE_QUERY_KEYS, useBaseApiRoute } from "lib/app-provider";
import { getAssetInfos } from "lib/services/asset";
import type { AssetInfo, Option } from "lib/types";

export type AssetInfosOpt = Option<{ [key: string]: AssetInfo }>;
export type AssetInfosOpt = Option<Record<string, AssetInfo>>;

export const useAssetInfos = ({ withPrices }: { withPrices: boolean }) => {
const assetsApiRoute = useBaseApiRoute("assets");
Expand Down
3 changes: 1 addition & 2 deletions src/lib/services/move/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from "./module";
export * from "./moduleService";
export * from "./resource";
export * from "./poolService";
export * from "./resourceService";
46 changes: 46 additions & 0 deletions src/lib/services/move/pool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import axios from "axios";
import { z } from "zod";

import { HexAddrSchema } from "lib/types";

const PairResponseCoinSchema = z.object({
metadata: HexAddrSchema,
denom: z.string(),
decimals: z.number().nonnegative(),
});
const PairResponseSchema = z
.object({
coin_a: PairResponseCoinSchema,
coin_b: PairResponseCoinSchema,
liquidity_token: PairResponseCoinSchema,
coin_a_weight: z.string(),
coin_b_weight: z.string(),
coin_a_amount: z.string(),
coin_b_amount: z.string(),
total_share: z.string(),
})
.transform((val) => ({
coin_a: {
metadata: val.coin_a.metadata,
denom: val.coin_a.denom,
precision: val.coin_a.decimals,
weight: val.coin_a_weight,
amount: val.coin_a_amount,
},
coin_b: {
denom: val.coin_b.denom,
metadata: val.coin_b.metadata,
precision: val.coin_b.decimals,
weight: val.coin_b_weight,
amount: val.coin_b_amount,
},
lp_denom: val.liquidity_token.denom,
lp_metadata: val.liquidity_token.metadata,
precision: val.liquidity_token.decimals,
total_share: val.total_share,
}));

export const getMovePoolInfos = async (endpoint: string) =>
axios
.get(`${endpoint}/pools`)
.then((res) => z.array(PairResponseSchema).parse(res.data));
113 changes: 113 additions & 0 deletions src/lib/services/move/poolService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useQuery } from "@tanstack/react-query";
import type { Big } from "big.js";
import big from "big.js";

import { useAssetInfos } from "../assetService";
import {
CELATONE_QUERY_KEYS,
useBaseApiRoute,
useMoveConfig,
} from "lib/app-provider";
import type { MovePoolInfos, Option, USD } from "lib/types";

import { getMovePoolInfos } from "./pool";

const computePricePerShare = (
amountAPerShare: Big,
weightA: string,
priceA: Option<number>,
amountBPerShare: Big,
weightB: string,
priceB: Option<number>
): Option<USD<Big>> => {
if (priceA && priceB)
return big(priceA)
.times(amountAPerShare)
.plus(big(priceB).times(amountBPerShare)) as USD<Big>;
if (priceA)
return big(priceA)
.times(amountAPerShare)
.times(big(weightA).plus(weightB).div(weightA)) as USD<Big>;
if (priceB)
return big(priceB)
.times(amountBPerShare)
.times(big(weightA).plus(weightB).div(weightB)) as USD<Big>;
return undefined;
};

// TODO: add withPrices option
export const useMovePoolInfos = () => {
const moveConfig = useMoveConfig({ shouldRedirect: false });
const moveEndpoint = useBaseApiRoute("move");

const {
data: assetInfos,
isLoading: isAssetsLoading,
error: assetsErrors,
} = useAssetInfos({ withPrices: true });
const {
data: pools,
isLoading: isPoolsLoading,
error: poolsErrors,
...queryResult
} = useQuery(
[CELATONE_QUERY_KEYS.MOVE_POOL_INFOS, moveEndpoint],
async () => getMovePoolInfos(moveEndpoint),
{
enabled: moveConfig.enabled,
refetchOnWindowFocus: false,
}
);

const data = pools?.reduce<MovePoolInfos>((acc, curr) => {
const coinAInfo = assetInfos?.[curr.coin_a.denom];
const coinAprecision = coinAInfo?.precision ?? curr.coin_a.precision;
const coinBInfo = assetInfos?.[curr.coin_b.denom];
const coinBprecision = coinBInfo?.precision ?? curr.coin_b.precision;

const totalShares = big(curr.total_share).div(big(10).pow(curr.precision));
const amountAPerShare = big(curr.coin_a.amount)
.div(big(10).pow(coinAprecision))
.div(totalShares);
const amountBPerShare = big(curr.coin_b.amount)
.div(big(10).pow(coinBprecision))
.div(totalShares);

const lpPricePerShare = computePricePerShare(
amountAPerShare,
curr.coin_a.weight,
coinAInfo?.price,
amountBPerShare,
curr.coin_b.weight,
coinBInfo?.price
);

return {
...acc,
[curr.lp_denom]: {
coinA: {
...curr.coin_a,
precision: coinAprecision,
amountAPerShare,
symbol: coinAInfo?.symbol,
},
coinB: {
...curr.coin_b,
precision: coinBprecision,
amountBPerShare,
symbol: coinBInfo?.symbol,
},
precision: curr.precision,
lpPricePerShare,
logo: [coinAInfo?.logo, coinBInfo?.logo],
},
};
}, {});

return {
songwongtp marked this conversation as resolved.
Show resolved Hide resolved
...queryResult,
isLoading: isAssetsLoading || isPoolsLoading,
error: assetsErrors ?? poolsErrors,
data,
};
};
Loading
Loading