diff --git a/apps/bridge/src/components/ConnectWallet.tsx b/apps/bridge/src/components/ConnectWallet.tsx index ff5a949a..09daba5e 100644 --- a/apps/bridge/src/components/ConnectWallet.tsx +++ b/apps/bridge/src/components/ConnectWallet.tsx @@ -6,11 +6,15 @@ import StateContext from "@providers/stateContext"; import { useAccount, useConnect, useDisconnect, useNetwork } from "wagmi"; -import { truncateAddress } from "@utils/formatStrings"; import { CHAINS, L1_CHAIN_ID, L2_CHAIN_ID } from "@config/constants"; import Avatar from "@mantle/ui/src/presentational/Avatar"; -import { ArrowDownIcon, Button, WalletModal } from "@mantle/ui"; +import { + ArrowDownIcon, + Button, + WalletModal, + truncateAddress, +} from "@mantle/ui"; import { BiError } from "react-icons/bi"; import { useIsChainID } from "@hooks/web3/read/useIsChainID"; diff --git a/apps/bridge/src/components/bridge/TokenSelect.tsx b/apps/bridge/src/components/bridge/TokenSelect.tsx index 8d36e512..bf604e77 100644 --- a/apps/bridge/src/components/bridge/TokenSelect.tsx +++ b/apps/bridge/src/components/bridge/TokenSelect.tsx @@ -16,9 +16,9 @@ import { L1_CHAIN_ID, L2_CHAIN_ID, } from "@config/constants"; -import { localeZero, formatBigNumberString } from "@utils/formatStrings"; +import { localeZero } from "@utils/formatStrings"; import { formatUnits, parseUnits } from "ethers/lib/utils.js"; - +import { formatBigNumberString } from "@mantle/ui"; import DirectionLabel from "@components/bridge/utils/DirectionLabel"; import { MantleLogo } from "@components/bridge/utils/MantleLogo"; import KindReminder from "@components/bridge/utils/KindReminder"; diff --git a/apps/bridge/src/components/bridge/TransactionPanel.tsx b/apps/bridge/src/components/bridge/TransactionPanel.tsx index fe051787..20f67a11 100644 --- a/apps/bridge/src/components/bridge/TransactionPanel.tsx +++ b/apps/bridge/src/components/bridge/TransactionPanel.tsx @@ -3,7 +3,7 @@ import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import StateContext from "@providers/stateContext"; -import { Typography } from "@mantle/ui"; +import { Typography, formatBigNumberString } from "@mantle/ui"; import { Direction, @@ -15,11 +15,7 @@ import { import { formatEther, formatUnits, parseUnits } from "ethers/lib/utils.js"; import { BigNumber, constants } from "ethers"; -import { - localeZero, - formatBigNumberString, - formatTime, -} from "@utils/formatStrings"; +import { localeZero, formatTime } from "@utils/formatStrings"; import { useIsChainID } from "@hooks/web3/read/useIsChainID"; import { useMantleSDK } from "@providers/mantleSDKContext"; import { useQuery } from "wagmi"; diff --git a/apps/bridge/src/components/bridge/utils/TxLink.tsx b/apps/bridge/src/components/bridge/utils/TxLink.tsx index bb53525e..82038a16 100644 --- a/apps/bridge/src/components/bridge/utils/TxLink.tsx +++ b/apps/bridge/src/components/bridge/utils/TxLink.tsx @@ -1,5 +1,5 @@ import { CHAINS } from "@config/constants"; -import { truncateAddress } from "@utils/formatStrings"; +import { truncateAddress } from "@mantle/ui/src/formatString"; // a link to the networks block explorer export default function TxLink({ diff --git a/apps/bridge/src/utils/formatStrings.ts b/apps/bridge/src/utils/formatStrings.ts index cec8c36c..317945fe 100644 --- a/apps/bridge/src/utils/formatStrings.ts +++ b/apps/bridge/src/utils/formatStrings.ts @@ -1,7 +1,5 @@ "use client"; -import Decimal from "decimal.js"; - export const localeZero = new Intl.NumberFormat( globalThis.navigator?.language || "en", { @@ -9,52 +7,6 @@ export const localeZero = new Intl.NumberFormat( } ).format(Number("0.0")); -export const formatBigNumberString = ( - str: string, - precision = 6, - groupingInt = true, - groupingFrac = true, - locale = globalThis.navigator?.language || "en" -) => { - const integerPart = BigInt(str.split(".")[0] || 0); - const fractionPart = new Decimal( - `0.${str?.split(".")?.[1]?.slice(0, precision) || 0}` - ) - .toFixed(precision) - .toString() - .replace(/^0|0+$/g, "") - .replace(/^([^0-9])$/, "$10"); - - // format according to the given locale - const mainFormatInt = new Intl.NumberFormat(locale, { - minimumFractionDigits: 0, - useGrouping: groupingInt, - }); - // format according to the given locale - const mainFormatFrac = new Intl.NumberFormat(locale, { - minimumFractionDigits: 0, - useGrouping: groupingFrac, - }); - - // construct a template to pull locales decimal seperator - const template = mainFormatInt.format(1.1); - - // reconnect the number parts and place templates seperator - return `${mainFormatInt.format(integerPart)}${template[1]}${mainFormatFrac - .format( - (fractionPart.length === 1 ? `${fractionPart}0` : fractionPart) - .split("") - .reverse() - .join("") as unknown as number - ) - .split("") - .reverse() - .join("")}`; -}; - -export const truncateAddress = (address: `0x${string}`) => - `${address.slice(0, 6)}...${address.slice(-4)}`; - // format a given time into a nice representation export const formatTime = ( seconds: number, diff --git a/apps/converter/src/components/ConnectWallet.tsx b/apps/converter/src/components/ConnectWallet.tsx index 22a021cc..f492425e 100644 --- a/apps/converter/src/components/ConnectWallet.tsx +++ b/apps/converter/src/components/ConnectWallet.tsx @@ -8,11 +8,15 @@ import StateContext from "@providers/stateContext"; import { useAccount, useConnect, useDisconnect, useNetwork } from "wagmi"; import { InjectedConnector } from "wagmi/connectors/injected"; -import { truncateAddress } from "@utils/formatStrings"; import { CHAINS, L1_CHAIN_ID } from "@config/constants"; import Avatar from "@mantle/ui/src/presentational/Avatar"; -import { ArrowDownIcon, Button, WalletModal } from "@mantle/ui"; +import { + ArrowDownIcon, + Button, + WalletModal, + truncateAddress, +} from "@mantle/ui"; import { BiError } from "react-icons/bi"; import { useIsChainID } from "@hooks/web3/read/useIsChainID"; diff --git a/apps/converter/src/components/converter/From.tsx b/apps/converter/src/components/converter/From.tsx index 1f8ec57a..36964a01 100644 --- a/apps/converter/src/components/converter/From.tsx +++ b/apps/converter/src/components/converter/From.tsx @@ -4,11 +4,10 @@ import StateContext from "@providers/stateContext"; import Image from "next/image"; import { L1_BITDAO_TOKEN } from "@config/constants"; -import { formatBigNumberString } from "@utils/formatStrings"; +import { formatBigNumberString, Button } from "@mantle/ui"; import { formatUnits, parseUnits } from "ethers/lib/utils.js"; import BalanceLabel from "@components/converter/utils/BalanceLabel"; -import { Button } from "@mantle/ui"; import { MantleLogo } from "./utils/MantleLogo"; export default function TokenSelect() { @@ -280,7 +279,10 @@ export default function TokenSelect() { ) ? "0.0" : formatBigNumberString( - balances?.[L1_BITDAO_TOKEN.address] + balances?.[L1_BITDAO_TOKEN.address], + L1_BITDAO_TOKEN.decimals, + true, + false ) || "0.0" }${" "}${L1_BITDAO_TOKEN.symbol}`} /> diff --git a/apps/converter/src/components/converter/To.tsx b/apps/converter/src/components/converter/To.tsx index 443ce29f..d46b88b0 100644 --- a/apps/converter/src/components/converter/To.tsx +++ b/apps/converter/src/components/converter/To.tsx @@ -9,7 +9,7 @@ import BalanceLabel from "@components/converter/utils/BalanceLabel"; import { formatUnits, parseUnits } from "ethers/lib/utils.js"; import { useContext, useMemo } from "react"; import StateContext from "@providers/stateContext"; -import { formatBigNumberString } from "@utils/formatStrings"; +import { formatBigNumberString } from "@mantle/ui"; export default function Destination() { const { amount, balances } = useContext(StateContext); @@ -36,7 +36,12 @@ export default function Destination() { className="max-w-[min(346px,100vw)] truncate" title={`${value} MNT`} > - {formatBigNumberString(value, L1_MANTLE_TOKEN.decimals, false)} + {formatBigNumberString( + value, + L1_MANTLE_TOKEN.decimals, + true, + false + )} MNT @@ -53,8 +58,12 @@ export default function Destination() { parseFloat(balances?.[L1_MANTLE_TOKEN.address] || "") ) ? "0" - : formatBigNumberString(balances?.[L1_MANTLE_TOKEN.address]) || - "0" + : formatBigNumberString( + balances?.[L1_MANTLE_TOKEN.address], + L1_MANTLE_TOKEN.decimals, + true, + false + ) || "0" }${" "}${L1_MANTLE_TOKEN.symbol}`} /> diff --git a/apps/converter/src/components/converter/utils/TxLink.tsx b/apps/converter/src/components/converter/utils/TxLink.tsx index bb53525e..82038a16 100644 --- a/apps/converter/src/components/converter/utils/TxLink.tsx +++ b/apps/converter/src/components/converter/utils/TxLink.tsx @@ -1,5 +1,5 @@ import { CHAINS } from "@config/constants"; -import { truncateAddress } from "@utils/formatStrings"; +import { truncateAddress } from "@mantle/ui/src/formatString"; // a link to the networks block explorer export default function TxLink({ diff --git a/apps/converter/src/utils/formatStrings.ts b/apps/converter/src/utils/formatStrings.ts deleted file mode 100644 index ff9b9240..00000000 --- a/apps/converter/src/utils/formatStrings.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Decimal from "decimal.js"; - -export const formatBigNumberString = ( - str: string, - precision = 6, - useGrouping = true, - locale = "en" -) => { - // format according to the given locale - const mainFormat = new Intl.NumberFormat(locale, { - useGrouping, - minimumFractionDigits: 0, - }); - - // construct a template to pull locales decimal seperator - const template = mainFormat.format(1.1); - - // extract the details from the value - const integerPart = BigInt(str.split(".")[0] || 0); - const fractionPart = new Decimal(`0.${str?.split(".")?.[1] || 0}`) - .toFixed(precision) - .toString() - // remove first 0 and repeated 00's at the end - .replace(/^0|0+$/g, "") - // match the decimal and place a 0 if decimals are absent - .replace(/^(\.)$/, ".0") - // remove the decimal so we're just left with the decimal component - .replace(/^./, ""); - - // construct the string with 2 bns (using BNs should prevent overflows and maintain strict precision) and the template seperator - return `${mainFormat.format(integerPart)}${ - fractionPart !== "0" - ? `${template[1]}${mainFormat - // format with gouping in reverse to position thsd seperators correctly - .format(BigInt(fractionPart.split("").reverse().join(""))) - // return oiriginal position post-formatting - .split("") - .reverse() - .join("")}` - : `` - }`; -}; - -export const truncateAddress = (address: `0x${string}`) => - `${address.slice(0, 6)}...${address.slice(-4)}`; diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 01f16a01..49a9768f 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -71,8 +71,15 @@ export * from './src/fonts/ThemeFonts' */ export { LocaleSwitcher } from './src/LocaleSwitcher' +/** + * Format string utils + * + */ +export * from './src/formatString' + /** * Locale Selector (given a list of locales and the currect selection) * */ export { Cookies } from './src/scripts/Cookies' + diff --git a/packages/ui/package.json b/packages/ui/package.json index 9cf4fb32..20fabc66 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -30,6 +30,7 @@ "boring-avatars": "^1.7.0", "classnames": "^2.3.2", "clsx": "^1.2.1", + "decimal.js": "^10.4.3", "react-wrap-balancer": "^0.4.0", "tailwind-merge": "^1.10.0" } diff --git a/packages/ui/src/formatString.ts b/packages/ui/src/formatString.ts new file mode 100644 index 00000000..8a669250 --- /dev/null +++ b/packages/ui/src/formatString.ts @@ -0,0 +1,49 @@ +'use client' + +import Decimal from 'decimal.js' + +export const formatBigNumberString = ( + str: string, + precision = 6, + groupingInt = true, + groupingFrac = true, + locale = globalThis.navigator?.language || 'en', +) => { + const integerPart = BigInt(str.split('.')[0] || 0) + const fractionPart = new Decimal( + `0.${str?.split('.')?.[1]?.slice(0, precision) || 0}`, + ) + .toFixed(precision) + .toString() + .replace(/^0|0+$/g, '') + .replace(/^([^0-9])$/, '$10') + + // format according to the given locale + const mainFormatInt = new Intl.NumberFormat(locale, { + minimumFractionDigits: 0, + useGrouping: groupingInt, + }) + // format according to the given locale + const mainFormatFrac = new Intl.NumberFormat(locale, { + minimumFractionDigits: 0, + useGrouping: groupingFrac, + }) + + // construct a template to pull locales decimal seperator + const template = mainFormatInt.format(1.1) + + // reconnect the number parts and place templates seperator + return `${mainFormatInt.format(integerPart)}${template[1]}${mainFormatFrac + .format( + (fractionPart.length === 1 ? `${fractionPart}0` : fractionPart) + .split('') + .reverse() + .join('') as unknown as number, + ) + .split('') + .reverse() + .join('')}` +} + +export const truncateAddress = (address: `0x${string}`) => + `${address.slice(0, 6)}...${address.slice(-4)}` diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb549da1..771cbc09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -662,7 +662,7 @@ importers: version: 8.5.0(eslint@7.32.0) eslint-config-turbo: specifier: latest - version: 1.10.7(eslint@7.32.0) + version: 1.10.6(eslint@7.32.0) eslint-import-resolver-typescript: specifier: ^3.5.2 version: 3.5.2(eslint-plugin-import@2.26.0)(eslint@7.32.0) @@ -787,6 +787,9 @@ importers: clsx: specifier: ^1.2.1 version: 1.2.1 + decimal.js: + specifier: ^10.4.3 + version: 10.4.3 react-wrap-balancer: specifier: ^0.4.0 version: 0.4.0(react@18.2.0) @@ -9793,13 +9796,13 @@ packages: eslint: 7.32.0 dev: false - /eslint-config-turbo@1.10.7(eslint@7.32.0): - resolution: {integrity: sha512-0yHt5UlXVph8S4SOvP6gYehLvYjJj6XFKTYOG/WUQbjlcF0OU4pOT1a1juqmmBPWYlvJ0evt7v+RekY4tOopPQ==} + /eslint-config-turbo@1.10.6(eslint@7.32.0): + resolution: {integrity: sha512-iZ63etePRUdEIDY5MxdUhU2ekV9TDbVdHg0BK00QqVFgQTXUYuJ7rsQj/wUKTsw3jwhbLfaY6H5sknAgYyWZ2g==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 7.32.0 - eslint-plugin-turbo: 1.10.7(eslint@7.32.0) + eslint-plugin-turbo: 1.10.6(eslint@7.32.0) dev: false /eslint-import-resolver-node@0.3.6: @@ -10042,8 +10045,8 @@ packages: string.prototype.matchall: 4.0.8 dev: false - /eslint-plugin-turbo@1.10.7(eslint@7.32.0): - resolution: {integrity: sha512-YikBHc75DY9VV1vAFUIBekHLQlxqVT5zTNibK8zBQInCUhF7PvyPJc0xXw5FSz8EYtt4uOV3r0Km3CmFRclS4Q==} + /eslint-plugin-turbo@1.10.6(eslint@7.32.0): + resolution: {integrity: sha512-jlzfxYaK8hcz1DTk8Glxxi1r0kgdy85191a4CbFOTiiBulmKHMLJgzhsyE9Ong796MA62n91KFpc20BiKjlHwg==} peerDependencies: eslint: '>6.6.0' dependencies: