diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f6a44f6..dfed6cd74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Improvements +- [#682](https://github.com/alleslabs/celatone-frontend/pull/682) Render token amount < 0.000001 properly - [#669](https://github.com/alleslabs/celatone-frontend/pull/669) api v1 - contract transaction - [#672](https://github.com/alleslabs/celatone-frontend/pull/672) refactor balances - [#662](https://github.com/alleslabs/celatone-frontend/pull/662) Add republish button in module detail diff --git a/src/config/chain/initia.ts b/src/config/chain/initia.ts index 19ee55913..9b48398e1 100644 --- a/src/config/chain/initia.ts +++ b/src/config/chain/initia.ts @@ -155,7 +155,7 @@ export const INITIA_CHAIN_CONFIGS: ChainConfigs = { prettyName: "Initia Testnet 12-1", lcd: "https://next-stone-rest.initia.tech", rpc: "https://next-stone-rpc.initia.tech:443", - indexer: "https://stone-12-1-graphql.alleslabs.dev/v1/graphql", + indexer: "https://stone-12-1-nft-graphql.alleslabs.dev/v1/graphql", wallets: [...keplrWallets], features: { faucet: { diff --git a/src/lib/components/token/TokenComposition.tsx b/src/lib/components/token/TokenComposition.tsx index 4e0566ae7..e59b91934 100644 --- a/src/lib/components/token/TokenComposition.tsx +++ b/src/lib/components/token/TokenComposition.tsx @@ -20,7 +20,10 @@ export const TokenComposition = ({ - {token.poolInfo.coinA.amount} + {formatUTokenWithPrecision( + token.poolInfo.coinA.amount, + token.poolInfo.coinA.precision ?? 0 + )} {getTokenLabel( token.poolInfo.coinA.denom, @@ -32,7 +35,10 @@ export const TokenComposition = ({ - {token.poolInfo.coinB.amount} + {formatUTokenWithPrecision( + token.poolInfo.coinB.amount, + token.poolInfo.coinB.precision ?? 0 + )} {getTokenLabel( token.poolInfo.coinB.denom, diff --git a/src/lib/services/move/poolService.ts b/src/lib/services/move/poolService.ts index b8c9834fd..02e885ca8 100644 --- a/src/lib/services/move/poolService.ts +++ b/src/lib/services/move/poolService.ts @@ -8,30 +8,35 @@ import { useBaseApiRoute, useMoveConfig, } from "lib/app-provider"; -import type { MovePoolInfos, Option, USD } from "lib/types"; +import type { MovePoolInfos, Option, Token, U, USD } from "lib/types"; +import { calculateAssetValue, toToken } from "lib/utils"; import { getMovePoolInfos } from "./pool"; -const computePricePerShare = ( - amountAPerShare: Big, +const computePricePerPShare = ( + amountAPerShare: Token, weightA: string, priceA: Option, - amountBPerShare: Big, + amountBPerShare: Token, weightB: string, - priceB: Option + priceB: Option, + poolPrecision: number ): Option> => { + const multiplier = big(10).pow(poolPrecision); if (priceA && priceB) - return big(priceA) - .times(amountAPerShare) - .plus(big(priceB).times(amountBPerShare)) as USD; - if (priceA) - return big(priceA) - .times(amountAPerShare) - .times(big(weightA).plus(weightB).div(weightA)) as USD; - if (priceB) - return big(priceB) - .times(amountBPerShare) - .times(big(weightA).plus(weightB).div(weightB)) as USD; + return calculateAssetValue(amountAPerShare, priceA as USD) + .plus(calculateAssetValue(amountBPerShare, priceB as USD)) + .times(multiplier) as USD; + + const totalWeight = big(weightA).plus(weightB); + if (priceA && big(weightA).gt(0)) + return calculateAssetValue(amountAPerShare, priceA as USD) + .times(totalWeight.div(weightA)) + .times(multiplier) as USD; + if (priceB && big(weightB).gt(0)) + return calculateAssetValue(amountBPerShare, priceB as USD) + .times(totalWeight.div(weightB)) + .times(multiplier) as USD; return undefined; }; @@ -61,29 +66,28 @@ export const useMovePoolInfos = () => { const data = pools?.reduce((acc, curr) => { const coinAInfo = assetInfos?.[curr.coin_a.denom]; - const coinAprecision = coinAInfo?.precision ?? 0; const coinBInfo = assetInfos?.[curr.coin_b.denom]; - const coinBprecision = coinBInfo?.precision ?? 0; - const totalShares = big(curr.total_share).div(big(10).pow(curr.precision)); - const [amountAPerShare, amountBPerShare] = totalShares.eq(0) + const totalShares = big(curr.total_share); + const [tempA, tempB] = totalShares.eq(0) ? [big(0), big(0)] : [ - big(curr.coin_a.amount) - .div(big(10).pow(coinAprecision)) - .div(totalShares), - big(curr.coin_b.amount) - .div(big(10).pow(coinBprecision)) - .div(totalShares), + big(curr.coin_a.amount).div(totalShares), + big(curr.coin_b.amount).div(totalShares), ]; + const [amountAPerShare, amountBPerShare] = [tempA, tempB] as [ + U>, + U>, + ]; - const lpPricePerShare = computePricePerShare( - amountAPerShare, + const lpPricePerPShare = computePricePerPShare( + toToken(amountAPerShare, coinAInfo?.precision ?? 0), curr.coin_a.weight, coinAInfo?.price, - amountBPerShare, + toToken(amountBPerShare, coinBInfo?.precision ?? 0), curr.coin_b.weight, - coinBInfo?.price + coinBInfo?.price, + curr.precision ); return { @@ -91,18 +95,18 @@ export const useMovePoolInfos = () => { [curr.lp_denom]: { coinA: { ...curr.coin_a, - precision: coinAprecision, amountAPerShare, + precision: coinAInfo?.precision, symbol: coinAInfo?.symbol, }, coinB: { ...curr.coin_b, - precision: coinBprecision, amountBPerShare, + precision: coinBInfo?.precision, symbol: coinBInfo?.symbol, }, precision: curr.precision, - lpPricePerShare, + lpPricePerPShare, logo: [coinAInfo?.logo, coinBInfo?.logo], }, }; diff --git a/src/lib/types/move/pool.ts b/src/lib/types/move/pool.ts index 935a24973..42bde75f1 100644 --- a/src/lib/types/move/pool.ts +++ b/src/lib/types/move/pool.ts @@ -1,6 +1,8 @@ +import type Big from "big.js"; + import type { HexAddr } from "../addrs"; import type { Option } from "../common"; -import type { USD } from "../currency"; +import type { Token, U, USD } from "../currency"; export type MovePoolInfos = Record< string, @@ -8,18 +10,18 @@ export type MovePoolInfos = Record< coinA: { metadata: HexAddr; denom: string; - precision: number; - amountAPerShare: Big; + amountAPerShare: U>; + precision: Option; symbol: Option; }; coinB: { metadata: HexAddr; denom: string; - precision: number; - amountBPerShare: Big; + amountBPerShare: U>; + precision: Option; symbol: Option; }; - lpPricePerShare: Option>; + lpPricePerPShare: Option>; precision: number; logo: Option[]; } diff --git a/src/lib/types/pool.ts b/src/lib/types/pool.ts index c311daac9..69697b0ed 100644 --- a/src/lib/types/pool.ts +++ b/src/lib/types/pool.ts @@ -6,7 +6,9 @@ import type { ContractAddr, Nullable, Option, + Token, TokenWithValue, + U, } from "lib/types"; export enum PoolType { @@ -57,12 +59,14 @@ export interface PoolDetail< export interface PoolInfo { coinA: { - amount: string; + amount: U>; + precision: Option; denom: string; symbol: Option; }; coinB: { - amount: string; + amount: U>; + precision: Option; denom: string; symbol: Option; }; diff --git a/src/lib/utils/assetValue.test.ts b/src/lib/utils/assetValue.test.ts index bb1597501..f3a9e5f01 100644 --- a/src/lib/utils/assetValue.test.ts +++ b/src/lib/utils/assetValue.test.ts @@ -16,7 +16,6 @@ import { coinToTokenWithValue, filterSupportedTokens, } from "./assetValue"; -import { formatUTokenWithPrecision } from "./formatter"; describe("filterSupportedTokens", () => { const token1: TokenWithValue = { @@ -53,12 +52,14 @@ describe("filterSupportedTokens", () => { poolInfo: { coinA: { denom: "", - amount: "", + amount: big(0) as U>, + precision: undefined, symbol: undefined, }, coinB: { denom: "", - amount: "", + amount: big(0) as U>, + precision: undefined, symbol: undefined, }, }, @@ -106,17 +107,17 @@ describe("coinToTokenWithValue", () => { metadata: zHexAddr.parse("0x1"), denom: "denom1", precision: 6, - amountAPerShare: big(1), + amountAPerShare: big(1) as U>, symbol: undefined, }, coinB: { metadata: zHexAddr.parse("0x2"), denom: "denom2", precision: 6, - amountBPerShare: big(1), + amountBPerShare: big(1) as U>, symbol: "DENOM_2", }, - lpPricePerShare: big(1) as USD, + lpPricePerPShare: big(0.000001) as USD, precision: 6, logo: ["denom1_logo", "denom2_logo"], }, @@ -144,28 +145,24 @@ describe("coinToTokenWithValue", () => { amount: big(coin.amount) as U>, symbol: `${movePoolInfo.coinA.denom}-${movePoolInfo.coinB.symbol}`, precision: movePoolInfo.precision, - price: movePoolInfo.lpPricePerShare, + price: movePoolInfo.lpPricePerPShare, value: big(coin.amount) - .mul(movePoolInfo.lpPricePerShare ?? big(0)) + .mul(movePoolInfo.lpPricePerPShare ?? big(0)) .div(10 ** movePoolInfo.precision) as USD, poolInfo: { coinA: { - amount: formatUTokenWithPrecision( - big(coin.amount).times(movePoolInfo.coinA.amountAPerShare) as U< - Token - >, - movePoolInfo.precision - ), + amount: movePoolInfo.coinA.amountAPerShare.times(coin.amount) as U< + Token + >, + precision: 6, denom: movePoolInfo.coinA.denom, symbol: movePoolInfo.coinA.symbol, }, coinB: { - amount: formatUTokenWithPrecision( - big(coin.amount).times(movePoolInfo.coinB.amountBPerShare) as U< - Token - >, - movePoolInfo.precision - ), + amount: movePoolInfo.coinB.amountBPerShare.times(coin.amount) as U< + Token + >, + precision: 6, denom: movePoolInfo.coinB.denom, symbol: movePoolInfo.coinB.symbol, }, diff --git a/src/lib/utils/assetValue.ts b/src/lib/utils/assetValue.ts index 34118ea4e..985dc92d5 100644 --- a/src/lib/utils/assetValue.ts +++ b/src/lib/utils/assetValue.ts @@ -12,11 +12,11 @@ import type { USD, } from "lib/types"; -import { formatUTokenWithPrecision, getTokenLabel, toToken } from "./formatter"; +import { getTokenLabel, toToken } from "./formatter"; export const calculateAssetValue = ( amount: Token, - price: USD + price: USD ): USD => big(amount).mul(price) as USD; export const filterSupportedTokens = (tokens: Option) => @@ -45,6 +45,7 @@ export const coinToTokenWithValue = ( const tokenAmount = big(amount) as U>; const assetInfo = assetInfos?.[denom]; const movePoolInfo = poolInfos?.[denom]; + return movePoolInfo ? { isLPToken: true, @@ -59,30 +60,27 @@ export const coinToTokenWithValue = ( )}`, logo: movePoolInfo.logo, precision: movePoolInfo.precision, - price: movePoolInfo.lpPricePerShare, - value: movePoolInfo.lpPricePerShare - ? (tokenAmount - .times(movePoolInfo.lpPricePerShare) - .div(big(10).pow(movePoolInfo.precision)) as USD) + price: movePoolInfo.lpPricePerPShare, + value: movePoolInfo.lpPricePerPShare + ? calculateAssetValue( + toToken(tokenAmount, movePoolInfo.precision), + movePoolInfo.lpPricePerPShare + ) : undefined, poolInfo: { coinA: { - amount: formatUTokenWithPrecision( - tokenAmount.times(movePoolInfo.coinA.amountAPerShare) as U< - Token - >, - movePoolInfo.precision - ), + amount: tokenAmount.times(movePoolInfo.coinA.amountAPerShare) as U< + Token + >, + precision: movePoolInfo.coinA.precision, denom: movePoolInfo.coinA.denom, symbol: movePoolInfo.coinA.symbol, }, coinB: { - amount: formatUTokenWithPrecision( - tokenAmount.times(movePoolInfo.coinB.amountBPerShare) as U< - Token - >, - movePoolInfo.precision - ), + amount: tokenAmount.times(movePoolInfo.coinB.amountBPerShare) as U< + Token + >, + precision: movePoolInfo.coinB.precision, denom: movePoolInfo.coinB.denom, symbol: movePoolInfo.coinB.symbol, }, diff --git a/src/lib/utils/formatter/token.test.ts b/src/lib/utils/formatter/token.test.ts index 826334c12..e95317a10 100644 --- a/src/lib/utils/formatter/token.test.ts +++ b/src/lib/utils/formatter/token.test.ts @@ -183,6 +183,17 @@ describe("formatUTokenWithPrecision", () => { ).toEqual("0.000"); }); }); + test("too small", () => { + expect(formatUTokenWithPrecision("0.1" as U, 6, false)).toEqual( + "<0.000001" + ); + expect(formatUTokenWithPrecision("0.1" as U, 7, false)).toEqual( + "<0.0000001" + ); + expect(formatUTokenWithPrecision("0.1" as U, 6, false, 2)).toEqual( + "<0.01" + ); + }); test("no suffix", () => { expect( formatUTokenWithPrecision("12345678901234567890" as U, 6, false) diff --git a/src/lib/utils/formatter/token.ts b/src/lib/utils/formatter/token.ts index 406457416..e5a29c488 100644 --- a/src/lib/utils/formatter/token.ts +++ b/src/lib/utils/formatter/token.ts @@ -75,6 +75,12 @@ export const formatUTokenWithPrecision = ( if (token.gte(M)) return `${d2Formatter(token.div(M), "0.00")}M`; if (token.gte(K)) return `${d2Formatter(token, "0.00")}`; } + + const lowestThreshold = big(10).pow(-(decimalPoints ?? precision)); + if (!token.eq(0) && token.lt(lowestThreshold)) { + return `<${lowestThreshold.toFixed()}`; + } + return formatDecimal({ decimalPoints: decimalPoints ?? precision, delimiter: true,