diff --git a/package.json b/package.json index 7495a2547..fa624850f 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", @@ -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/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/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/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..1d1907d11 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.lte(allowance.limit); // if the allowance is enough, return if (hasEnoughAllowance) return; diff --git a/src/api/modules/sign/fee.ts b/src/api/modules/sign/fee.ts index 25a5379a4..62e7a1bef 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, BigNumber.ROUND_FLOOR); } 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/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 2944205cb..ccd051e21 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,9 +53,9 @@ 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); + return fee.toFixed(0, BigNumber.ROUND_FLOOR); } /** @@ -66,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" @@ -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.lt(0.1)) maximumFractionDigits = 6; + else if (arPrice.lt(10)) maximumFractionDigits = 4; + else if (arPrice.lt(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/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/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 4b3a3dd65..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, Gateway } from "~gateways/gateway"; +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 0b3e09226..1facd10f2 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,23 +26,23 @@ 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]); - function winstonToArFormatted(val: number) { + function winstonToArFormatted(val: string) { const arweave = new Arweave(defaultGateway); const arVal = arweave.ar.winstonToAr(val.toString()); - return parseFloat(arVal); + return new Quantity("0", 20n).fromString(arVal); } // display theme 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/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/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/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 1a4ac033e..bfbc6d018 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -27,9 +27,10 @@ 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"; import JSConfetti from "js-confetti"; import { AO_NATIVE_TOKEN } from "~utils/ao_import"; @@ -54,35 +55,22 @@ 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 = props.ao ? props.balance : fractBalance; - const isTinyBalance = balanceToFormat > 0 && balanceToFormat < 1e-6; - const isSmallBalance = balanceToFormat < 1 && balanceToFormat >= 1e-6; - - const formattedBalance = - isTinyBalance || isSmallBalance - ? balanceToFormat.toString() - : formatTokenBalance(balanceToFormat); - - setTotalBalance( - isTinyBalance - ? balanceToFormat.toFixed(20).replace(/(\.?)0+$/, "") - : formattedBalance - ); - - const numBalance = parseFloat(formattedBalance.replace(/,/g, "")); - setShowTooltip(numBalance >= 1_000_000 || isTinyBalance); - - return isSmallBalance ? numBalance : abbreviateNumber(numBalance); - }, [fractBalance]); + const formattedBalance = formatBalance(fractBalance); + setTotalBalance(formattedBalance.tooltipBalance); + setShowTooltip(formattedBalance.showTooltip); + return formattedBalance.displayBalance; + }, [fractBalance.toString()]); // token price const { price, currency } = usePrice(props.ticker); @@ -91,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]); @@ -406,8 +394,11 @@ export function ArToken({ onClick }: ArTokenProps) { }); // load ar balance - const [balance, setBalance] = useState(0); - const [fiatBalance, setFiatBalance] = useState(0); + 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 }), []); @@ -421,14 +412,18 @@ 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(arBalance); + + const formattedBalance = formatBalance(arBalance); + setTotalBalance(formattedBalance.tooltipBalance); + setShowTooltip(formattedBalance.showTooltip); + setDisplayBalance(formattedBalance.displayBalance); })(); }, [activeAddress, gateway]); useEffect(() => { - setFiatBalance(balance * price); + setFiatBalance(balance.multipliedBy(price)); }, [balance, price]); return ( @@ -439,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())} + + + )} ); } 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 195381472..aa345928f 100644 --- a/src/components/popup/home/Tokens.tsx +++ b/src/components/popup/home/Tokens.tsx @@ -53,7 +53,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/components/popup/home/Transactions.tsx b/src/components/popup/home/Transactions.tsx index 51cf4727e..06e1734eb 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: @@ -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/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/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/popup.tsx b/src/popup.tsx index 3d025a619..6dd422422 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -132,7 +132,7 @@ export default function Popup() { }) => ( )} diff --git a/src/routes/auth/allowance.tsx b/src/routes/auth/allowance.tsx index 6ee82decd..44ddd362b 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,13 @@ export default function Allowance() { appName={appData?.name || params?.url} appUrl={params?.url} appIcon={appData?.logo} - allowance={allowance} + allowance={ + allowance && { + enabled: allowance.enabled, + limit: allowance.limit.toFixed(), + spent: allowance.spent.toFixed() + } + } />
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 52ec99399..df0ab6189 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/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/auth/subscription.tsx b/src/routes/auth/subscription.tsx index dbbbb357c..ef0f5cfdb 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..7b39d1e8c 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"; @@ -206,7 +206,7 @@ export default function Token() { logo }} priceData={historicalPrices} - latestPrice={price} + latestPrice={+price} loading={loadingHistoricalPrices} > { (async () => { if (token.id === "AR") { - return setBalance(arBalance); + return setBalance(arBalance.toString()); } // placeholder balance @@ -243,20 +244,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(() => { @@ -264,34 +265,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 @@ -321,35 +322,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.lt(0) || max.lt(parsedQty); + }, [qty, max.toString()]); // show token selector const [showTokenSelector, setShownTokenSelector] = useState(false); @@ -397,11 +401,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 @@ -430,7 +434,7 @@ export default function Send({ id }: Props) { {(keystoneError || degraded) && ( - +
{keystoneError ? ( @@ -500,7 +504,7 @@ export default function Send({ id }: Props) { fullWidth icon={ - {!!price && ( + {!!+price && ( USD/ @@ -509,8 +513,9 @@ export default function Send({ id }: Props) { )} setQty(max.toString())} + onClick={() => setQty(max.toFixed())} > Max @@ -519,7 +524,7 @@ export default function Send({ id }: Props) { /> - {!!price && !isAo ? ( + {!!+price && !isAo ? ( ≈ {qtyMode === "fiat" @@ -599,7 +604,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)} /> ))} @@ -659,7 +664,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 +735,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")}; } 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/subscriptions/subscriptionDetails.tsx b/src/routes/popup/subscriptions/subscriptionDetails.tsx index 896ed2e39..043e02bcf 100644 --- a/src/routes/popup/subscriptions/subscriptionDetails.tsx +++ b/src/routes/popup/subscriptions/subscriptionDetails.tsx @@ -36,6 +36,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; @@ -49,7 +50,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(""); @@ -137,7 +138,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 ffdd793a2..1d3cf0334 100644 --- a/src/routes/popup/subscriptions/subscriptionPayment.tsx +++ b/src/routes/popup/subscriptions/subscriptionPayment.tsx @@ -23,6 +23,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); @@ -127,7 +128,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/routes/popup/token/[id].tsx b/src/routes/popup/token/[id].tsx index 369d3148d..82b1cad27 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({ @@ -230,7 +236,7 @@ export default function Asset({ id }: Props) { logo }} priceData={historicalPrices} - latestPrice={price} + latestPrice={+price} loading={loadingHistoricalPrices} > setPeriod(p)} /> diff --git a/src/routes/popup/tokens.tsx b/src/routes/popup/tokens.tsx index dfe4d4dad..4d7daae0c 100644 --- a/src/routes/popup/tokens.tsx +++ b/src/routes/popup/tokens.tsx @@ -140,7 +140,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 502917d1c..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 { @@ -39,6 +45,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"; import { Token } from "ao-tokens"; // pull contacts and check if to address is in contacts @@ -57,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); @@ -146,16 +160,17 @@ export default function Transaction({ id: rawId, gw, message }: Props) { if (aoQuantity) { const tokenInfo = (await Token(data.transaction.recipient)).info; - const amount = balanceToFractioned(Number(aoQuantity.value), { + const amount = balanceToFractioned(aoQuantity.value, { id: data.transaction.recipient, 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; } } + setQuantity(data.transaction.quantity.ar); setTransaction(data.transaction); } }; @@ -193,9 +208,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 +296,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) { }, [transaction]); return ( - +
- + {!ao.isAo - ? formatTokenBalance(Number(transaction.quantity.ar)) + ? formatTokenBalance(transaction.quantity.ar || "0") : transaction.quantity.ar} {/* NEEDS TO BE DYNAMIC */} {ticker ? ticker : "AR"} @@ -548,7 +571,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 }); @@ -565,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; diff --git a/src/routes/popup/transaction/transactions.tsx b/src/routes/popup/transaction/transactions.tsx index 184bd719b..3ab4592d9 100644 --- a/src/routes/popup/transaction/transactions.tsx +++ b/src/routes/popup/transaction/transactions.tsx @@ -200,7 +200,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/subscriptions/payments.ts b/src/subscriptions/payments.ts index dd5ea9c15..e2368e495 100644 --- a/src/subscriptions/payments.ts +++ b/src/subscriptions/payments.ts @@ -16,6 +16,7 @@ import { trackCanceledSubscription } from "~subscriptions"; import { isLocalWallet } from "~utils/assertions"; +import BigNumber from "bignumber.js"; export async function handleSubscriptionPayment( data: SubscriptionData, @@ -28,7 +29,7 @@ export async function handleSubscriptionPayment( } if ( - data.applicationAllowance < data.subscriptionFeeAmount && + BigNumber(data.applicationAllowance).lt(data.subscriptionFeeAmount) && !initialPayment ) { await statusUpdateSubscription( @@ -78,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/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index 9a875f4a5..b52fadf5c 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -86,7 +86,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); @@ -130,7 +130,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), @@ -153,31 +153,33 @@ export function useAoTokens(): [TokenInfoWithBalance[], boolean] { const balances = await Promise.all( tokens.map(async ({ id }) => { try { - const balance = Number( - await timeoutPromise( - (async () => { - if (id === AO_NATIVE_TOKEN) { - const res = await dryrun({ - Id: "0000000000000000000000000000000000000000001", - Owner: activeAddress, - process: AO_NATIVE_TOKEN_BALANCE_MIRROR, - tags: [{ name: "Action", value: "Balance" }] - }); - const balance = res.Messages[0].Data; - if (balance) { - return new Quantity(BigInt(balance), BigInt(12)); - } - // default return - return new Quantity(0, BigInt(12)); - } else { - const aoToken = await Token(id); - const balance = await aoToken.getBalance(activeAddress); - return balance; + const balance = await timeoutPromise( + (async () => { + if (id === AO_NATIVE_TOKEN) { + const res = await dryrun({ + Id: "0000000000000000000000000000000000000000001", + Owner: activeAddress, + process: AO_NATIVE_TOKEN_BALANCE_MIRROR, + tags: [{ name: "Action", value: "Balance" }] + }); + const balance = res.Messages[0].Data; + if (balance) { + return new Quantity( + BigInt(balance), + BigInt(12) + ).toString(); } - })(), - 10000 - ) + // default return + return new Quantity(0, BigInt(12)).toString(); + } else { + const aoToken = await Token(id); + const balance = await aoToken.getBalance(activeAddress); + return balance.toString(); + } + })(), + 10000 ); + return { id, balance @@ -199,7 +201,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); @@ -251,7 +253,7 @@ export function useAoTokensCache(): [TokenInfoWithBalance[], boolean] { ...token, id: token.processId, Denomination: Number(token.Denomination || 0), - balance: 0 + balance: "0" })); }, [aoTokensCache, aoTokensIds, activeAddress, aoTokens]); @@ -275,15 +277,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; - })(), - 10000 - ) + 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, @@ -390,5 +390,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..3f809167c 100644 --- a/src/tokens/currency.ts +++ b/src/tokens/currency.ts @@ -1,3 +1,6 @@ +import { Quantity } from "ao-tokens"; +import BigNumber from "bignumber.js"; + /** Token formatting config */ export const tokenConfig: Intl.NumberFormatOptions = { maximumFractionDigits: 2 @@ -6,10 +9,15 @@ export const tokenConfig: Intl.NumberFormatOptions = { /** * Format token balance */ -export function formatTokenBalance(balance: string | number) { - const val = typeof balance === "string" ? parseFloat(balance) : balance; - - return val.toLocaleString(undefined, tokenConfig); +export function formatTokenBalance( + balance: string | number | BigNumber | Quantity +) { + const bigNum = BigNumber.isBigNumber(balance) + ? balance + : BigNumber(balance.toString()); + return bigNum + .toFormat(tokenConfig.maximumFractionDigits) + .replace(/\.?0*$/, ""); } /** Fiat formatting config */ @@ -22,10 +30,11 @@ export const fiatConfig: Intl.NumberFormatOptions = { /** * Format fiat balance */ -export function formatFiatBalance(balance: string | number, currency?: string) { - const val = typeof balance === "string" ? parseFloat(balance) : balance; - - return val.toLocaleString(undefined, { +export function formatFiatBalance( + balance: string | number | BigNumber | Quantity, + currency?: string +) { + return (+balance).toLocaleString(undefined, { ...fiatConfig, currency: currency?.toLowerCase() }); @@ -94,16 +103,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 +129,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("."); + const balanceBigNum = BigNumber(balance).shiftedBy(decimals); - // Calculate the number of fractional digits - const fractionalDigits = fractionalPart.length; - - 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, BigNumber.ROUND_FLOOR); } 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..58c1be9de 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -23,7 +23,7 @@ export interface Token { type: TokenType; gateway?: Gateway; dre?: string; - balance: number | null; + balance: string | null; divisibility?: number; decimals?: number; defaultLogo?: string; @@ -218,12 +218,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/analytics.ts b/src/utils/analytics.ts index 92b96f07b..31e996ab8 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"; import axios from "axios"; const PUBLIC_SEGMENT_WRITEKEY = "J97E4cvSZqmpeEdiUQNC2IxS1Kw4Cwxm"; @@ -166,7 +167,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 }) => { @@ -174,14 +175,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.toFixed() + }); const timer = setToStartOfNextMonth(new Date()); browser.alarms.create("track-balance", { when: timer.getTime() 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..aceb07cc8 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 * @@ -75,32 +77,67 @@ 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 { - let suffix = ""; - let abbreviatedValue = value; - - if (value >= 1e12) { - // Trillions - suffix = "T"; - abbreviatedValue = value / 1e12; - } else if (value >= 1e9) { - // Billions - suffix = "B"; - abbreviatedValue = value / 1e9; - } else if (value >= 1e6) { - // Millions - suffix = "M"; - abbreviatedValue = value / 1e6; - } +export function formatBalance(balance: BigNumber) { + let displayBalance: string; + let showTooltip = false; + + const tooltipBalance = balance + .toFormat(20, BigNumber.ROUND_FLOOR) + .replace(/\.?0*$/, ""); - if (abbreviatedValue % 1 === 0) { - return `${abbreviatedValue}${suffix}`; + 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 }; } 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 754716b22..4932c9c8f 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" @@ -4655,10 +4655,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" @@ -5032,7 +5032,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==