Skip to content

Commit

Permalink
Fix decimals conversion (#800)
Browse files Browse the repository at this point in the history
* Create prettyNumber util

* Fix decimals conversion
  • Loading branch information
JayJay1024 authored Aug 20, 2024
1 parent cf558e4 commit afa22b4
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 33 deletions.
8 changes: 4 additions & 4 deletions apps/web/src/components/modals/transfer-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BaseBridge } from "../../bridges";
import { ChainConfig, Token } from "../../types";
import Modal from "../../ui/modal";
import { formatBalance, getChainLogoSrc, toShortAdrress } from "../../utils";
import { formatBalance, getChainLogoSrc, prettyNumber, toShortAdrress } from "../../utils";
import { Address } from "viem";

interface Props {
Expand All @@ -13,7 +13,7 @@ interface Props {
targetToken: Token;
fee: { token: Token; value: bigint } | null | undefined;
bridge: BaseBridge | undefined;
amount: bigint;
amount: { value: bigint; input: string };
busy: boolean;
isOpen: boolean;
onClose: () => void;
Expand Down Expand Up @@ -73,7 +73,7 @@ function SourceTarget({
amount,
}: {
type: "source" | "target";
amount: bigint;
amount: { value: bigint; input: string };
chain?: ChainConfig;
token?: Token;
address?: Address | null;
Expand All @@ -89,7 +89,7 @@ function SourceTarget({
className={`max-w-[46%] truncate text-sm font-extrabold ${type === "source" ? "text-app-red" : "text-app-green"}`}
>
{type === "source" ? "-" : "+"}
{formatBalance(amount, token.decimals)}
{prettyNumber(amount.input)}
</span>
</div>

Expand Down
72 changes: 48 additions & 24 deletions apps/web/src/components/transfer-amount-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import { formatUnits, parseUnits } from "viem";
import Faucet from "./faucet";

interface Value {
input: string;
value: bigint;
input: string; // In units of ETH
value: bigint; // In units of wei
valid: boolean;
alert: string;
}

interface Props {
min?: bigint;
max?: bigint;
token: Token;
sourceToken: Token;
targetToken: Token;
value: Value;
balance: bigint;
loading?: boolean;
Expand All @@ -27,7 +28,8 @@ export default function TransferAmountInput({
min,
max,
chain,
token,
sourceToken,
targetToken,
value,
balance,
loading,
Expand All @@ -37,7 +39,27 @@ export default function TransferAmountInput({
const [dynamicFont, setDynamicFont] = useState("text-[3rem] font-light");
const inputRef = useRef<HTMLInputElement>(null);
const spanRef = useRef<HTMLSpanElement>(null);
const tokenRef = useRef(token);
const sourceTokenRef = useRef(sourceToken);

useEffect(() => {
if (
sourceToken.decimals !== sourceTokenRef.current.decimals ||
sourceToken.symbol !== sourceTokenRef.current.symbol
) {
// Reset
sourceTokenRef.current = sourceToken;
onChange({ input: "", value: 0n, valid: true, alert: "" });
}
}, [sourceToken, onChange]);

const handleMaxInput = useCallback(() => {
const { value, input } = parseAmount(
formatUnits(max ?? 0n, sourceToken.decimals),
sourceToken.decimals,
minimumPrecision(sourceToken, targetToken),
);
onChange({ valid: true, alert: "", value, input });
}, [sourceToken, targetToken, max, onChange]);

const handleChange = useCallback<ChangeEventHandler<HTMLInputElement>>(
(e) => {
Expand All @@ -48,16 +70,16 @@ export default function TransferAmountInput({

if (input) {
if (!Number.isNaN(Number(input))) {
parsed = parseAmount(input, token.decimals);
parsed = parseAmount(input, sourceToken.decimals, minimumPrecision(sourceToken, targetToken));
if (balance < parsed.value) {
valid = false;
alert = "* Insufficient";
} else if (typeof min === "bigint" && parsed.value < min) {
valid = false;
alert = `* Minimum transfer amount: ${formatBalance(min, token.decimals)}`;
alert = `* Minimum transfer amount: ${formatBalance(min, sourceToken.decimals)}`;
} else if (typeof max === "bigint" && max < parsed.value) {
valid = false;
alert = `* Maximum transfer amount: ${formatBalance(max, token.decimals, {
alert = `* Maximum transfer amount: ${formatBalance(max, sourceToken.decimals, {
precision: 6,
})}`;
}
Expand All @@ -67,13 +89,9 @@ export default function TransferAmountInput({
onChange({ valid, alert, ...parsed });
}
},
[min, max, balance, token.decimals, onChange],
[min, max, balance, sourceToken, targetToken, onChange],
);

const handleMaxInput = useCallback(() => {
onChange({ valid: true, alert: "", value: max ?? 0n, input: formatUnits(max ?? 0n, token.decimals) });
}, [token.decimals, max, onChange]);

useEffect(() => {
const inputWidth = inputRef.current?.clientWidth || 1;
const spanWidth = spanRef.current?.clientWidth || 0;
Expand All @@ -93,13 +111,6 @@ export default function TransferAmountInput({
}
}, [value.input]);

useEffect(() => {
if (token.decimals !== tokenRef.current.decimals || token.symbol !== tokenRef.current.symbol) {
tokenRef.current = token;
onChange({ input: "", value: 0n, valid: true, alert: "" });
}
}, [token, onChange]);

return (
<div className="gap-medium px-medium flex flex-col">
<input
Expand All @@ -110,7 +121,9 @@ export default function TransferAmountInput({
onChange={handleChange}
/>
<div className="flex items-center gap-2">
<span className="text-sm font-normal text-white/50">Balance: {formatBalance(balance, token.decimals)}</span>
<span className="text-sm font-normal text-white/50">
Balance: {formatBalance(balance, sourceToken.decimals)}
</span>
<button
className={`rounded-full bg-white/20 p-[3px] opacity-50 transition hover:opacity-100 active:scale-95 ${
loading ? "animate-spin" : ""
Expand All @@ -125,7 +138,7 @@ export default function TransferAmountInput({
>
Max
</button>
{chain.testnet ? <Faucet sourceChain={chain} sourceToken={token} onSuccess={onRefresh} /> : null}
{chain.testnet ? <Faucet sourceChain={chain} sourceToken={sourceToken} onSuccess={onRefresh} /> : null}
</div>

<span className="invisible fixed left-0 top-0 -z-50" ref={spanRef}>
Expand All @@ -135,13 +148,24 @@ export default function TransferAmountInput({
);
}

function parseAmount(source: string, decimals: number) {
/**
* Parse amount
* @param source For example, the input value
* @param decimals The decimals value of token
* @param precision The precision you want to preserve
* @returns { value: bigint; input: string }
*/
function parseAmount(source: string, decimals: number, precision: number) {
let input = "";
let value = 0n;
const [i, d] = source.replace(/,/g, "").split(".").concat("-1"); // The commas must be removed or parseUnits will error
if (i) {
input = d === "-1" ? i : d ? `${i}.${d.slice(0, decimals)}` : `${i}.`;
input = d === "-1" ? i : d ? `${i}.${d.slice(0, precision)}` : `${i}.`;
value = parseUnits(input, decimals);
}
return { value, input };
}

function minimumPrecision(token0: Token, token1: Token) {
return token0.decimals < token1.decimals ? token0.decimals : token1.decimals;
}
9 changes: 6 additions & 3 deletions apps/web/src/components/transfer-amount-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ interface Amount {
interface Props {
min?: bigint;
max?: bigint;
token: Token;
sourceToken: Token;
targetToken: Token;
amount: Amount;
balance: bigint;
loading?: boolean;
Expand All @@ -24,7 +25,8 @@ interface Props {
export default function TransferAmountSection({
min,
max,
token,
sourceToken,
targetToken,
chain,
amount,
balance,
Expand All @@ -38,7 +40,8 @@ export default function TransferAmountSection({
min={min}
max={max ? BigInt(max) : undefined}
loading={loading}
token={token}
sourceToken={sourceToken}
targetToken={targetToken}
chain={chain}
balance={balance}
value={amount}
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/components/transfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ function Component() {
amount={amount}
loading={loadingBalance}
balance={balance}
token={sourceToken}
sourceToken={sourceToken}
targetToken={targetToken}
chain={sourceChain}
max={maxTransfer}
onChange={setAmount}
Expand Down Expand Up @@ -318,7 +319,7 @@ function Component() {
targetToken={targetToken}
fee={fee}
bridge={bridge}
amount={deferredAmount.value}
amount={deferredAmount}
busy={isTransfering}
isOpen={isOpen}
onClose={() => setIsOpen(false)}
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/utils/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export function formatBalance(value: bigint, decimals = 18, options?: { precisio
return `${_integers}${_decimals.slice(1)}`;
}

export function prettyNumber(value: string | number) {
const [i, d] = value.toString().split(".");
const integers = i.replace(/(?=(?!^)(\d{3})+$)/g, ",");
return d ? `${integers}.${d}` : integers;
}

export async function getBalance(address: Address, token: Token, publicClient: PublicClient) {
let value = 0n;
if (token.type === "native") {
Expand Down

0 comments on commit afa22b4

Please sign in to comment.