From 8104af925e00edb8edb5b6c14a12373c8ba9c315 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 31 May 2024 22:45:54 +0545 Subject: [PATCH 01/16] refactor: Using bignumber instead of number for token quantity to prevent precision loss --- package.json | 3 +- .../arweave_config.background.ts | 2 +- src/api/modules/connect/connect.foreground.ts | 2 +- src/api/modules/sign/fee.ts | 13 ++-- src/api/modules/sign/utils.ts | 15 +++-- src/applications/application.ts | 2 +- src/components/auth/App.tsx | 9 +-- src/components/dashboard/Tokens.tsx | 4 +- src/components/popup/Collectible.tsx | 2 +- src/components/popup/Token.tsx | 35 ++++++----- src/components/popup/WalletSwitcher.tsx | 6 +- src/components/popup/home/Balance.tsx | 11 ++-- src/components/popup/home/Tokens.tsx | 2 +- src/popup.tsx | 3 +- src/routes/auth/connect.tsx | 2 +- src/routes/auth/sign.tsx | 10 ++- src/routes/popup/index.tsx | 2 +- src/routes/popup/notifications.tsx | 17 +++-- src/routes/popup/send/confirm.tsx | 23 +++---- src/routes/popup/send/index.tsx | 62 +++++++++--------- src/routes/popup/send/recipient.tsx | 4 +- src/routes/popup/token/[id].tsx | 28 +++++---- src/routes/popup/tokens.tsx | 4 +- src/routes/popup/transaction/[id].tsx | 7 ++- src/tokens/aoTokens/ao.ts | 42 ++++++------- src/tokens/currency.ts | 63 ++++++++----------- src/tokens/index.ts | 10 +-- src/tokens/token.ts | 7 ++- src/utils/events.ts | 2 +- src/utils/format.ts | 27 +++++--- src/wallets/hooks.ts | 5 +- yarn.lock | 10 +-- 32 files changed, 223 insertions(+), 211 deletions(-) diff --git a/package.json b/package.json index 2426780a4..7d3becb05 100644 --- a/package.json +++ b/package.json @@ -59,11 +59,12 @@ "@plasmohq/storage": "^1.7.2", "@segment/analytics-next": "^1.53.2", "@untitled-ui/icons-react": "^0.1.1", + "ao-tokens": "^0.0.4", "ar-gql": "1.2.9", - "ao-tokens": "^0.0.3", "arbundles": "^0.9.5", "arweave": "^1.13.0", "axios": "^1.7.2", + "bignumber.js": "^9.1.2", "bip39-web-crypto": "^4.0.1", "check-password-strength": "^2.0.7", "copy-to-clipboard": "^3.3.2", diff --git a/src/api/modules/arweave_config/arweave_config.background.ts b/src/api/modules/arweave_config/arweave_config.background.ts index 4d97ebb5a..fa979aac0 100644 --- a/src/api/modules/arweave_config/arweave_config.background.ts +++ b/src/api/modules/arweave_config/arweave_config.background.ts @@ -1,6 +1,6 @@ import type { ModuleFunction } from "~api/background"; import Application from "~applications/application"; -import { Gateway } from "~gateways/gateway"; +import { type Gateway } from "~gateways/gateway"; const background: ModuleFunction = async (appData) => { const app = new Application(appData.appURL); diff --git a/src/api/modules/connect/connect.foreground.ts b/src/api/modules/connect/connect.foreground.ts index 83ca792fc..afe8f4389 100644 --- a/src/api/modules/connect/connect.foreground.ts +++ b/src/api/modules/connect/connect.foreground.ts @@ -1,7 +1,7 @@ import type { PermissionType } from "~applications/permissions"; import type { AppInfo } from "~applications/application"; import type { ModuleFunction } from "~api/module"; -import { Gateway } from "~gateways/gateway"; +import { type Gateway } from "~gateways/gateway"; import { getAppURL } from "~utils/format"; const foreground: ModuleFunction = async ( diff --git a/src/api/modules/sign/fee.ts b/src/api/modules/sign/fee.ts index 25a5379a4..db91ac831 100644 --- a/src/api/modules/sign/fee.ts +++ b/src/api/modules/sign/fee.ts @@ -1,5 +1,5 @@ import { getActiveAddress, getActiveKeyfile } from "~wallets"; -import { defaultGateway, Gateway } from "~gateways/gateway"; +import { defaultGateway, type Gateway } from "~gateways/gateway"; import { freeDecryptedWallet } from "~wallets/encryption"; import type { Alarms } from "webextension-polyfill"; import { findGateway } from "~gateways/wayfinder"; @@ -10,6 +10,7 @@ import { gql } from "~gateways/api"; import Application from "~applications/application"; import browser from "webextension-polyfill"; import Arweave from "arweave/web/common"; +import BigNumber from "bignumber.js"; //import redstone from "redstone-api"; /** @@ -57,7 +58,9 @@ export default async function handleFeeAlarm(alarmInfo: Alarms.Alarm) { // fee multiplication if (feeMultiplier > 1) { - feeTx.reward = (+feeTx.reward * feeMultiplier).toFixed(0); + feeTx.reward = BigNumber(feeTx.reward) + .multipliedBy(feeMultiplier) + .toFixed(0); } await arweave.transactions.sign(feeTx, keyfile); @@ -186,13 +189,13 @@ export async function getFeeAmount(address: string, app: Application) { arPrice = res.arweave.usd; }*/ const arPrice = await getArPrice("usd"); - const usdPrice = 1 / arPrice; // 1 USD how much AR + const usdPrice = BigNumber(1).dividedBy(arPrice); // 1 USD how much AR if (res.data.transactions.edges.length) { const usd = res.data.transactions.edges.length >= 10 ? 0.01 : 0.03; - return arweave.ar.arToWinston((usdPrice * usd).toString()); - } else return arweave.ar.arToWinston((usdPrice * 0.01).toString()); + return arweave.ar.arToWinston(usdPrice.multipliedBy(usd).toString()); + } else return arweave.ar.arToWinston(usdPrice.multipliedBy(0.01).toString()); } /** diff --git a/src/api/modules/sign/utils.ts b/src/api/modules/sign/utils.ts index 2944205cb..5e2befd8c 100644 --- a/src/api/modules/sign/utils.ts +++ b/src/api/modules/sign/utils.ts @@ -6,6 +6,7 @@ import { ExtensionStorage } from "~utils/storage"; import iconUrl from "url:/assets/icon512.png"; import browser from "webextension-polyfill"; import Arweave from "arweave"; +import BigNumber from "bignumber.js"; /** * Fetch current arconfetti icon @@ -52,7 +53,7 @@ export async function calculateReward({ reward }: Transaction) { if (multiplier === 1) return reward; // calculate fee with multiplier - const fee = +reward * multiplier; + const fee = BigNumber(reward).multipliedBy(multiplier); return fee.toFixed(0); } @@ -87,18 +88,16 @@ export async function signNotification( const arweave = new Arweave(gateway); // calculate price in AR - const arPrice = parseFloat(arweave.ar.winstonToAr(price.toString())); + const arPrice = BigNumber(arweave.ar.winstonToAr(price.toString())); // format price let maximumFractionDigits = 0; - if (arPrice < 0.1) maximumFractionDigits = 6; - else if (arPrice < 10) maximumFractionDigits = 4; - else if (arPrice < 1000) maximumFractionDigits = 2; + if (arPrice.isLessThan(0.1)) maximumFractionDigits = 6; + else if (arPrice.isLessThan(10)) maximumFractionDigits = 4; + else if (arPrice.isLessThan(1000)) maximumFractionDigits = 2; - const formattedPrice = arPrice.toLocaleString(undefined, { - maximumFractionDigits - }); + const formattedPrice = arPrice.toFormat(maximumFractionDigits); // give an ID to the notification const notificationID = nanoid(); diff --git a/src/applications/application.ts b/src/applications/application.ts index 4b3a3dd65..6b64f088d 100644 --- a/src/applications/application.ts +++ b/src/applications/application.ts @@ -3,7 +3,7 @@ import { type Allowance, defaultAllowance } from "./allowance"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import type { Storage } from "@plasmohq/storage"; -import { defaultGateway, Gateway } from "~gateways/gateway"; +import { defaultGateway, type Gateway } from "~gateways/gateway"; export const PREFIX = "app_"; export const defaultBundler = "https://turbo.ardrive.io"; diff --git a/src/components/auth/App.tsx b/src/components/auth/App.tsx index 0b3e09226..77359130f 100644 --- a/src/components/auth/App.tsx +++ b/src/components/auth/App.tsx @@ -4,7 +4,7 @@ import { Spacer, Text } from "@arconnect/components"; -import { defaultGateway, Gateway } from "~gateways/gateway"; +import { defaultGateway, type Gateway } from "~gateways/gateway"; import { useTheme as useDisplayTheme } from "~utils/theme"; import type { Allowance } from "~applications/allowance"; import { formatTokenBalance } from "~tokens/currency"; @@ -15,6 +15,7 @@ import browser from "webextension-polyfill"; import Arweave from "arweave"; import styled from "styled-components"; import Label from "./Label"; +import { Quantity } from "ao-tokens"; export default function App({ appIcon, @@ -25,14 +26,14 @@ export default function App({ }: Props) { // allowance spent in AR const spent = useMemo(() => { - if (!allowance) return 0; + if (!allowance) return new Quantity("0"); return winstonToArFormatted(allowance.spent); }, [allowance]); // allowance limit in AR const limit = useMemo(() => { - if (!allowance) return 0; + if (!allowance) return new Quantity("0"); return winstonToArFormatted(allowance.limit); }, [allowance]); @@ -41,7 +42,7 @@ export default function App({ const arweave = new Arweave(defaultGateway); const arVal = arweave.ar.winstonToAr(val.toString()); - return parseFloat(arVal); + return new Quantity(arVal, 20n); } // display theme diff --git a/src/components/dashboard/Tokens.tsx b/src/components/dashboard/Tokens.tsx index 983e5b397..141c1ea08 100644 --- a/src/components/dashboard/Tokens.tsx +++ b/src/components/dashboard/Tokens.tsx @@ -32,7 +32,7 @@ export default function Tokens() { return aoTokens.map((token) => ({ id: token.processId, defaultLogo: token.Logo, - balance: 0, + balance: "0", ticker: token.Ticker, type: "asset" as TokenType, name: token.Name @@ -95,7 +95,7 @@ export default function Tokens() { const handleTokenClick = (token: { id: any; defaultLogo?: string; - balance?: number; + balance?: string; ticker?: string; type?: TokenType; name?: string; diff --git a/src/components/popup/Collectible.tsx b/src/components/popup/Collectible.tsx index d862ef2ff..f3c38e4cb 100644 --- a/src/components/popup/Collectible.tsx +++ b/src/components/popup/Collectible.tsx @@ -92,7 +92,7 @@ const Qty = styled(Name)` interface Props { id: string; name: string; - balance: number; + balance: string; divisibility?: number; decimals?: number; onClick?: MouseEventHandler; diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 6f1809b4e..49d353b96 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -24,6 +24,7 @@ import { getUserAvatar } from "~lib/avatar"; import { abbreviateNumber } from "~utils/format"; import Skeleton from "~components/Skeleton"; import { TrashIcon, PlusIcon, SettingsIcon } from "@iconicicons/react"; +import BigNumber from "bignumber.js"; export default function Token({ onClick, ...props }: Props) { const [totalBalance, setTotalBalance] = useState(""); @@ -48,26 +49,28 @@ export default function Token({ onClick, ...props }: Props) { ); const balance = useMemo(() => { - const balanceToFormat = props.ao ? props.balance : fractBalance; - const isTinyBalance = balanceToFormat > 0 && balanceToFormat < 1e-6; - const isSmallBalance = balanceToFormat < 1 && balanceToFormat >= 1e-6; + const balanceToFormat = BigNumber(props.ao ? props.balance : fractBalance); + const isTinyBalance = + balanceToFormat.isGreaterThan(0) && balanceToFormat.isLessThan(1e-6); + const isSmallBalance = + balanceToFormat.isLessThan(1) && + balanceToFormat.isGreaterThanOrEqualTo(1e-6); const formattedBalance = isTinyBalance || isSmallBalance ? balanceToFormat.toString() : formatTokenBalance(balanceToFormat); - setTotalBalance( - isTinyBalance - ? balanceToFormat.toFixed(20).replace(/(\.?)0+$/, "") - : formattedBalance - ); + setTotalBalance(balanceToFormat.toFixed()); - const numBalance = parseFloat(formattedBalance.replace(/,/g, "")); - setShowTooltip(numBalance >= 1_000_000 || isTinyBalance); + setShowTooltip( + balanceToFormat.isGreaterThanOrEqualTo(1_000_000) || isTinyBalance + ); - return isSmallBalance ? numBalance : abbreviateNumber(numBalance); - }, [fractBalance]); + return isSmallBalance + ? formattedBalance + : abbreviateNumber(balanceToFormat); + }, [fractBalance.toString()]); // token price const { price, currency } = usePrice(props.ticker); @@ -76,7 +79,7 @@ export default function Token({ onClick, ...props }: Props) { const fiatBalance = useMemo(() => { if (!price) return
; - const estimate = fractBalance * price; + const estimate = fractBalance.multipliedBy(price); return formatFiatBalance(estimate, currency.toLowerCase()); }, [price, balance, currency]); @@ -347,7 +350,7 @@ export function ArToken({ onClick }: ArTokenProps) { // load ar balance const [balance, setBalance] = useState("0"); - const [fiatBalance, setFiatBalance] = useState(0); + const [fiatBalance, setFiatBalance] = useState("0"); const gateway = useGateway({ ensureStake: true }); useEffect(() => { @@ -358,10 +361,10 @@ export function ArToken({ onClick }: ArTokenProps) { // fetch balance const winstonBalance = await arweave.wallets.getBalance(activeAddress); - const arBalance = Number(arweave.ar.winstonToAr(winstonBalance)); + const arBalance = BigNumber(arweave.ar.winstonToAr(winstonBalance)); setBalance(formatTokenBalance(arBalance)); - setFiatBalance(arBalance * price); + setFiatBalance(arBalance.multipliedBy(price).toString()); })(); }, [activeAddress, price, gateway]); diff --git a/src/components/popup/WalletSwitcher.tsx b/src/components/popup/WalletSwitcher.tsx index 7f41150fd..1c021217c 100644 --- a/src/components/popup/WalletSwitcher.tsx +++ b/src/components/popup/WalletSwitcher.tsx @@ -58,7 +58,7 @@ export default function WalletSwitcher({ storedWallets.map((wallet) => ({ name: wallet.nickname, address: wallet.address, - balance: 0, + balance: "0", hasAns: false, api: wallet.type === "hardware" ? wallet.api : undefined })) @@ -131,7 +131,7 @@ export default function WalletSwitcher({ return { ...wallet, - balance: Number(balance) + balance }; }) ); @@ -442,7 +442,7 @@ interface DisplayedWallet { name: string; api?: HardwareApi; address: string; - balance: number; + balance: string; avatar?: string; hasAns: boolean; } diff --git a/src/components/popup/home/Balance.tsx b/src/components/popup/home/Balance.tsx index 596768e0f..90ee69b4e 100644 --- a/src/components/popup/home/Balance.tsx +++ b/src/components/popup/home/Balance.tsx @@ -26,6 +26,7 @@ import Arweave from "arweave"; import { removeDecryptionKey } from "~wallets/auth"; import { findGateway } from "~gateways/wayfinder"; import type { Gateway } from "~gateways/gateway"; +import BigNumber from "bignumber.js"; export default function Balance() { const [loading, setLoading] = useState(false); @@ -39,7 +40,7 @@ export default function Balance() { const balance = useBalance(); // balance in local currency - const [fiat, setFiat] = useState(0); + const [fiat, setFiat] = useState(BigNumber("0")); const [currency] = useSetting("currency"); useEffect(() => { @@ -50,9 +51,9 @@ export default function Balance() { const arPrice = await getArPrice(currency); // calculate fiat balance - setFiat(arPrice * balance); + setFiat(BigNumber(arPrice).multipliedBy(balance)); })(); - }, [balance, currency]); + }, [balance.toString(), currency]); // balance display const [hideBalance, setHideBalance] = useStorage( @@ -123,12 +124,12 @@ export default function Balance() { } useEffect(() => { - if (balance !== historicalBalance[0]) { + if (!balance.isEqualTo(historicalBalance[0])) { setLoading(true); } else { setLoading(false); } - }, [balance, historicalBalance]); + }, [balance.toString(), historicalBalance]); return ( diff --git a/src/components/popup/home/Tokens.tsx b/src/components/popup/home/Tokens.tsx index b619c760f..edb286957 100644 --- a/src/components/popup/home/Tokens.tsx +++ b/src/components/popup/home/Tokens.tsx @@ -61,7 +61,7 @@ export default function Tokens() { defaultLogo={token?.Logo} id={token.id} ticker={token.Ticker} - balance={Number(token.balance)} + balance={token.balance || "0"} onClick={() => handleTokenClick(token.id)} /> ))} diff --git a/src/popup.tsx b/src/popup.tsx index ecb2bc3e1..07bfd769e 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -33,6 +33,7 @@ import MessageNotification from "~routes/popup/notification/[id]"; import SubscriptionDetails from "~routes/popup/subscriptions/subscriptionDetails"; import SubscriptionPayment from "~routes/popup/subscriptions/subscriptionPayment"; import SubscriptionManagement from "~routes/popup/subscriptions/subscriptionManagement"; +import BigNumber from "bignumber.js"; export default function Popup() { const theme = useTheme(); @@ -130,7 +131,7 @@ export default function Popup() { }) => ( )} diff --git a/src/routes/auth/connect.tsx b/src/routes/auth/connect.tsx index 2edef12ba..e513ee087 100644 --- a/src/routes/auth/connect.tsx +++ b/src/routes/auth/connect.tsx @@ -31,7 +31,7 @@ import App from "~components/auth/App"; import styled from "styled-components"; import { EventType, trackEvent } from "~utils/analytics"; import Application from "~applications/application"; -import { defaultGateway, Gateway } from "~gateways/gateway"; +import { defaultGateway, type Gateway } from "~gateways/gateway"; export default function Connect() { // active address diff --git a/src/routes/auth/sign.tsx b/src/routes/auth/sign.tsx index e8a8eefe9..507d171aa 100644 --- a/src/routes/auth/sign.tsx +++ b/src/routes/auth/sign.tsx @@ -39,6 +39,7 @@ import useSetting from "~settings/hook"; import prettyBytes from "pretty-bytes"; import Arweave from "arweave"; import { defaultGateway } from "~gateways/gateway"; +import BigNumber from "bignumber.js"; export default function Sign() { // sign params @@ -97,13 +98,13 @@ export default function Sign() { // quantity const quantity = useMemo(() => { if (!params?.transaction?.quantity) { - return 0; + return BigNumber("0"); } const arweave = new Arweave(defaultGateway); const ar = arweave.ar.winstonToAr(params.transaction.quantity); - return Number(ar); + return BigNumber(ar); }, [params]); // currency setting @@ -119,7 +120,10 @@ export default function Sign() { }, [currency]); // transaction price - const fiatPrice = useMemo(() => quantity * arPrice, [quantity, arPrice]); + const fiatPrice = useMemo( + () => quantity.multipliedBy(arPrice), + [quantity.toString(), arPrice] + ); // transaction fee const fee = useMemo(() => { diff --git a/src/routes/popup/index.tsx b/src/routes/popup/index.tsx index 5b19178cd..87457da61 100644 --- a/src/routes/popup/index.tsx +++ b/src/routes/popup/index.tsx @@ -57,7 +57,7 @@ export default function Home() { if (tokens) { setNoBalance(false); return; - } else if (balance) { + } else if (balance.toNumber()) { setNoBalance(false); return; } else { diff --git a/src/routes/popup/notifications.tsx b/src/routes/popup/notifications.tsx index e469399a9..bc9988b70 100644 --- a/src/routes/popup/notifications.tsx +++ b/src/routes/popup/notifications.tsx @@ -95,14 +95,11 @@ export default function Notifications() { quantityTransfered = notification.quantity; } else { ticker = token.Ticker; - quantityTransfered = balanceToFractioned( - Number(notification.quantity), - { - id: notification.tokenId, - decimals: token.Denomination, - divisibility: token.Denomination - } - ); + quantityTransfered = balanceToFractioned(notification.quantity, { + id: notification.tokenId, + decimals: token.Denomination, + divisibility: token.Denomination + }).toFixed(); } } else if (notification.transactionType !== "Transaction") { let token = await fetchTokenById(notification.tokenId); @@ -117,7 +114,7 @@ export default function Notifications() { notification.node.tags ); quantityTransfered = formatTokenBalance( - balanceToFractioned(Number(quantityTransfered), { + balanceToFractioned(quantityTransfered, { id: notification.tokenId, decimals: token.decimals, divisibility: token.divisibility @@ -126,7 +123,7 @@ export default function Notifications() { } else { ticker = token.ticker; quantityTransfered = formatTokenBalance( - Number(notification.quantity) + notification.quantity || "0" ); } } diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index 47a058cce..4e2b4d854 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -54,6 +54,7 @@ import Progress from "~components/Progress"; import { updateSubscription } from "~subscriptions"; import { SubscriptionStatus } from "~subscriptions/subscription"; import { checkPassword } from "~wallets/auth"; +import BigNumber from "bignumber.js"; interface Props { tokenID: string; @@ -62,13 +63,8 @@ interface Props { subscription?: boolean; } -function formatNumber(amount: number, decimalPlaces: number = 2): string { - const rounded = amount.toFixed(decimalPlaces); - - const factor = Math.pow(10, decimalPlaces); - const truncated = Math.floor(amount * factor) / factor; - - return rounded; +function formatNumber(amount: string, decimalPlaces: number = 2): string { + return BigNumber(amount).toFixed(decimalPlaces); } export default function Confirm({ tokenID, qty, subscription }: Props) { @@ -115,16 +111,15 @@ export default function Confirm({ tokenID, qty, subscription }: Props) { try { const data: TransactionData = await TempTransactionStorage.get("send"); if (data) { - if (Number(data.qty) < Number(allowance)) { + const qty = BigNumber(data.qty); + if (qty.isLessThan(Number(allowance))) { setNeedsSign(false); } else { setNeedsSign(true); } - const estimatedFiatTotal = Number( - ( - Number(data.estimatedFiat) + Number(data.estimatedNetworkFee) - ).toFixed(2) - ); + const estimatedFiatTotal = BigNumber(data.estimatedFiat) + .plus(data.estimatedNetworkFee) + .toFixed(2); setIsAo(data.isAo); setRecipient(data.recipient); setEstimatedTotal(estimatedFiatTotal.toString()); @@ -663,7 +658,7 @@ export default function Confirm({ tokenID, qty, subscription }: Props) { { (async () => { if (token.id === "AR") { - return setBalance(arBalance); + return setBalance(arBalance.toString()); } // placeholder balance @@ -239,20 +240,20 @@ export default function Send({ id }: Props) { ); setBalance( - balanceToFractioned(result[0], { + balanceToFractioned(String(result[0]), { id: token.id, decimals: token.decimals, divisibility: token.divisibility - }) + }).toString() ); } else { setBalance(token.balance); } })(); - }, [token, activeAddress, arBalance, id]); + }, [token, activeAddress, arBalance.toString(), id]); // token price - const [price, setPrice] = useState(0); + const [price, setPrice] = useState("0"); const message = useInput(); useEffect(() => { @@ -260,34 +261,34 @@ export default function Send({ id }: Props) { if (token.id === "AR") { const arPrice = await getArPrice(currency); - return setPrice(arPrice); + return setPrice(arPrice.toString()); } // get price from redstone if (isAo) { - return setPrice(0); + return setPrice("0"); } const res = await redstone.getPrice(token.ticker); if (!res.value) { - return setPrice(0); + return setPrice("0"); } // get price in currency const multiplier = currency !== "usd" ? await getPrice("usd", currency) : 1; - setPrice(res.value * multiplier); + setPrice(BigNumber(res.value).multipliedBy(multiplier).toString()); })(); }, [token, currency]); // quantity in the other currency const secondaryQty = useMemo(() => { - const qtyParsed = parseFloat(qty) || 0; + const qtyParsed = BigNumber(qty || "0"); - if (qtyMode === "token") return qtyParsed * price; - else return qtyParsed * (1 / price); + if (qtyMode === "token") return qtyParsed.multipliedBy(price); + else return qtyParsed.dividedBy(price); }, [qty, qtyMode, price]); // network fee @@ -317,35 +318,38 @@ export default function Send({ id }: Props) { // maximum possible send amount const max = useMemo(() => { - let maxAmountToken = balance - parseFloat(networkFee); + const balanceBigNum = BigNumber(balance); + const networkFeeBigNum = BigNumber(networkFee); - if (token.id !== "AR") maxAmountToken = balance; + let maxAmountToken = balanceBigNum.minus(networkFeeBigNum); - return maxAmountToken * (qtyMode === "fiat" ? price : 1); + if (token.id !== "AR") maxAmountToken = balanceBigNum; + + return maxAmountToken.multipliedBy(qtyMode === "fiat" ? price : 1); }, [balance, token, networkFee, qtyMode]); // switch back to token qty mode if the // token does not have a fiat price useEffect(() => { - if (!!price) return; + if (!!+price) return; setQtyMode("token"); }, [price]); // switch between fiat qty mode / token qty mode function switchQtyMode() { - if (!price) return; + if (!+price) return; setQty(secondaryQty.toFixed(4)); setQtyMode((val) => (val === "fiat" ? "token" : "fiat")); } // invalid qty const invalidQty = useMemo(() => { - const parsedQty = Number(qty); + const parsedQty = BigNumber(qty); - if (Number.isNaN(parsedQty)) return true; + if (parsedQty.isNaN()) return true; - return parsedQty < 0 || parsedQty > max; - }, [qty, max]); + return parsedQty.isLessThan(0) || max.isLessThan(parsedQty); + }, [qty, max.toString()]); // show token selector const [showTokenSelector, setShownTokenSelector] = useState(false); @@ -390,11 +394,11 @@ export default function Send({ id }: Props) { await TempTransactionStorage.set("send", { networkFee, - qty: qtyMode === "fiat" ? secondaryQty : qty, + qty: qtyMode === "fiat" ? secondaryQty.toFixed() : qty, token, recipient, - estimatedFiat: qtyMode === "fiat" ? qty : secondaryQty, - estimatedNetworkFee: parseFloat(networkFee) * price, + estimatedFiat: qtyMode === "fiat" ? qty : secondaryQty.toFixed(), + estimatedNetworkFee: BigNumber(networkFee).multipliedBy(price).toFixed(), message: message.state, qtyMode, isAo @@ -490,7 +494,7 @@ export default function Send({ id }: Props) { fullWidth icon={ - {!!price && ( + {!!+price && ( USD/ @@ -509,7 +513,7 @@ export default function Send({ id }: Props) { /> - {!!price && !isAo ? ( + {!!+price && !isAo ? ( ≈ {qtyMode === "fiat" @@ -597,7 +601,7 @@ export default function Send({ id }: Props) { id={token.id} ticker={token.Ticker} divisibility={token.Denomination} - balance={Number(token.balance)} + balance={token.balance || "0"} onClick={() => updateSelectedToken(token.id)} /> ))} diff --git a/src/routes/popup/send/recipient.tsx b/src/routes/popup/send/recipient.tsx index f06ee9d9d..fdc7677eb 100644 --- a/src/routes/popup/send/recipient.tsx +++ b/src/routes/popup/send/recipient.tsx @@ -123,7 +123,7 @@ export default function Recipient({ tokenID, qty, message }: Props) { JSON.stringify({ function: "transfer", target: target, - qty + qty: +qty }) ); addTransferTags(tx); @@ -224,6 +224,6 @@ const Address = styled(Text).attrs({ interface Props { tokenID: string; - qty: number; + qty: string; message?: string; } diff --git a/src/routes/popup/token/[id].tsx b/src/routes/popup/token/[id].tsx index 369d3148d..f433ec42f 100644 --- a/src/routes/popup/token/[id].tsx +++ b/src/routes/popup/token/[id].tsx @@ -96,11 +96,14 @@ export default function Asset({ id }: Props) { const tokenBalance = useMemo(() => { if (!state) return "0"; - const val = balanceToFractioned(state.balances?.[activeAddress], { - id, - decimals: state.decimals, - divisibility: state.divisibility - }); + const val = balanceToFractioned( + String(state.balances?.[activeAddress] || "0"), + { + id, + decimals: state.decimals, + divisibility: state.divisibility + } + ); return formatTokenBalance(val); }, [id, state, activeAddress]); @@ -180,13 +183,16 @@ export default function Asset({ id }: Props) { return `?? ${currency.toUpperCase()}`; } - const bal = balanceToFractioned(state.balances?.[activeAddress], { - id, - decimals: state.decimals, - divisibility: state.divisibility - }); + const bal = balanceToFractioned( + String(state.balances?.[activeAddress] || "0"), + { + id, + decimals: state.decimals, + divisibility: state.divisibility + } + ); - return formatFiatBalance(price * bal, currency); + return formatFiatBalance(bal.multipliedBy(price), currency); }, [id, state, activeAddress, price, currency]); const [priceWarningShown, setPriceWarningShown] = useStorage({ diff --git a/src/routes/popup/tokens.tsx b/src/routes/popup/tokens.tsx index db07fc7ce..5a2522210 100644 --- a/src/routes/popup/tokens.tsx +++ b/src/routes/popup/tokens.tsx @@ -153,7 +153,7 @@ export default function Tokens() { defaultLogo={token?.Logo} id={token.id} ticker={token.Ticker} - balance={Number(token.balance || null)} + balance={token.balance || "0"} onClick={(e) => { e.preventDefault(); handleTokenClick(token.id); @@ -172,7 +172,7 @@ export default function Tokens() { defaultLogo={token?.Logo} id={token.id} ticker={token.Ticker} - balance={Number(token.balance || null)} + balance={token.balance || "0"} onClick={(e) => { e.preventDefault(); handleTokenClick(token.id); diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index a8552d865..84533fed0 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -34,6 +34,7 @@ import { import { TempTransactionStorage } from "~utils/storage"; import { useContact } from "~contacts/hooks"; import { EventType, PageType, trackEvent, trackPage } from "~utils/analytics"; +import BigNumber from "bignumber.js"; // pull contacts and check if to address is in contacts @@ -174,9 +175,9 @@ export default function Transaction({ id: rawId, gw, message }: Props) { // transaction price const fiatPrice = useMemo(() => { - const transactionQty = Number(transaction?.quantity?.ar || "0"); + const transactionQty = BigNumber(transaction?.quantity?.ar || "0"); - return transactionQty * arPrice; + return transactionQty.multipliedBy(arPrice); }, [transaction, arPrice]); // get content type @@ -281,7 +282,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) {
{!ao.isAo - ? formatTokenBalance(Number(transaction.quantity.ar)) + ? formatTokenBalance(transaction.quantity.ar || "0") : 0} {/* NEEDS TO BE DYNAMIC */} {!ao.isAo && AR} diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index df53afa3f..4da9f89bf 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -74,7 +74,7 @@ export function useAo() { export function useAoTokens(): [TokenInfoWithBalance[], boolean] { const [tokens, setTokens] = useState([]); - const [balances, setBalances] = useState<{ id: string; balance: number }[]>( + const [balances, setBalances] = useState<{ id: string; balance: string }[]>( [] ); const [loading, setLoading] = useState(true); @@ -118,7 +118,7 @@ export function useAoTokens(): [TokenInfoWithBalance[], boolean] { setTokens( aoTokens.map((aoToken) => ({ id: aoToken.processId, - balance: 0, + balance: "0", Ticker: aoToken.Ticker, Name: aoToken.Name, Denomination: Number(aoToken.Denomination || 0), @@ -141,15 +141,13 @@ export function useAoTokens(): [TokenInfoWithBalance[], boolean] { const balances = await Promise.all( tokens.map(async ({ id }) => { try { - const balance = Number( - await timeoutPromise( - (async () => { - const aoToken = await Token(id); - const balance = await aoToken.getBalance(activeAddress); - return balance; - })(), - 6000 - ) + const balance = await timeoutPromise( + (async () => { + const aoToken = await Token(id); + const balance = await aoToken.getBalance(activeAddress); + return balance.toString(); + })(), + 6000 ); return { id, @@ -171,7 +169,7 @@ export function useAoTokens(): [TokenInfoWithBalance[], boolean] { } export function useAoTokensCache(): [TokenInfoWithBalance[], boolean] { - const [balances, setBalances] = useState<{ id: string; balance: number }[]>( + const [balances, setBalances] = useState<{ id: string; balance: string }[]>( [] ); const [loading, setLoading] = useState(true); @@ -223,7 +221,7 @@ export function useAoTokensCache(): [TokenInfoWithBalance[], boolean] { ...token, id: token.processId, Denomination: Number(token.Denomination || 0), - balance: 0 + balance: "0" })); }, [aoTokensCache, aoTokensIds, activeAddress, aoTokens]); @@ -247,15 +245,13 @@ export function useAoTokensCache(): [TokenInfoWithBalance[], boolean] { const balances = await Promise.all( aoTokensToAdd.map(async (token) => { try { - const balance = Number( - await timeoutPromise( - (async () => { - const aoToken = await Token(token.id); - const balance = await aoToken.getBalance(activeAddress); - return balance; - })(), - 6000 - ) + const balance = await timeoutPromise( + (async () => { + const aoToken = await Token(token.id); + const balance = await aoToken.getBalance(activeAddress); + return balance.toString(); + })(), + 6000 ); return { id: token.id, @@ -361,5 +357,5 @@ export interface TokenInfo { } export interface TokenInfoWithBalance extends TokenInfo { id: string; - balance: number; + balance: string; } diff --git a/src/tokens/currency.ts b/src/tokens/currency.ts index a54b7d028..a2acdcd2d 100644 --- a/src/tokens/currency.ts +++ b/src/tokens/currency.ts @@ -1,19 +1,28 @@ +import { Quantity } from "ao-tokens"; +import BigNumber from "bignumber.js"; + /** Token formatting config */ -export const tokenConfig: Intl.NumberFormatOptions = { +export const tokenConfig: Intl.NumberFormatOptions & + BigIntToLocaleStringOptions = { maximumFractionDigits: 2 }; /** * Format token balance */ -export function formatTokenBalance(balance: string | number) { - const val = typeof balance === "string" ? parseFloat(balance) : balance; +export function formatTokenBalance( + balance: string | number | BigNumber | Quantity +) { + const val = Quantity.isQuantity(balance) + ? balance + : new Quantity("0", 20n).fromString(balance.toString()); return val.toLocaleString(undefined, tokenConfig); } /** Fiat formatting config */ -export const fiatConfig: Intl.NumberFormatOptions = { +export const fiatConfig: Intl.NumberFormatOptions & + BigIntToLocaleStringOptions = { style: "currency", currencyDisplay: "symbol", maximumFractionDigits: 2 @@ -22,8 +31,13 @@ export const fiatConfig: Intl.NumberFormatOptions = { /** * Format fiat balance */ -export function formatFiatBalance(balance: string | number, currency?: string) { - const val = typeof balance === "string" ? parseFloat(balance) : balance; +export function formatFiatBalance( + balance: string | number | BigNumber | Quantity, + currency?: string +) { + const val = Quantity.isQuantity(balance) + ? balance + : new Quantity("0", 20n).fromString(balance.toString()); return val.toLocaleString(undefined, { ...fiatConfig, @@ -94,16 +108,16 @@ export function getDecimals(cfg: DivisibilityOrDecimals) { * See the specs at specs.arweave.dev */ export function balanceToFractioned( - balance: number, + balance: string, cfg: DivisibilityOrDecimals ) { - if (!balance) return 0; + if (!balance) return BigNumber("0"); // parse decimals const decimals = getDecimals(cfg); // divide base balance using the decimals - return balance / Math.pow(10, decimals); + return BigNumber(balance).shiftedBy(-decimals); } /** @@ -120,34 +134,11 @@ export function fractionedToBalance( // parse decimals const decimals = getDecimals(cfg); - if (tokenType !== "WARP") { - // Convert balance to a string to avoid precision issues - const balanceStr = balance.toString(); - - // Split the balance into integer and fractional parts - const [integerPart, fractionalPart = ""] = balanceStr.split("."); - - // Calculate the number of fractional digits - const fractionalDigits = fractionalPart.length; + const balanceBigNum = BigNumber(balance).shiftedBy(decimals); - let combined: string; - if (fractionalDigits > decimals) { - // Truncate the fractional part to fit the specified number of decimals - const truncatedFractionalPart = fractionalPart.slice(0, decimals); - combined = integerPart + truncatedFractionalPart; - } else { - // Combine integer and fractional parts into a single string - combined = integerPart + fractionalPart.padEnd(decimals, "0"); - } - - // Convert the combined string to a BigInt - const result = BigInt(combined); - - // Return the result as a string to avoid exponential notation - return result.toString(); - } else { - return (+balance * Math.pow(10, decimals)).toString(); - } + return tokenType === "WARP" + ? balanceBigNum.toFixed() + : balanceBigNum.toFixed(0); } export interface DivisibilityOrDecimals { diff --git a/src/tokens/index.ts b/src/tokens/index.ts index 61a4e1c17..53b765ea6 100644 --- a/src/tokens/index.ts +++ b/src/tokens/index.ts @@ -21,7 +21,7 @@ export const defaultTokens: Token[] = [ name: "U", ticker: "U", type: "asset", - balance: 0, + balance: "0", divisibility: 1e6, defaultLogo: "J3WXX4OGa6wP5E9oLhNyqlN4deYI7ARjrd5se740ftE", dre: "https://dre-u.warp.cc" @@ -31,7 +31,7 @@ export const defaultTokens: Token[] = [ name: "STAMP Protocol", ticker: "$STAMP", type: "asset", - balance: 0, + balance: "0", dre: "https://dre-u.warp.cc" }, { @@ -39,7 +39,7 @@ export const defaultTokens: Token[] = [ name: "ArDrive", ticker: "ARDRIVE", type: "asset", - balance: 0, + balance: "0", defaultLogo: "tN4vheZxrAIjqCfbs3MDdWTXg8a_57JUNyoqA4uwr1k", dre: "https://dre-4.warp.cc" } @@ -96,7 +96,7 @@ export async function addToken(id: string, type: TokenType, dre?: string) { name: state.name, ticker: state.ticker, type, - balance: state.balances[activeAddress] || 0, + balance: String(state.balances[activeAddress] || 0), divisibility: state.divisibility, decimals: state.decimals, defaultLogo: settings.get("communityLogo") as string, @@ -194,7 +194,7 @@ export function useTokens() { // parse settings const settings = getSettings(state); - token.balance = state.balances[activeAddress] || 0; + token.balance = String(state.balances[activeAddress] || 0); token.divisibility = state.divisibility; token.decimals = state.decimals; token.defaultLogo = settings.get("communityLogo"); diff --git a/src/tokens/token.ts b/src/tokens/token.ts index d4669f174..8aa30bde5 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -15,6 +15,7 @@ import { type Gateway } from "~gateways/gateway"; import { ExtensionStorage } from "~utils/storage"; import { defaultTokens } from "~tokens"; import { defaultAoTokens, type TokenInfo } from "./aoTokens/ao"; +import BigNumber from "bignumber.js"; export interface Token { id: string; @@ -23,7 +24,7 @@ export interface Token { type: TokenType; gateway?: Gateway; dre?: string; - balance: number | null; + balance: string | null; divisibility?: number; decimals?: number; defaultLogo?: string; @@ -218,12 +219,12 @@ export function parseInteractions( // interaction data let type: TokenInteractionType = "interaction"; - let qty = Number(tx.node.quantity.ar); + let qty = tx.node.quantity.ar; let otherAddress: string; if (input.function === "transfer") { type = (recipient === activeAddress && "in") || "out"; - qty = balanceToFractioned(Number(input.qty), fractionsCfg); + qty = balanceToFractioned(input.qty, fractionsCfg).toFixed(); otherAddress = (recipient === activeAddress && tx.node.owner.address) || recipient; } diff --git a/src/utils/events.ts b/src/utils/events.ts index 731551c91..49af899a4 100644 --- a/src/utils/events.ts +++ b/src/utils/events.ts @@ -1,6 +1,6 @@ import type { PermissionType } from "~applications/permissions"; import { ExtensionStorage } from "./storage"; -import { Gateway } from "~gateways/gateway"; +import { type Gateway } from "~gateways/gateway"; import type { EventType } from "mitt"; interface SecurityEvent { diff --git a/src/utils/format.ts b/src/utils/format.ts index ad467b7c7..826af7df6 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -1,3 +1,5 @@ +import BigNumber from "bignumber.js"; + /** * Get app URL from any link * @@ -80,27 +82,32 @@ export const formatSettingName = (name: string) => { * @param value The numeric value to abbreviate, as a number or string. * @returns The abbreviated string representation of the number. */ -export function abbreviateNumber(value: number): string { +export function abbreviateNumber(value: number | string | BigNumber): string { let suffix = ""; + + if (!BigNumber.isBigNumber(value)) { + value = BigNumber(value); + } + let abbreviatedValue = value; - if (value >= 1e12) { + if (value.isGreaterThanOrEqualTo(1e12)) { // Trillions suffix = "T"; - abbreviatedValue = value / 1e12; - } else if (value >= 1e9) { + abbreviatedValue = value.dividedBy(1e12); + } else if (value.isGreaterThanOrEqualTo(1e9)) { // Billions suffix = "B"; - abbreviatedValue = value / 1e9; - } else if (value >= 1e6) { + abbreviatedValue = value.dividedBy(1e9); + } else if (value.isGreaterThanOrEqualTo(1e6)) { // Millions suffix = "M"; - abbreviatedValue = value / 1e6; + abbreviatedValue = value.dividedBy(1e6); } - if (abbreviatedValue % 1 === 0) { - return `${abbreviatedValue}${suffix}`; + if (abbreviatedValue.mod(1).isEqualTo(0)) { + return `${abbreviatedValue.toFixed()}${suffix}`; } else { - return `${abbreviatedValue.toFixed(1)}${suffix}`; + return `${abbreviatedValue.toFixed(2)}${suffix}`; } } diff --git a/src/wallets/hooks.ts b/src/wallets/hooks.ts index d7274146e..6bafbcd58 100644 --- a/src/wallets/hooks.ts +++ b/src/wallets/hooks.ts @@ -9,6 +9,7 @@ import { findGateway } from "~gateways/wayfinder"; import type { HardwareApi } from "./hardware"; import type { StoredWallet } from "~wallets"; import Arweave from "arweave"; +import BigNumber from "bignumber.js"; /** * Wallets with details hook @@ -110,7 +111,7 @@ export function useBalance() { }); // balance in AR - const [balance, setBalance] = useState(0); + const [balance, setBalance] = useState(BigNumber("0")); useEffect(() => { (async () => { @@ -122,7 +123,7 @@ export function useBalance() { // fetch balance const winstonBalance = await arweave.wallets.getBalance(activeAddress); - setBalance(Number(arweave.ar.winstonToAr(winstonBalance))); + setBalance(BigNumber(arweave.ar.winstonToAr(winstonBalance))); })(); }, [activeAddress]); diff --git a/yarn.lock b/yarn.lock index ca109d58c..bd2da2c4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4602,10 +4602,10 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -ao-tokens@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/ao-tokens/-/ao-tokens-0.0.3.tgz#55f3e74a1931d842f59d9c6f947eab302434c0b4" - integrity sha512-M8Wzq9VIRYNVKhgcu+UaiKDe/jftmoPbZXGLZtoBKEv4XKdbBBcSapHwYk7Lx2k4Kyz2zTacfDv1SvGGO+PFyQ== +ao-tokens@^0.0.4: + version "0.0.4" + resolved "https://registry.npmjs.org/ao-tokens/-/ao-tokens-0.0.4.tgz#bf4208fd6d19b91d38cba51ceb7ae6439f83b415" + integrity sha512-WUifVHG2kpl//uEkZ3iBe/q+oiU2mj+sXpaIkvBOSVtL7nuFUUMnqdwO8aAWr8e8CuxPaasbz3TeLlVoJGHVgQ== aproba@^2.0.0: version "2.0.0" @@ -4979,7 +4979,7 @@ bignumber.js@9.1.1: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== -bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.0.2: +bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.0.2, bignumber.js@^9.1.2: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== From 3c14e77e6e95d5da335d8932d8dd63c13ddab188 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 4 Jun 2024 17:46:19 +0545 Subject: [PATCH 02/16] refactor: Make sure allowance is stored as string & operated as bignumber --- src/api/modules/decrypt/decrypt.foreground.ts | 2 +- src/api/modules/dispatch/allowance.ts | 7 +++-- .../modules/dispatch/dispatch.background.ts | 3 +- src/api/modules/sign/allowance.ts | 24 +++++++++----- src/api/modules/sign/sign.background.ts | 3 +- src/api/modules/sign/utils.ts | 2 +- src/applications/allowance.ts | 16 +++++++--- src/applications/application.ts | 16 ++++++++-- src/components/auth/App.tsx | 2 +- src/components/dashboard/SignSettings.tsx | 2 +- .../dashboard/subsettings/AppSettings.tsx | 4 +-- src/components/popup/Token.tsx | 11 ++----- src/routes/auth/allowance.tsx | 31 +++++++++++++------ src/routes/auth/subscription.tsx | 5 +-- src/routes/auth/token.tsx | 2 +- .../subscriptions/subscriptionDetails.tsx | 7 +++-- .../subscriptions/subscriptionPayment.tsx | 5 ++- src/subscriptions/payments.ts | 9 ++++-- src/tokens/token.ts | 1 - src/utils/analytics.ts | 9 ++++-- src/utils/format.ts | 6 ++-- 21 files changed, 109 insertions(+), 58 deletions(-) diff --git a/src/api/modules/decrypt/decrypt.foreground.ts b/src/api/modules/decrypt/decrypt.foreground.ts index 04d9f3232..4e74944a7 100644 --- a/src/api/modules/decrypt/decrypt.foreground.ts +++ b/src/api/modules/decrypt/decrypt.foreground.ts @@ -1,4 +1,4 @@ -import { TransformFinalizer } from "~api/foreground"; +import { type TransformFinalizer } from "~api/foreground"; import type { ModuleFunction } from "~api/module"; const foreground: ModuleFunction = (data, options) => { diff --git a/src/api/modules/dispatch/allowance.ts b/src/api/modules/dispatch/allowance.ts index 284152037..2f984f11a 100644 --- a/src/api/modules/dispatch/allowance.ts +++ b/src/api/modules/dispatch/allowance.ts @@ -1,5 +1,5 @@ import { freeDecryptedWallet } from "~wallets/encryption"; -import type { Allowance } from "~applications/allowance"; +import type { Allowance, AllowanceBigNumber } from "~applications/allowance"; import type { ModuleAppData } from "~api/background"; import { defaultGateway } from "~gateways/gateway"; import type { JWKInterface } from "warp-contracts"; @@ -8,6 +8,7 @@ import { signAuth } from "../sign/sign_auth"; import Arweave from "arweave"; import type { DataItem } from "arbundles"; import type Transaction from "arweave/web/lib/transaction"; +import type BigNumber from "bignumber.js"; /** * Ensure allowance for dispatch @@ -15,9 +16,9 @@ import type Transaction from "arweave/web/lib/transaction"; export async function ensureAllowanceDispatch( dataEntry: DataItem | Transaction, appData: ModuleAppData, - allowance: Allowance, + allowance: AllowanceBigNumber, keyfile: JWKInterface, - price: number + price: number | BigNumber ) { const arweave = new Arweave(defaultGateway); diff --git a/src/api/modules/dispatch/dispatch.background.ts b/src/api/modules/dispatch/dispatch.background.ts index 442dafc1c..6716b1d05 100644 --- a/src/api/modules/dispatch/dispatch.background.ts +++ b/src/api/modules/dispatch/dispatch.background.ts @@ -19,6 +19,7 @@ import browser from "webextension-polyfill"; import Arweave from "arweave"; import { ensureAllowanceDispatch } from "./allowance"; import { updateAllowance } from "../sign/allowance"; +import BigNumber from "bignumber.js"; type ReturnType = { arConfetti: string | false; @@ -124,7 +125,7 @@ const background: ModuleFunction = async ( transaction.addTag(arcTag.name, arcTag.value); } // calculate price - const price = +transaction.reward + parseInt(transaction.quantity); + const price = BigNumber(transaction.reward).plus(transaction.quantity); // ensure allowance await ensureAllowanceDispatch( diff --git a/src/api/modules/sign/allowance.ts b/src/api/modules/sign/allowance.ts index 347dfe273..4e33f771d 100644 --- a/src/api/modules/sign/allowance.ts +++ b/src/api/modules/sign/allowance.ts @@ -1,6 +1,11 @@ -import { type Allowance, defaultAllowance } from "~applications/allowance"; +import { + type Allowance, + type AllowanceBigNumber, + defaultAllowance +} from "~applications/allowance"; import Application from "~applications/application"; import authenticate from "../connect/auth"; +import BigNumber from "bignumber.js"; /** * Get allowance for an app @@ -23,7 +28,10 @@ export async function getAllowance(tabURL: string) { * @param price Price to update the allowance spent amount * with (quantity + reward) */ -export async function updateAllowance(tabURL: string, price: number) { +export async function updateAllowance( + tabURL: string, + price: number | BigNumber +) { // construct application const app = new Application(tabURL); @@ -33,7 +41,9 @@ export async function updateAllowance(tabURL: string, price: number) { allowance: { ...defaultAllowance, ...allowance, - spent: (allowance?.spent || 0) + price + spent: BigNumber(allowance?.spent || 0) + .plus(price) + .toString() } }; }); @@ -49,15 +59,15 @@ export async function updateAllowance(tabURL: string, price: number) { * @param price Price to check the allowance for (quantity + reward) */ export async function allowanceAuth( - allowance: Allowance, + allowance: AllowanceBigNumber, tabURL: string, - price: number + price: number | BigNumber ) { // spent amount after this transaction - const total = allowance.spent + price; + const total = allowance.spent.plus(price); // check if the price goes over the allowed total limit - const hasEnoughAllowance = allowance.limit >= total; + const hasEnoughAllowance = total.isLessThanOrEqualTo(allowance.limit); // if the allowance is enough, return if (hasEnoughAllowance) return; diff --git a/src/api/modules/sign/sign.background.ts b/src/api/modules/sign/sign.background.ts index cebe145cd..234225935 100644 --- a/src/api/modules/sign/sign.background.ts +++ b/src/api/modules/sign/sign.background.ts @@ -22,6 +22,7 @@ import Application from "~applications/application"; import browser from "webextension-polyfill"; import Arweave from "arweave"; import { EventType, trackDirect } from "~utils/analytics"; +import BigNumber from "bignumber.js"; const background: ModuleFunction = async ( appData, @@ -83,7 +84,7 @@ const background: ModuleFunction = async ( // validate the user's allowance for this app // if it is not enough, we need the user to // raise it or cancel the transaction - const price = +transaction.reward + parseInt(transaction.quantity); + const price = BigNumber(transaction.reward).plus(transaction.quantity); // get allowance const allowance = await getAllowance(appData.appURL); diff --git a/src/api/modules/sign/utils.ts b/src/api/modules/sign/utils.ts index 5e2befd8c..a5687b22f 100644 --- a/src/api/modules/sign/utils.ts +++ b/src/api/modules/sign/utils.ts @@ -67,7 +67,7 @@ export async function calculateReward({ reward }: Transaction) { * @param type Signed transaction type */ export async function signNotification( - price: number, + price: number | BigNumber, id: string, appURL: string, type: "sign" | "dispatch" = "sign" diff --git a/src/applications/allowance.ts b/src/applications/allowance.ts index 74f111014..cf8191fa0 100644 --- a/src/applications/allowance.ts +++ b/src/applications/allowance.ts @@ -1,11 +1,19 @@ +import type BigNumber from "bignumber.js"; + export interface Allowance { enabled: boolean; - limit: number; // in winstons - spent: number; // in winstons + limit: string; // in winstons + spent: string; // in winstons +} + +export interface AllowanceBigNumber { + enabled: boolean; + limit: BigNumber; // in winstons + spent: BigNumber; // in winstons } export const defaultAllowance: Allowance = { enabled: true, - limit: 1000000000000, - spent: 0 + limit: "1000000000000", + spent: "0" }; diff --git a/src/applications/application.ts b/src/applications/application.ts index 6b64f088d..bd7bfc05f 100644 --- a/src/applications/application.ts +++ b/src/applications/application.ts @@ -1,9 +1,14 @@ import { getMissingPermissions, type PermissionType } from "./permissions"; -import { type Allowance, defaultAllowance } from "./allowance"; +import { + type Allowance, + type AllowanceBigNumber, + defaultAllowance +} from "./allowance"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import type { Storage } from "@plasmohq/storage"; import { defaultGateway, type Gateway } from "~gateways/gateway"; +import BigNumber from "bignumber.js"; export const PREFIX = "app_"; export const defaultBundler = "https://turbo.ardrive.io"; @@ -135,10 +140,15 @@ export default class Application { /** * Allowance limit and spent qty */ - async getAllowance(): Promise { + async getAllowance(): Promise { const settings = await this.#getSettings(); - return settings.allowance || defaultAllowance; + const allowance = settings.allowance || defaultAllowance; + return { + enabled: allowance.enabled, + limit: BigNumber(allowance.limit), + spent: BigNumber(allowance.spent) + }; } /** diff --git a/src/components/auth/App.tsx b/src/components/auth/App.tsx index 77359130f..d0bb8be22 100644 --- a/src/components/auth/App.tsx +++ b/src/components/auth/App.tsx @@ -38,7 +38,7 @@ export default function App({ return winstonToArFormatted(allowance.limit); }, [allowance]); - function winstonToArFormatted(val: number) { + function winstonToArFormatted(val: string) { const arweave = new Arweave(defaultGateway); const arVal = arweave.ar.winstonToAr(val.toString()); diff --git a/src/components/dashboard/SignSettings.tsx b/src/components/dashboard/SignSettings.tsx index e51be7070..95b121fde 100644 --- a/src/components/dashboard/SignSettings.tsx +++ b/src/components/dashboard/SignSettings.tsx @@ -19,7 +19,7 @@ export default function SignSettings() { useEffect(() => { async function initializeSettings() { - const currentSetting = await ExtensionStorage.get( + const currentSetting = await ExtensionStorage.get( "setting_sign_notification" ); setSignSettingsState(currentSetting); diff --git a/src/components/dashboard/subsettings/AppSettings.tsx b/src/components/dashboard/subsettings/AppSettings.tsx index b9aa01e65..c2c3bf0e7 100644 --- a/src/components/dashboard/subsettings/AppSettings.tsx +++ b/src/components/dashboard/subsettings/AppSettings.tsx @@ -179,7 +179,7 @@ export default function AppSettings({ app, showTitle = false }: Props) { allowance: { ...defaultAllowance, ...val.allowance, - spent: 0 + spent: "0" } })) } @@ -201,7 +201,7 @@ export default function AppSettings({ app, showTitle = false }: Props) { allowance: { ...defaultAllowance, ...val.allowance, - limit: Number(arweave.ar.arToWinston(e.target.value)) + limit: arweave.ar.arToWinston(e.target.value) } })) } diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 49d353b96..004f49fea 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -50,11 +50,8 @@ export default function Token({ onClick, ...props }: Props) { const balance = useMemo(() => { const balanceToFormat = BigNumber(props.ao ? props.balance : fractBalance); - const isTinyBalance = - balanceToFormat.isGreaterThan(0) && balanceToFormat.isLessThan(1e-6); - const isSmallBalance = - balanceToFormat.isLessThan(1) && - balanceToFormat.isGreaterThanOrEqualTo(1e-6); + const isTinyBalance = balanceToFormat.gt(0) && balanceToFormat.lt(1e-6); + const isSmallBalance = balanceToFormat.lt(1) && balanceToFormat.gte(1e-6); const formattedBalance = isTinyBalance || isSmallBalance @@ -63,9 +60,7 @@ export default function Token({ onClick, ...props }: Props) { setTotalBalance(balanceToFormat.toFixed()); - setShowTooltip( - balanceToFormat.isGreaterThanOrEqualTo(1_000_000) || isTinyBalance - ); + setShowTooltip(balanceToFormat.gte(1_000_000) || isTinyBalance); return isSmallBalance ? formattedBalance diff --git a/src/routes/auth/allowance.tsx b/src/routes/auth/allowance.tsx index 6ee82decd..704987dd6 100644 --- a/src/routes/auth/allowance.tsx +++ b/src/routes/auth/allowance.tsx @@ -1,5 +1,9 @@ import { replyToAuthRequest, useAuthParams, useAuthUtils } from "~utils/auth"; -import { type Allowance, defaultAllowance } from "~applications/allowance"; +import { + type Allowance, + type AllowanceBigNumber, + defaultAllowance +} from "~applications/allowance"; import Application, { type AppInfo } from "~applications/application"; import { checkPassword } from "~wallets/auth"; import { useEffect, useState } from "react"; @@ -18,6 +22,7 @@ import App from "~components/auth/App"; import Arweave from "arweave"; import styled from "styled-components"; import { defaultGateway } from "~gateways/gateway"; +import BigNumber from "bignumber.js"; export default function Allowance() { const arweave = new Arweave(defaultGateway); @@ -35,7 +40,7 @@ export default function Allowance() { const limitInput = useInput(); // allowance - const [allowance, setAllowance] = useState(); + const [allowance, setAllowance] = useState(); useEffect(() => { if (!allowance) return; @@ -93,28 +98,32 @@ export default function Allowance() { // update allowance await app.updateSettings(() => { - const updatedAllowance: Allowance = { + const updatedAllowance: AllowanceBigNumber = { ...defaultAllowance, ...allowance }; if (limitInput.state !== "") { - const limitInputState = Number( + const limitInputState = BigNumber( arweave.ar.arToWinston(limitInput.state) ); if ( - limitInputState !== (allowance?.limit || 0) && - limitInputState > 0 + !limitInputState.eq(allowance?.limit || 0) && + limitInputState.gt(0) ) { updatedAllowance.limit = limitInputState; } } - updatedAllowance.spent = 0; + updatedAllowance.spent = BigNumber("0"); return { - allowance: updatedAllowance + allowance: { + enabled: updatedAllowance.enabled, + limit: updatedAllowance.limit.toString(), + spent: updatedAllowance.spent.toString() + } }; }); @@ -138,7 +147,11 @@ export default function Allowance() { appName={appData?.name || params?.url} appUrl={params?.url} appIcon={appData?.logo} - allowance={allowance} + allowance={{ + enabled: allowance.enabled, + limit: allowance.limit.toString(), + spent: allowance.spent.toString() + }} />
diff --git a/src/routes/auth/subscription.tsx b/src/routes/auth/subscription.tsx index 63f04ad1b..8d8cba3da 100644 --- a/src/routes/auth/subscription.tsx +++ b/src/routes/auth/subscription.tsx @@ -40,6 +40,7 @@ import { getPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import { EventType, trackEvent } from "~utils/analytics"; import { handleSubscriptionPayment } from "~subscriptions/payments"; +import BigNumber from "bignumber.js"; export default function Subscription() { // connect params @@ -54,13 +55,13 @@ export default function Subscription() { // get auth utils const { closeWindow, cancel } = useAuthUtils("subscription", params?.authID); const theme = useTheme(); - const [price, setPrice] = useState(); + const [price, setPrice] = useState(); useEffect(() => { async function fetchArPrice() { const arPrice = await getPrice("arweave", currency); if (arPrice) { - setPrice(arPrice * params.subscriptionFeeAmount); + setPrice(BigNumber(arPrice).multipliedBy(params.subscriptionFeeAmount)); } } diff --git a/src/routes/auth/token.tsx b/src/routes/auth/token.tsx index 3373bc60b..8f45c7408 100644 --- a/src/routes/auth/token.tsx +++ b/src/routes/auth/token.tsx @@ -28,7 +28,7 @@ import browser from "webextension-polyfill"; import Title from "~components/popup/Title"; import Head from "~components/popup/Head"; import styled from "styled-components"; -import { Gateway } from "~gateways/gateway"; +import { type Gateway } from "~gateways/gateway"; import { concatGatewayURL } from "~gateways/utils"; import { findGateway, useGateway } from "~gateways/wayfinder"; diff --git a/src/routes/popup/subscriptions/subscriptionDetails.tsx b/src/routes/popup/subscriptions/subscriptionDetails.tsx index 9b88da365..3111bf10b 100644 --- a/src/routes/popup/subscriptions/subscriptionDetails.tsx +++ b/src/routes/popup/subscriptions/subscriptionDetails.tsx @@ -35,6 +35,7 @@ import { useHistory } from "~utils/hash_router"; import { getPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import { PageType, trackPage } from "~utils/analytics"; +import BigNumber from "bignumber.js"; interface Props { id?: string; @@ -48,7 +49,7 @@ export default function SubscriptionDetails({ id }: Props) { const [push, goBack] = useHistory(); const { setToast } = useToasts(); - const [price, setPrice] = useState(); + const [price, setPrice] = useState(); const [currency] = useSetting("currency"); const [color, setColor] = useState(""); @@ -135,7 +136,9 @@ export default function SubscriptionDetails({ id }: Props) { setAutopayChecked(!!subscription.applicationAllowance); const arPrice = await getPrice("arweave", currency); if (arPrice) { - setPrice(arPrice * subscription.subscriptionFeeAmount); + setPrice( + BigNumber(arPrice).multipliedBy(subscription.subscriptionFeeAmount) + ); } } catch (error) { console.error("Error fetching subscription data:", error); diff --git a/src/routes/popup/subscriptions/subscriptionPayment.tsx b/src/routes/popup/subscriptions/subscriptionPayment.tsx index 01551473f..e453c6776 100644 --- a/src/routes/popup/subscriptions/subscriptionPayment.tsx +++ b/src/routes/popup/subscriptions/subscriptionPayment.tsx @@ -22,6 +22,7 @@ import { ButtonV2, useToasts } from "@arconnect/components"; import { useHistory } from "~utils/hash_router"; import { getPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; +import BigNumber from "bignumber.js"; export default function SubscriptionPayment({ id }: { id: string }) { const [subData, setSubData] = useState(null); @@ -125,7 +126,9 @@ export default function SubscriptionPayment({ id }: { id: string }) { const arPrice = await getPrice("arweave", currency); if (arPrice) { setPrice( - (arPrice * subData.subscriptionFeeAmount).toFixed(2).toString() + BigNumber(arPrice) + .multipliedBy(subData.subscriptionFeeAmount) + .toFixed(2) ); } } diff --git a/src/subscriptions/payments.ts b/src/subscriptions/payments.ts index 2fc01eba9..cfecff97b 100644 --- a/src/subscriptions/payments.ts +++ b/src/subscriptions/payments.ts @@ -15,6 +15,7 @@ import { updateSubscription as statusUpdateSubscription } from "~subscriptions"; import { isLocalWallet } from "~utils/assertions"; +import BigNumber from "bignumber.js"; export async function handleSubscriptionPayment( data: SubscriptionData, @@ -27,7 +28,9 @@ export async function handleSubscriptionPayment( } if ( - data.applicationAllowance < data.subscriptionFeeAmount && + BigNumber(data.applicationAllowance).isLessThan( + data.subscriptionFeeAmount + ) && !initialPayment ) { await statusUpdateSubscription( @@ -76,10 +79,10 @@ export const prepare = async ( const winstonBalance = await arweave.wallets.getBalance(activeAddress); - const balance = Number(arweave.ar.winstonToAr(winstonBalance)); + const balance = arweave.ar.winstonToAr(winstonBalance); // Check if the subscription fee exceeds the user's balance - if (data.subscriptionFeeAmount > balance) { + if (BigNumber(data.subscriptionFeeAmount).gt(balance)) { throw new Error("Subscription fee amount exceeds wallet balance"); } diff --git a/src/tokens/token.ts b/src/tokens/token.ts index 8aa30bde5..58c1be9de 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -15,7 +15,6 @@ import { type Gateway } from "~gateways/gateway"; import { ExtensionStorage } from "~utils/storage"; import { defaultTokens } from "~tokens"; import { defaultAoTokens, type TokenInfo } from "./aoTokens/ao"; -import BigNumber from "bignumber.js"; export interface Token { id: string; diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts index 18cf30d51..652e744bc 100644 --- a/src/utils/analytics.ts +++ b/src/utils/analytics.ts @@ -6,6 +6,7 @@ import Arweave from "arweave"; import { defaultGateway } from "~gateways/gateway"; import { v4 as uuid } from "uuid"; import browser, { type Alarms } from "webextension-polyfill"; +import BigNumber from "bignumber.js"; const PUBLIC_SEGMENT_WRITEKEY = "J97E4cvSZqmpeEdiUQNC2IxS1Kw4Cwxm"; @@ -163,7 +164,7 @@ export const trackBalance = async (alarmInfo?: Alarms.Alarm) => { if (alarmInfo && !alarmInfo.name.startsWith("track-balance")) return; const wallets = await getWallets(); const arweave = new Arweave(defaultGateway); - let totalBalance = 0; + let totalBalance = BigNumber("0"); await Promise.all( wallets.map(async ({ address }) => { @@ -171,14 +172,16 @@ export const trackBalance = async (alarmInfo?: Alarms.Alarm) => { const balance = arweave.ar.winstonToAr( await arweave.wallets.getBalance(address) ); - totalBalance += Number(balance); + totalBalance = totalBalance.plus(balance); } catch (e) { console.log("invalid", e); } }) ); try { - await trackDirect(EventType.BALANCE, { totalBalance }); + await trackDirect(EventType.BALANCE, { + totalBalance: totalBalance.toString() + }); const timer = setToStartOfNextMonth(new Date()); browser.alarms.create("track-balance", { when: timer.getTime() diff --git a/src/utils/format.ts b/src/utils/format.ts index 826af7df6..d00e8bd61 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -91,15 +91,15 @@ export function abbreviateNumber(value: number | string | BigNumber): string { let abbreviatedValue = value; - if (value.isGreaterThanOrEqualTo(1e12)) { + if (value.gte(1e12)) { // Trillions suffix = "T"; abbreviatedValue = value.dividedBy(1e12); - } else if (value.isGreaterThanOrEqualTo(1e9)) { + } else if (value.gte(1e9)) { // Billions suffix = "B"; abbreviatedValue = value.dividedBy(1e9); - } else if (value.isGreaterThanOrEqualTo(1e6)) { + } else if (value.gte(1e6)) { // Millions suffix = "M"; abbreviatedValue = value.dividedBy(1e6); From c381c3f9b3814643239d8c81b3eb7b4b72a51612 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 4 Jun 2024 22:50:46 +0545 Subject: [PATCH 03/16] refactor: Use short function names --- src/api/modules/sign/allowance.ts | 2 +- src/api/modules/sign/utils.ts | 6 +++--- src/routes/popup/send/confirm.tsx | 2 +- src/routes/popup/send/index.tsx | 2 +- src/subscriptions/payments.ts | 4 +--- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/api/modules/sign/allowance.ts b/src/api/modules/sign/allowance.ts index 4e33f771d..1d1907d11 100644 --- a/src/api/modules/sign/allowance.ts +++ b/src/api/modules/sign/allowance.ts @@ -67,7 +67,7 @@ export async function allowanceAuth( const total = allowance.spent.plus(price); // check if the price goes over the allowed total limit - const hasEnoughAllowance = total.isLessThanOrEqualTo(allowance.limit); + const hasEnoughAllowance = total.lte(allowance.limit); // if the allowance is enough, return if (hasEnoughAllowance) return; diff --git a/src/api/modules/sign/utils.ts b/src/api/modules/sign/utils.ts index a5687b22f..f0a572b04 100644 --- a/src/api/modules/sign/utils.ts +++ b/src/api/modules/sign/utils.ts @@ -93,9 +93,9 @@ export async function signNotification( // format price let maximumFractionDigits = 0; - if (arPrice.isLessThan(0.1)) maximumFractionDigits = 6; - else if (arPrice.isLessThan(10)) maximumFractionDigits = 4; - else if (arPrice.isLessThan(1000)) maximumFractionDigits = 2; + if (arPrice.lt(0.1)) maximumFractionDigits = 6; + else if (arPrice.lt(10)) maximumFractionDigits = 4; + else if (arPrice.lt(1000)) maximumFractionDigits = 2; const formattedPrice = arPrice.toFormat(maximumFractionDigits); diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index 4e2b4d854..30840ac0f 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -112,7 +112,7 @@ export default function Confirm({ tokenID, qty, subscription }: Props) { const data: TransactionData = await TempTransactionStorage.get("send"); if (data) { const qty = BigNumber(data.qty); - if (qty.isLessThan(Number(allowance))) { + if (qty.lt(Number(allowance))) { setNeedsSign(false); } else { setNeedsSign(true); diff --git a/src/routes/popup/send/index.tsx b/src/routes/popup/send/index.tsx index ba94a6b7f..b2901076d 100644 --- a/src/routes/popup/send/index.tsx +++ b/src/routes/popup/send/index.tsx @@ -348,7 +348,7 @@ export default function Send({ id }: Props) { if (parsedQty.isNaN()) return true; - return parsedQty.isLessThan(0) || max.isLessThan(parsedQty); + return parsedQty.lt(0) || max.lt(parsedQty); }, [qty, max.toString()]); // show token selector diff --git a/src/subscriptions/payments.ts b/src/subscriptions/payments.ts index cfecff97b..38ca576ce 100644 --- a/src/subscriptions/payments.ts +++ b/src/subscriptions/payments.ts @@ -28,9 +28,7 @@ export async function handleSubscriptionPayment( } if ( - BigNumber(data.applicationAllowance).isLessThan( - data.subscriptionFeeAmount - ) && + BigNumber(data.applicationAllowance).lt(data.subscriptionFeeAmount) && !initialPayment ) { await statusUpdateSubscription( From 00850bddef61f347d9a5265d05b3aee58f059566 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Wed, 5 Jun 2024 13:17:25 +0545 Subject: [PATCH 04/16] refactor: Use bignumber for usePrice --- src/gateways/api.ts | 4 ++-- src/gateways/utils.ts | 3 +-- src/lib/redstone.ts | 5 +++-- src/routes/auth/token.tsx | 2 +- src/routes/popup/send/auth.tsx | 7 ++++--- src/routes/popup/send/confirm.tsx | 8 ++++---- src/routes/popup/token/[id].tsx | 2 +- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/gateways/api.ts b/src/gateways/api.ts index 755f99e42..56cbf2e67 100644 --- a/src/gateways/api.ts +++ b/src/gateways/api.ts @@ -1,7 +1,7 @@ -import GQLResultInterface from "ar-gql/dist/faces"; +import type GQLResultInterface from "ar-gql/dist/faces"; import { concatGatewayURL } from "./utils"; import { findGateway } from "./wayfinder"; -import { Gateway } from "./gateway"; +import { type Gateway } from "./gateway"; /** * Run a query on the Arweave Graphql API, diff --git a/src/gateways/utils.ts b/src/gateways/utils.ts index 6457e9b54..6f72f1880 100644 --- a/src/gateways/utils.ts +++ b/src/gateways/utils.ts @@ -1,4 +1,4 @@ -import { Gateway } from "~gateways/gateway"; +import { type Gateway } from "~gateways/gateway"; /** * Get the full gateway URL string, from the @@ -21,7 +21,6 @@ export function urlToGateway(url: string): Gateway { return { host: gatewayURL.hostname, port: gatewayURL.port === "" ? 443 : Number(gatewayURL.port), - // @ts-expect-error protocol: gatewayURL.protocol?.replace(":", "") || "http" }; } diff --git a/src/lib/redstone.ts b/src/lib/redstone.ts index 335033d2f..1e9421fed 100644 --- a/src/lib/redstone.ts +++ b/src/lib/redstone.ts @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import useSetting from "~settings/hook"; import redstone from "redstone-api"; import { getPrice } from "./coingecko"; +import BigNumber from "bignumber.js"; /** * Hook for the redstone token price API @@ -11,7 +12,7 @@ import { getPrice } from "./coingecko"; * @param opts Custom Redstone API "getPrice" options */ export function usePrice(symbol?: string, opts?: GetPriceOptions) { - const [price, setPrice] = useState(); + const [price, setPrice] = useState(); const [loading, setLoading] = useState(false); // currency setting @@ -37,7 +38,7 @@ export function usePrice(symbol?: string, opts?: GetPriceOptions) { const multiplier = currency !== "usd" ? await getPrice("usd", currency) : 1; - setPrice(res.value * multiplier); + setPrice(BigNumber(res.value).multipliedBy(multiplier)); } catch {} setLoading(false); diff --git a/src/routes/auth/token.tsx b/src/routes/auth/token.tsx index 8f45c7408..7b39d1e8c 100644 --- a/src/routes/auth/token.tsx +++ b/src/routes/auth/token.tsx @@ -206,7 +206,7 @@ export default function Token() { logo }} priceData={historicalPrices} - latestPrice={price} + latestPrice={+price} loading={loadingHistoricalPrices} > setPeriod(p)} /> From b7d7548541b0adf4f40d31bc2d96db60bdb8846f Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Mon, 10 Jun 2024 13:16:09 +0545 Subject: [PATCH 05/16] refactor: Use toFixed instead of toString --- src/components/popup/Token.tsx | 2 +- src/routes/popup/send/auth.tsx | 2 +- src/routes/popup/send/index.tsx | 2 +- src/utils/analytics.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 004f49fea..17493c25d 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -55,7 +55,7 @@ export default function Token({ onClick, ...props }: Props) { const formattedBalance = isTinyBalance || isSmallBalance - ? balanceToFormat.toString() + ? balanceToFormat.toFixed() : formatTokenBalance(balanceToFormat); setTotalBalance(balanceToFormat.toFixed()); diff --git a/src/routes/popup/send/auth.tsx b/src/routes/popup/send/auth.tsx index 3f31165c8..844f29002 100644 --- a/src/routes/popup/send/auth.tsx +++ b/src/routes/popup/send/auth.tsx @@ -209,7 +209,7 @@ export default function SendAuth({ tokenID }: Props) { console.log( "transaction amount:", - transactionAmount.toString(), + transactionAmount.toFixed(), "vs.", "sign allowance:", signAllowance diff --git a/src/routes/popup/send/index.tsx b/src/routes/popup/send/index.tsx index b2901076d..e5d6b1c1f 100644 --- a/src/routes/popup/send/index.tsx +++ b/src/routes/popup/send/index.tsx @@ -504,7 +504,7 @@ export default function Send({ id }: Props) { )} setQty(max.toString())} + onClick={() => setQty(max.toFixed())} > Max diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts index 0d6504519..27f6ce511 100644 --- a/src/utils/analytics.ts +++ b/src/utils/analytics.ts @@ -181,7 +181,7 @@ export const trackBalance = async (alarmInfo?: Alarms.Alarm) => { ); try { await trackDirect(EventType.BALANCE, { - totalBalance: totalBalance.toString() + totalBalance: totalBalance.toFixed() }); const timer = setToStartOfNextMonth(new Date()); browser.alarms.create("track-balance", { From 85cceb67e718a3172424aca46c2e5dd8e83d796c Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Tue, 18 Jun 2024 15:42:06 -0700 Subject: [PATCH 06/16] fix: misc light mode fixes on send and transactions --- shim.d.ts | 1 + src/components/popup/home/Transactions.tsx | 29 ++++++++++++++++------ src/routes/popup/send/index.tsx | 7 +++--- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/shim.d.ts b/shim.d.ts index e806a771a..d93dbe22f 100644 --- a/shim.d.ts +++ b/shim.d.ts @@ -49,6 +49,7 @@ declare module "styled-components" { secondaryTextv2: string; background: string; backgroundSecondary: string; + secondaryBtnHover: string; inputField: string; primary: string; cardBorder: string; diff --git a/src/components/popup/home/Transactions.tsx b/src/components/popup/home/Transactions.tsx index 51cf4727e..f0904b42a 100644 --- a/src/components/popup/home/Transactions.tsx +++ b/src/components/popup/home/Transactions.tsx @@ -200,7 +200,6 @@ export default function Transactions() {
- )) ) : ( @@ -248,17 +247,31 @@ const Section = styled.div<{ alignRight?: boolean }>` const TransactionItem = styled.div` padding: 0 10px; - border-radius: 10px; + position: relative; &:hover { - background: #36324d; - border-radius: 10px; + background: ${(props) => props.theme.secondaryBtnHover}; + } + + &:not(:last-child)::before { + content: ""; + position: absolute; + bottom: 0; + left: 10px; + right: 10px; + height: 1px; + background-color: ${(props) => props.theme.backgroundSecondary}; } -`; -const Underline = styled.div` - height: 1px; - background: ${(props) => props.theme.backgroundSecondary}; + &:first-child { + border-top-left-radius: 10px; + border-top-right-radius: 10px; + } + + &:last-child { + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + } `; const NoTransactions = styled(Text).attrs({ diff --git a/src/routes/popup/send/index.tsx b/src/routes/popup/send/index.tsx index 36ad6a6c1..974a90752 100644 --- a/src/routes/popup/send/index.tsx +++ b/src/routes/popup/send/index.tsx @@ -430,7 +430,7 @@ export default function Send({ id }: Props) { {(keystoneError || degraded) && ( - +
{keystoneError ? ( @@ -509,6 +509,7 @@ export default function Send({ id }: Props) { )} setQty(max.toString())} > @@ -659,7 +660,7 @@ export default function Send({ id }: Props) { } const Currency = styled.span<{ active: boolean }>` - color: ${(props) => (!props.active ? "#B9B9B9" : "#ffffff")}; + color: ${(props) => (!props.active ? "#B9B9B9" : props.theme.primaryTextv2)}; `; const Image = styled.img` @@ -730,7 +731,7 @@ const Wrapper = styled.div<{ showOverlay: boolean }>` left: 0; right: 0; bottom: 0; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(${(props) => props.theme.background}, 0.5); z-index: 10; display: ${({ showOverlay }) => (showOverlay ? "block" : "none")}; } From 6f3c650920b035a8803c37e8652439df9f4830e8 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Wed, 19 Jun 2024 14:29:09 +0545 Subject: [PATCH 07/16] fix: Resolve bigint error in currency formatting functions --- src/components/popup/Token.tsx | 19 ++++++++++--------- src/tokens/currency.ts | 21 ++++++++------------- src/utils/format.ts | 2 +- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 0b8d1c93a..c6a36437a 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -55,16 +55,18 @@ export default function Token({ onClick, ...props }: Props) { // token balance const fractBalance = useMemo( () => - balanceToFractioned(props.balance, { - id: props.id, - decimals: props.decimals, - divisibility: props.divisibility - }), + props.ao + ? BigNumber(props.balance) + : balanceToFractioned(props.balance, { + id: props.id, + decimals: props.decimals, + divisibility: props.divisibility + }), [props] ); const balance = useMemo(() => { - const balanceToFormat = BigNumber(props.ao ? props.balance : fractBalance); + const balanceToFormat = fractBalance; const isTinyBalance = balanceToFormat.gt(0) && balanceToFormat.lt(1e-6); const isSmallBalance = balanceToFormat.lt(1) && balanceToFormat.gte(1e-6); @@ -75,11 +77,10 @@ export default function Token({ onClick, ...props }: Props) { setTotalBalance(balanceToFormat.toFixed()); + const numBalance = formattedBalance.replace(/,/g, ""); setShowTooltip(balanceToFormat.gte(1_000_000) || isTinyBalance); - return isSmallBalance - ? formattedBalance - : abbreviateNumber(balanceToFormat); + return isSmallBalance ? numBalance : abbreviateNumber(numBalance); }, [fractBalance.toString()]); // token price diff --git a/src/tokens/currency.ts b/src/tokens/currency.ts index a2acdcd2d..0122eb2f2 100644 --- a/src/tokens/currency.ts +++ b/src/tokens/currency.ts @@ -2,8 +2,7 @@ import { Quantity } from "ao-tokens"; import BigNumber from "bignumber.js"; /** Token formatting config */ -export const tokenConfig: Intl.NumberFormatOptions & - BigIntToLocaleStringOptions = { +export const tokenConfig: Intl.NumberFormatOptions = { maximumFractionDigits: 2 }; @@ -13,16 +12,16 @@ export const tokenConfig: Intl.NumberFormatOptions & export function formatTokenBalance( balance: string | number | BigNumber | Quantity ) { - const val = Quantity.isQuantity(balance) + const bigNum = BigNumber.isBigNumber(balance) ? balance - : new Quantity("0", 20n).fromString(balance.toString()); - - return val.toLocaleString(undefined, tokenConfig); + : BigNumber(balance.toString()); + return bigNum + .toFormat(tokenConfig.maximumFractionDigits) + .replace(/\.?0*$/, ""); } /** Fiat formatting config */ -export const fiatConfig: Intl.NumberFormatOptions & - BigIntToLocaleStringOptions = { +export const fiatConfig: Intl.NumberFormatOptions = { style: "currency", currencyDisplay: "symbol", maximumFractionDigits: 2 @@ -35,11 +34,7 @@ export function formatFiatBalance( balance: string | number | BigNumber | Quantity, currency?: string ) { - const val = Quantity.isQuantity(balance) - ? balance - : new Quantity("0", 20n).fromString(balance.toString()); - - return val.toLocaleString(undefined, { + return (+balance).toLocaleString(undefined, { ...fiatConfig, currency: currency?.toLowerCase() }); diff --git a/src/utils/format.ts b/src/utils/format.ts index d00e8bd61..da202a76c 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -108,6 +108,6 @@ export function abbreviateNumber(value: number | string | BigNumber): string { if (abbreviatedValue.mod(1).isEqualTo(0)) { return `${abbreviatedValue.toFixed()}${suffix}`; } else { - return `${abbreviatedValue.toFixed(2)}${suffix}`; + return `${abbreviatedValue.toFixed(1)}${suffix}`; } } From e51724356faf8571ecd74773759a48c56c91b27a Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Wed, 19 Jun 2024 15:48:18 +0545 Subject: [PATCH 08/16] fix: Show quantity without exponential notation --- src/components/popup/home/Transactions.tsx | 2 +- src/lib/transactions.ts | 4 ++-- src/routes/popup/transaction/[id].tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/popup/home/Transactions.tsx b/src/components/popup/home/Transactions.tsx index 51cf4727e..46789a963 100644 --- a/src/components/popup/home/Transactions.tsx +++ b/src/components/popup/home/Transactions.tsx @@ -128,7 +128,7 @@ export default function Transactions() { if (transaction.aoInfo) { return `${balanceToFractioned(transaction.aoInfo.quantity, { divisibility: transaction.aoInfo.denomination - })} ${transaction.aoInfo.tickerName}`; + }).toFixed()} ${transaction.aoInfo.tickerName}`; } return ""; default: diff --git a/src/lib/transactions.ts b/src/lib/transactions.ts index b8d90224a..de8ed8b82 100644 --- a/src/lib/transactions.ts +++ b/src/lib/transactions.ts @@ -17,7 +17,7 @@ export type ExtendedTransaction = RawTransaction & { aoInfo?: { tickerName: string; denomination?: number; - quantity: number; + quantity: string; }; }; @@ -73,7 +73,7 @@ const processAoTransaction = async ( year: 0, date: "", aoInfo: { - quantity: quantityTag ? Number(quantityTag.value) : undefined, + quantity: quantityTag ? quantityTag.value : undefined, tickerName: tokenData?.Ticker || formatAddress(transaction.node.recipient, 4), denomination: tokenData?.Denomination || 0 diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 4d1453da0..bb8443b27 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -152,7 +152,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) { decimals: Number(tokenInfo.Denomination) }); setTicker(tokenInfo.Ticker); - data.transaction.quantity = { ar: amount.toString(), winston: "" }; + data.transaction.quantity = { ar: amount.toFixed(), winston: "" }; data.transaction.recipient = aoRecipient.value; } } @@ -549,7 +549,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) { fullWidth onClick={() => { const url = ao.isAo - ? `https://www.ao.link/message/${id}` + ? `https://www.ao.link/#/message/${id}` : `https://viewblock.io/arweave/tx/${id}`; browser.tabs.create({ url }); From 14486c1400e71f06d913a8d9ada8b0573557457d Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Wed, 19 Jun 2024 16:42:03 +0545 Subject: [PATCH 09/16] fix: Amount width for larger amounts --- src/routes/auth/signDataItem.tsx | 55 +++---------------- src/routes/popup/transaction/[id].tsx | 78 +++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 53 deletions(-) diff --git a/src/routes/auth/signDataItem.tsx b/src/routes/auth/signDataItem.tsx index 5cd2a9211..d0c9d42fc 100644 --- a/src/routes/auth/signDataItem.tsx +++ b/src/routes/auth/signDataItem.tsx @@ -16,7 +16,8 @@ import { TransactionProperty, PropertyName, PropertyValue, - TagValue + TagValue, + useAdjustAmountTitleWidth } from "~routes/popup/transaction/[id]"; import Wrapper from "~components/auth/Wrapper"; import browser from "webextension-polyfill"; @@ -51,23 +52,6 @@ interface DataStructure { tags: Tag[]; } -const useMeasureTextWidth = () => { - const canvasRef = useRef(null); - - if (!canvasRef.current) { - canvasRef.current = document.createElement("canvas"); - } - - const measureTextWidth = useCallback((text: string, font: string): number => { - const context = canvasRef.current.getContext("2d"); - context.font = font; - const metrics = context.measureText(text); - return metrics.width; - }, []); - - return measureTextWidth; -}; - export default function SignDataItem() { // connect params const params = useAuthParams<{ @@ -75,9 +59,6 @@ export default function SignDataItem() { data: DataStructure; }>(); - const parentRef = useRef(null); - const childRef = useRef(null); - const measureTextWidth = useMeasureTextWidth(); const [password, setPassword] = useState(false); const [loading, setLoading] = useState(false); const [tokenName, setTokenName] = useState(""); @@ -101,6 +82,11 @@ export default function SignDataItem() { [amount] ); + // adjust amount title font sizes + const parentRef = useRef(null); + const childRef = useRef(null); + useAdjustAmountTitleWidth(parentRef, childRef, formattedAmount); + const [signatureAllowance] = useStorage( { key: "signatureAllowance", @@ -160,25 +146,6 @@ export default function SignDataItem() { closeWindow(); } - function adjustAmountFontSize() { - if (!amount || !parentRef.current || !childRef.current) return; - - const parentWidth = parentRef.current.offsetWidth; - const style = getComputedStyle(childRef.current); - const font = `${style.fontSize} ${style.fontFamily}`; - const childWidth = measureTextWidth(formattedAmount, font); - - if (childWidth / parentWidth > 0.85) { - const newFontSize = parentWidth * 0.05; - childRef.current.style.fontSize = `${newFontSize}px`; - childRef.current.children[0].style.fontSize = `0.75rem`; - } else { - // default font sizes - childRef.current.style.fontSize = `2.5rem`; - childRef.current.children[0].style.fontSize = `1.25rem`; - } - } - useEffect(() => { if (amount === null) return; @@ -263,14 +230,6 @@ export default function SignDataItem() { return () => window.removeEventListener("keydown", listener); }, [params?.authID, password]); - useEffect(() => { - adjustAmountFontSize(); - - window.addEventListener("resize", adjustAmountFontSize); - - return () => window.removeEventListener("resize", adjustAmountFontSize); - }, [amount]); - useEffect(() => { if (tokenName && !logo) { setLogo(arweaveLogo); diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index bb8443b27..1a288d514 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -1,13 +1,19 @@ import { balanceToFractioned, formatFiatBalance, - formatTokenBalance, - fractionedToBalance + formatTokenBalance } from "~tokens/currency"; import { AnimatePresence, type Variants, motion } from "framer-motion"; import { Section, Spacer, Text } from "@arconnect/components"; import type { GQLNodeInterface } from "ar-gql/dist/faces"; -import { useEffect, useMemo, useState } from "react"; +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, + type MutableRefObject +} from "react"; import { useGateway } from "~gateways/wayfinder"; import { useHistory } from "~utils/hash_router"; import { @@ -58,6 +64,13 @@ export default function Transaction({ id: rawId, gw, message }: Props) { // fetch tx data const [transaction, setTransaction] = useState(); + const [quantity, setQuantity] = useState(""); + + // adjust amount title font sizes + const parentRef = useRef(null); + const childRef = useRef(null); + useAdjustAmountTitleWidth(parentRef, childRef, quantity); + // const [contact, setContact] = useState(undefined); const contact = useContact(transaction?.recipient); @@ -157,6 +170,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) { } } + setQuantity(data.transaction.quantity.ar); setTransaction(data.transaction); } }; @@ -282,7 +296,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) { }, [transaction]); return ( - +
- + {!ao.isAo ? formatTokenBalance(transaction.quantity.ar || "0") : transaction.quantity.ar} @@ -566,6 +588,52 @@ export default function Transaction({ id: rawId, gw, message }: Props) { ); } +export const useAdjustAmountTitleWidth = ( + parentRef: MutableRefObject, + childRef: MutableRefObject, + quantity: string +) => { + const canvasRef = useRef(null); + + if (!canvasRef.current) { + canvasRef.current = document.createElement("canvas"); + } + + const measureTextWidth = useCallback((text: string, font: string): number => { + const context = canvasRef.current.getContext("2d"); + context.font = font; + const metrics = context.measureText(text); + return metrics.width; + }, []); + + function adjustAmountFontSize() { + if (!quantity || !parentRef.current || !childRef.current) return; + + const parentWidth = parentRef.current.offsetWidth; + const style = getComputedStyle(childRef.current); + const font = `${style.fontSize} ${style.fontFamily}`; + const childWidth = measureTextWidth(quantity, font); + + if (childWidth / parentWidth > 0.85) { + const newFontSize = parentWidth * 0.05; + childRef.current.style.fontSize = `${newFontSize}px`; + childRef.current.children[0].style.fontSize = `0.75rem`; + } else { + // default font sizes + childRef.current.style.fontSize = `2.5rem`; + childRef.current.children[0].style.fontSize = `1.25rem`; + } + } + + useEffect(() => { + adjustAmountFontSize(); + + window.addEventListener("resize", adjustAmountFontSize); + + return () => window.removeEventListener("resize", adjustAmountFontSize); + }, [quantity]); +}; + const Wrapper = styled.div` display: flex; flex-direction: column; From c3cee142695faf0a0cd18bbc34daf31fba1d14fb Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Wed, 19 Jun 2024 18:52:55 +0545 Subject: [PATCH 10/16] fix: Use correct ao link message url --- src/routes/popup/transaction/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 502917d1c..25481d9c4 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -548,7 +548,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) { fullWidth onClick={() => { const url = ao.isAo - ? `https://www.ao.link/message/${id}` + ? `https://www.ao.link/#/message/${id}` : `https://viewblock.io/arweave/tx/${id}`; browser.tabs.create({ url }); From dc5a6755c1d10e5227acbea3e6bd07fa07a1d62e Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Wed, 19 Jun 2024 15:40:36 -0700 Subject: [PATCH 11/16] chore: components version bump --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e3936d34b..3a299332a 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ ] }, "dependencies": { - "@arconnect/components": "^0.3.8", + "@arconnect/components": "^0.3.11", "@arconnect/keystone-sdk": "^0.0.5", "@arconnect/warp-dre": "^0.0.1", "@arconnect/webext-bridge": "^5.0.6", diff --git a/yarn.lock b/yarn.lock index 754716b22..47e92179c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,10 +10,10 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@arconnect/components@^0.3.8": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@arconnect/components/-/components-0.3.8.tgz#aa230bb0908b5b4b5ec9701b8b78810ab9388dc7" - integrity sha512-k7YKaMO37It+IYGQA3DYqriOeKlIJnII9wc7Sl+3V7dsUZ3rNEk+qXIFVbcO/vMZM/Z6AR9MEUwbTFufu8MV8Q== +"@arconnect/components@^0.3.11": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@arconnect/components/-/components-0.3.11.tgz#a1920019702eb00663c7d78eeb5272fe78de96cf" + integrity sha512-wy1OY6w6NtNh0tvBWyDohub0/kQzhdCleXTODcx8wyD3PBs1i846B+a3KG7etEJiKsXoVYdohhje1ne83CoBaQ== dependencies: "@iconicicons/react" "^1.5.1" axios "^1.6.7" From f21d9b3a85a3a0cc124be60108a3d6d74c0c5c1d Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 20 Jun 2024 18:04:23 +0545 Subject: [PATCH 12/16] refactor: Update balance format logic --- src/api/modules/sign/fee.ts | 2 +- src/api/modules/sign/utils.ts | 2 +- src/components/popup/Token.tsx | 21 ++------ src/routes/auth/signDataItem.tsx | 12 ++++- src/tokens/currency.ts | 2 +- src/utils/format.ts | 91 ++++++++++++++++++++++---------- 6 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/api/modules/sign/fee.ts b/src/api/modules/sign/fee.ts index db91ac831..62e7a1bef 100644 --- a/src/api/modules/sign/fee.ts +++ b/src/api/modules/sign/fee.ts @@ -60,7 +60,7 @@ export default async function handleFeeAlarm(alarmInfo: Alarms.Alarm) { if (feeMultiplier > 1) { feeTx.reward = BigNumber(feeTx.reward) .multipliedBy(feeMultiplier) - .toFixed(0); + .toFixed(0, BigNumber.ROUND_FLOOR); } await arweave.transactions.sign(feeTx, keyfile); diff --git a/src/api/modules/sign/utils.ts b/src/api/modules/sign/utils.ts index f0a572b04..ccd051e21 100644 --- a/src/api/modules/sign/utils.ts +++ b/src/api/modules/sign/utils.ts @@ -55,7 +55,7 @@ export async function calculateReward({ reward }: Transaction) { // calculate fee with multiplier const fee = BigNumber(reward).multipliedBy(multiplier); - return fee.toFixed(0); + return fee.toFixed(0, BigNumber.ROUND_FLOOR); } /** diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index c6a36437a..22203c85b 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -27,7 +27,7 @@ import Arweave from "arweave"; import { useGateway } from "~gateways/wayfinder"; import aoLogo from "url:/assets/ecosystem/ao-logo.svg"; import { getUserAvatar } from "~lib/avatar"; -import { abbreviateNumber } from "~utils/format"; +import { formatBalance } from "~utils/format"; import Skeleton from "~components/Skeleton"; import { TrashIcon, PlusIcon, SettingsIcon } from "@iconicicons/react"; import BigNumber from "bignumber.js"; @@ -66,21 +66,10 @@ export default function Token({ onClick, ...props }: Props) { ); const balance = useMemo(() => { - const balanceToFormat = fractBalance; - const isTinyBalance = balanceToFormat.gt(0) && balanceToFormat.lt(1e-6); - const isSmallBalance = balanceToFormat.lt(1) && balanceToFormat.gte(1e-6); - - const formattedBalance = - isTinyBalance || isSmallBalance - ? balanceToFormat.toFixed() - : formatTokenBalance(balanceToFormat); - - setTotalBalance(balanceToFormat.toFixed()); - - const numBalance = formattedBalance.replace(/,/g, ""); - setShowTooltip(balanceToFormat.gte(1_000_000) || isTinyBalance); - - return isSmallBalance ? numBalance : abbreviateNumber(numBalance); + const formattedBalance = formatBalance(fractBalance); + setTotalBalance(formattedBalance.tooltipBalance); + setShowTooltip(formattedBalance.showTooltip); + return formattedBalance.displayBalance; }, [fractBalance.toString()]); // token price diff --git a/src/routes/auth/signDataItem.tsx b/src/routes/auth/signDataItem.tsx index d0c9d42fc..1ed1ac9ac 100644 --- a/src/routes/auth/signDataItem.tsx +++ b/src/routes/auth/signDataItem.tsx @@ -40,6 +40,7 @@ import { LogoWrapper, Logo } from "~components/popup/Token"; import arLogoLight from "url:/assets/ar/logo_light.png"; import arLogoDark from "url:/assets/ar/logo_dark.png"; import { useTheme } from "~utils/theme"; +import BigNumber from "bignumber.js"; interface Tag { name: string; @@ -74,6 +75,9 @@ export default function SignDataItem() { const transfer = params?.data?.tags?.some( (tag) => tag.name === "Action" && tag.value === "Transfer" ); + const isAo = params?.data?.tags?.some( + (tag) => tag.name === "Data-Protocol" && tag.value === "ao" + ); const process = params?.data?.target; @@ -205,7 +209,7 @@ export default function SignDataItem() { } const tokenAmount = new Quantity( - BigInt(quantity), + BigInt(BigNumber(quantity).toFixed(0, BigNumber.ROUND_FLOOR)), BigInt(tokenInfo.Denomination) ); setTokenName(tokenInfo.Name); @@ -350,7 +354,11 @@ export default function SignDataItem() { params?.data?.tags?.map((tag, i) => ( {tag.name} - {tag.value} + + {isAo && transfer && tag.name === "Quantity" + ? BigNumber(tag.value).toFixed(0, BigNumber.ROUND_FLOOR) + : tag.value} + ))} diff --git a/src/tokens/currency.ts b/src/tokens/currency.ts index 0122eb2f2..3f809167c 100644 --- a/src/tokens/currency.ts +++ b/src/tokens/currency.ts @@ -133,7 +133,7 @@ export function fractionedToBalance( return tokenType === "WARP" ? balanceBigNum.toFixed() - : balanceBigNum.toFixed(0); + : balanceBigNum.toFixed(0, BigNumber.ROUND_FLOOR); } export interface DivisibilityOrDecimals { diff --git a/src/utils/format.ts b/src/utils/format.ts index da202a76c..ab426a31a 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -77,37 +77,70 @@ export const formatSettingName = (name: string) => { }; /** - * Abbreviates large numbers into a more readable format, using M, B, and T for millions, billions, and trillions respectively. + * Format balance into a more readable format, using M, B, T, Q, Qi, and S for millions, billions, trillions, quadrillions, quintillions, and sextillions respectively. * - * @param value The numeric value to abbreviate, as a number or string. - * @returns The abbreviated string representation of the number. + * @param balance The bignumber balance value to format. + * @returns An object containing displayBalance, tooltipBalance and showTooltip */ -export function abbreviateNumber(value: number | string | BigNumber): string { - let suffix = ""; - - if (!BigNumber.isBigNumber(value)) { - value = BigNumber(value); - } - - let abbreviatedValue = value; - - if (value.gte(1e12)) { - // Trillions - suffix = "T"; - abbreviatedValue = value.dividedBy(1e12); - } else if (value.gte(1e9)) { - // Billions - suffix = "B"; - abbreviatedValue = value.dividedBy(1e9); - } else if (value.gte(1e6)) { - // Millions - suffix = "M"; - abbreviatedValue = value.dividedBy(1e6); - } - - if (abbreviatedValue.mod(1).isEqualTo(0)) { - return `${abbreviatedValue.toFixed()}${suffix}`; +export function formatBalance(balance: BigNumber) { + let displayBalance: string; + let showTooltip = false; + + const tooltipBalance = balance + .toFormat(20, BigNumber.ROUND_FLOOR) + .replace(/\.?0*$/, ""); + + if (balance.lt(1)) { + displayBalance = + balance.toFixed(20, BigNumber.ROUND_FLOOR).replace(/\.?0*$/, "") || "0"; + } else if (balance.lte(1e4)) { + displayBalance = BigNumber(balance.toPrecision(6, BigNumber.ROUND_FLOOR)) + .toFixed(20, BigNumber.ROUND_FLOOR) + .replace(/\.?0*$/, ""); + showTooltip = !balance.eq(displayBalance); + } else if (balance.lt(1e6)) { + displayBalance = BigNumber(balance.toPrecision(8, BigNumber.ROUND_FLOOR)) + .toFixed(20, BigNumber.ROUND_FLOOR) + .replace(/\.?0*$/, ""); + showTooltip = !balance.eq(displayBalance); } else { - return `${abbreviatedValue.toFixed(1)}${suffix}`; + showTooltip = true; + let suffix = ""; + let divisor = 1; + + if (balance.gte(1e21)) { + // Sextillions + suffix = "S"; + divisor = 1e21; + } else if (balance.gte(1e18)) { + // Quintillions + suffix = "Qi"; + divisor = 1e18; + } else if (balance.gte(1e15)) { + // Quadrillions + suffix = "Q"; + divisor = 1e15; + } else if (balance.gte(1e12)) { + // Trillions + suffix = "T"; + divisor = 1e12; + } else if (balance.gte(1e9)) { + // Billions + suffix = "B"; + divisor = 1e9; + } else if (balance.gte(1e6)) { + // Millions + suffix = "M"; + divisor = 1e6; + } + + displayBalance = + BigNumber( + balance.dividedBy(divisor).toPrecision(6, BigNumber.ROUND_FLOOR) + ) + .toFixed(20, BigNumber.ROUND_FLOOR) + .replace(/\.?0*$/, "") + suffix; } + + return { displayBalance, tooltipBalance, showTooltip }; } From 7cc3a60a1ff35ac4888c32831cd7f9756ddfbeae Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 20 Jun 2024 18:34:01 +0545 Subject: [PATCH 13/16] refactor: Format AR balance in currency selector --- src/components/popup/Token.tsx | 41 +++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 22203c85b..bfbc6d018 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -396,6 +396,9 @@ export function ArToken({ onClick }: ArTokenProps) { // load ar balance const [balance, setBalance] = useState(BigNumber("0")); const [fiatBalance, setFiatBalance] = useState(BigNumber("0")); + const [displayBalance, setDisplayBalance] = useState("0"); + const [totalBalance, setTotalBalance] = useState(""); + const [showTooltip, setShowTooltip] = useState(false); // memoized requirements to ensure stability const requirements = useMemo(() => ({ ensureStake: true }), []); @@ -410,8 +413,12 @@ export function ArToken({ onClick }: ArTokenProps) { // fetch balance const winstonBalance = await arweave.wallets.getBalance(activeAddress); const arBalance = BigNumber(arweave.ar.winstonToAr(winstonBalance)); - setBalance(arBalance); + + const formattedBalance = formatBalance(arBalance); + setTotalBalance(formattedBalance.tooltipBalance); + setShowTooltip(formattedBalance.showTooltip); + setDisplayBalance(formattedBalance.displayBalance); })(); }, [activeAddress, gateway]); @@ -427,15 +434,29 @@ export function ArToken({ onClick }: ArTokenProps) { Arweave - - - {formatTokenBalance(balance)} - {" AR"} - - - {formatFiatBalance(fiatBalance, currency.toLowerCase())} - - + {showTooltip ? ( + + + + {displayBalance} + {" AR"} + + + + {formatFiatBalance(fiatBalance, currency.toLowerCase())} + + + ) : ( + + + {displayBalance} + {" AR"} + + + {formatFiatBalance(fiatBalance, currency.toLowerCase())} + + + )} ); } From d6200284c77c4736b1ef5c4890e357c0774f8f37 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 20 Jun 2024 21:19:39 +0545 Subject: [PATCH 14/16] refactor: Improve formatBalance for balance < 1 to correct precision --- src/utils/format.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/utils/format.ts b/src/utils/format.ts index ab426a31a..aceb07cc8 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -90,10 +90,7 @@ export function formatBalance(balance: BigNumber) { .toFormat(20, BigNumber.ROUND_FLOOR) .replace(/\.?0*$/, ""); - if (balance.lt(1)) { - displayBalance = - balance.toFixed(20, BigNumber.ROUND_FLOOR).replace(/\.?0*$/, "") || "0"; - } else if (balance.lte(1e4)) { + if (balance.lte(1e4)) { displayBalance = BigNumber(balance.toPrecision(6, BigNumber.ROUND_FLOOR)) .toFixed(20, BigNumber.ROUND_FLOOR) .replace(/\.?0*$/, ""); From 1b8bc6215056d5ca1e2b25771d576ce684c13a8b Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 20 Jun 2024 22:14:51 +0545 Subject: [PATCH 15/16] fix: Issue related to allowance --- src/components/auth/App.tsx | 2 +- src/routes/auth/allowance.tsx | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/auth/App.tsx b/src/components/auth/App.tsx index d0bb8be22..1facd10f2 100644 --- a/src/components/auth/App.tsx +++ b/src/components/auth/App.tsx @@ -42,7 +42,7 @@ export default function App({ const arweave = new Arweave(defaultGateway); const arVal = arweave.ar.winstonToAr(val.toString()); - return new Quantity(arVal, 20n); + return new Quantity("0", 20n).fromString(arVal); } // display theme diff --git a/src/routes/auth/allowance.tsx b/src/routes/auth/allowance.tsx index 704987dd6..44ddd362b 100644 --- a/src/routes/auth/allowance.tsx +++ b/src/routes/auth/allowance.tsx @@ -147,11 +147,13 @@ export default function Allowance() { appName={appData?.name || params?.url} appUrl={params?.url} appIcon={appData?.logo} - allowance={{ - enabled: allowance.enabled, - limit: allowance.limit.toString(), - spent: allowance.spent.toString() - }} + allowance={ + allowance && { + enabled: allowance.enabled, + limit: allowance.limit.toFixed(), + spent: allowance.spent.toFixed() + } + } />
From 364a5b5a9a8c7dabd017efefc75fbcd4f23624f9 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 20 Jun 2024 23:01:39 +0545 Subject: [PATCH 16/16] fix: Make quantity integer if its ao token transfer --- .../sign_data_item/sign_data_item.background.ts | 14 ++++++++++++++ src/routes/auth/signDataItem.tsx | 12 ++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/api/modules/sign_data_item/sign_data_item.background.ts b/src/api/modules/sign_data_item/sign_data_item.background.ts index bd4c8a5a2..57c98f1a8 100644 --- a/src/api/modules/sign_data_item/sign_data_item.background.ts +++ b/src/api/modules/sign_data_item/sign_data_item.background.ts @@ -14,6 +14,7 @@ import browser from "webextension-polyfill"; import { signAuth } from "../sign/sign_auth"; import Arweave from "arweave"; import authenticate from "../connect/auth"; +import BigNumber from "bignumber.js"; const background: ModuleFunction = async ( appData, @@ -34,6 +35,19 @@ const background: ModuleFunction = async ( (tag) => tag.name === "Data-Protocol" && tag.value === "ao" ) ) { + try { + const tags = dataItem?.tags || []; + const quantityTag = tags.find((tag) => tag.name === "Quantity"); + if (quantityTag) { + const quantity = BigNumber(quantityTag.value).toFixed( + 0, + BigNumber.ROUND_FLOOR + ); + if (!isNaN(+quantity)) { + quantityTag.value = quantity; + } + } + } catch {} try { await authenticate({ type: "signDataItem", diff --git a/src/routes/auth/signDataItem.tsx b/src/routes/auth/signDataItem.tsx index 1ed1ac9ac..d0c9d42fc 100644 --- a/src/routes/auth/signDataItem.tsx +++ b/src/routes/auth/signDataItem.tsx @@ -40,7 +40,6 @@ import { LogoWrapper, Logo } from "~components/popup/Token"; import arLogoLight from "url:/assets/ar/logo_light.png"; import arLogoDark from "url:/assets/ar/logo_dark.png"; import { useTheme } from "~utils/theme"; -import BigNumber from "bignumber.js"; interface Tag { name: string; @@ -75,9 +74,6 @@ export default function SignDataItem() { const transfer = params?.data?.tags?.some( (tag) => tag.name === "Action" && tag.value === "Transfer" ); - const isAo = params?.data?.tags?.some( - (tag) => tag.name === "Data-Protocol" && tag.value === "ao" - ); const process = params?.data?.target; @@ -209,7 +205,7 @@ export default function SignDataItem() { } const tokenAmount = new Quantity( - BigInt(BigNumber(quantity).toFixed(0, BigNumber.ROUND_FLOOR)), + BigInt(quantity), BigInt(tokenInfo.Denomination) ); setTokenName(tokenInfo.Name); @@ -354,11 +350,7 @@ export default function SignDataItem() { params?.data?.tags?.map((tag, i) => ( {tag.name} - - {isAo && transfer && tag.name === "Quantity" - ? BigNumber(tag.value).toFixed(0, BigNumber.ROUND_FLOOR) - : tag.value} - + {tag.value} ))}