Skip to content

Commit

Permalink
Add Types
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandrugu committed Jan 22, 2024
1 parent 8992201 commit d6a3c6c
Show file tree
Hide file tree
Showing 14 changed files with 163 additions and 42 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@tanstack/react-query": "^5.17.15",
"buffer": "^6.0.3",
"dayjs": "^1.11.9",
"ethers": "^6.10.0",
"events": "^3.3.0",
"flowbite-react": "0.6.0",
"lodash": "*",
Expand Down
41 changes: 33 additions & 8 deletions src/components/Amount.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,59 @@
import { FixedNumber } from "ethers";
import { Spinner } from "flowbite-react";
import { formatUnits, parseUnits } from "viem";
import { useAccount } from "wagmi";

import { CURRENCY_MAXIMUM_FRACTION_DIGITS } from "../constants";
import { useTokens } from "../hooks/token";
import { Address } from "../hooks/types";
import TokenAvatar from "./TokenAvatar";

const NUMBER_FORMAT = "en-US";

function prettify(value: FixedNumber) {
const ndigits =
value.cmp(FixedNumber.fromString("1")) === -1
? CURRENCY_MAXIMUM_FRACTION_DIGITS
: 2;

// most numbers will look OK with standard US number formatting (e.g. 12.56)
// however, 0.00034 will look like 0
// let's remedy this with custom formatting based on decimals value
return new Intl.NumberFormat(NUMBER_FORMAT, {
maximumFractionDigits: ndigits,
}).format(
// @ts-expect-error: format takes a string as well not just a number, so we need to disable TS
// Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/format#syntax
value.round(ndigits).toString(),
);
}

export default function Amount({
amount,
decimals = null,
tokenAddress = "",
symbol = null,
showLogo = true,
}: {
amount: bigint | number;
decimals?: number | null;
tokenAddress?: string;
symbol?: string | null;
showLogo?: boolean;
}) {
const { address: accountAddress } = useAccount();
const { data: tokens } = useTokens(accountAddress);

if (!amount) {
if (amount == null) {
return <Spinner color="gray" size="xs" />;
}

const addr = String(tokenAddress).toLowerCase();
const token = tokens.filter((asset) => asset.address.includes(addr));
const addr = String(tokenAddress).toLowerCase() as Address;
const token = tokens?.filter((asset) => asset.address.includes(addr));

// @ts-ignore
const amountDecimals = decimals || token?.decimals || 18;

const ndigits = amount < parseUnits("1", amountDecimals) ? 5 : 2;
const pretty = Number(formatUnits(amount, amountDecimals))
.toFixed(ndigits)
.toString();
const pretty = prettify(FixedNumber.fromValue(amount, amountDecimals));

if (showLogo) {
return (
Expand Down
12 changes: 11 additions & 1 deletion src/components/AssetInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Spinner, TextInput } from "flowbite-react";
import { useEffect, useState } from "react";
import { formatUnits, parseUnits } from "viem";

import { Token } from "../hooks/types";
import ActionLink from "./ActionLink";
import Amount from "./Amount";
import AssetSelect from "./AssetSelect";
Expand All @@ -16,6 +17,15 @@ export default function AssetInput({
disabled = false,
validate = true,
title = "",
}: {
assets: Token[];
asset: Token | null;
setAsset: (asset: Token | null) => void;
amount: bigint;
setAmount: (amount: bigint) => void;
disabled?: boolean;
validate?: boolean;
title?: string;
}) {
const [invalid, setInvalid] = useState(false);

Expand Down Expand Up @@ -81,7 +91,7 @@ export default function AssetInput({
<TextInput
required={true}
disabled={disabled}
value={disabled ? formatUnits(amount || 0, asset?.decimals) : value}
value={disabled ? formatUnits(amount || 0n, asset?.decimals) : value}
// @ts-ignore
onClick={(e) => e.target.select()}
onChange={(e) => !disabled && setValue(e.target.value)}
Expand Down
11 changes: 10 additions & 1 deletion src/components/AssetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import { isEmpty } from "lodash";
import { AlertCircle as AlertCircleleIcon } from "lucide-react";
import { isAddress } from "viem";

import { Token } from "../hooks/types";
import Amount from "./Amount";
import TokenAvatar from "./TokenAvatar";

export default function AssetsList({ assets, onSelect, search = null }) {
export default function AssetsList({
assets,
onSelect,
search = null,
}: {
assets: Token[];
onSelect: (asset: Token | null) => void;
search?: string;
}) {
if (isAddress(search) && isEmpty(assets)) {
return (
<div className="flex p-8 items-center text-sm gap-2 text-gray-500 dark:text-gray-400">
Expand Down
6 changes: 6 additions & 0 deletions src/components/AssetSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "lucide-react";
import { useEffect, useState } from "react";

import { Token } from "../hooks/types";
import AssetList from "./AssetList";
import Modal from "./Modal";
import TokenAvatar from "./TokenAvatar";
Expand All @@ -15,6 +16,11 @@ export default function AssetSelect({
assets,
onSelect,
className = "",
}: {
selectedAsset: Token | null;
assets: Token[];
onSelect: (asset: Token | null) => void;
className?: string;
}) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
Expand Down
9 changes: 8 additions & 1 deletion src/components/TokenAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ import {
TOKEN_ASSETS_CDN,
TOKEN_ICON,
} from "../constants";
import { Address } from "../hooks/types";

export default function TokenAvatar({ address, className = null }) {
export default function TokenAvatar({
address,
className = null,
}: {
address: Address;
className?: string;
}) {
if (!address) {
return <></>;
}
Expand Down
26 changes: 20 additions & 6 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import { optimism } from "viem/chains";

export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
import { Address } from "./hooks/types";

export const ZERO_ADDRESS: Address =
"0x0000000000000000000000000000000000000000";
export const TOKEN_ICON = "/svg/coin.svg";
export const RPC_URI = import.meta.env.VITE_RPC_URI;
export const TOKEN_ADDRESSES = String(
import.meta.env.VITE_TOKEN_ADDRESSES,
).split(",");
export const TOKEN_ADDRESSES: Address[] =
import.meta.env.VITE_TOKEN_ADDRESSES.split(",");
export const TOKEN_ASSETS_CDN = String(
import.meta.env.VITE_TOKEN_ASSETS_CDN,
).split(",");
export const DEFAULT_CHAIN = optimism;
export const NATIVE_TOKEN_LOGO = import.meta.env.VITE_NATIVE_TOKEN_LOGO;
export const NATIVE_TOKEN = {
...DEFAULT_CHAIN.nativeCurrency,
wrappedAddress: import.meta.env.VITE_WRAPPED_NATIVE_TOKEN.toLowerCase(),
address: DEFAULT_CHAIN.nativeCurrency.symbol.toLowerCase(),
wrappedAddress:
import.meta.env.VITE_WRAPPED_NATIVE_TOKEN.toLowerCase() as Address,
/**
* TODO: This is an exception for the native token where "address" is "ETH"
* - Change this later so it's type-safe
* Discussion: https://github.com/velodrome-finance/app/pull/347#discussion_r1380073009
*/

address: DEFAULT_CHAIN.nativeCurrency.symbol.toLowerCase() as Address,
};

export const WALLETCONNECT_PROJECT_ID = import.meta.env
.VITE_WALLETCONNECT_PROJECT_ID;

// prettier-ignore
export const FEATURE_FLAGS = String(
import.meta.env.VITE_FEATURE_FLAGS
).split(",");

export const CURRENCY_MAXIMUM_FRACTION_DIGITS = parseInt(
import.meta.env.VITE_CURRENCY_MAXIMUM_FRACTION_DIGITS || "5",
);
21 changes: 12 additions & 9 deletions src/hooks/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { useQuery } from "@tanstack/react-query";
import { getBalance } from "@wagmi/core";

import { TOKEN_ADDRESSES } from "../constants";
import rpc from "../rpc";
import config from "../rpc";
import { Address, Token } from "./types";

async function fetchTokens(accountAddress) {
const tokens = TOKEN_ADDRESSES.map(async (tokenAddress) => {
const token = await getBalance(rpc, {
address: accountAddress,
token: tokenAddress as `0x${string}`,
});
return { ...token, address: tokenAddress };
});
async function fetchTokens(accountAddress: Address): Promise<Token[]> {
const tokens = TOKEN_ADDRESSES.map(
async (tokenAddress: Address): Promise<Token> => {
const token = await getBalance(config, {
address: accountAddress,
token: tokenAddress,
});
return { ...token, address: tokenAddress };
},
);

return Promise.all(tokens);
}
Expand Down
11 changes: 11 additions & 0 deletions src/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Address } from "viem";

export type { Address };

export type Token = {
address: Address;
decimals: number;
formatted: string;
symbol: string;
value: bigint;
};
6 changes: 3 additions & 3 deletions src/pages/Create/components/Creator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
ToggleSwitch,
} from "flowbite-react";
import { useEffect, useState } from "react";
import { parseUnits } from "viem";
import { isAddress } from "viem";
import { isAddress, parseUnits } from "viem";
import { useAccount } from "wagmi";

import AssetInput from "../../../components/AssetInput";
import { useTokens } from "../../../hooks/token";
import { Token } from "../../../hooks/types";
import Checklist from "./Checklist";
import Graph from "./Graph";
import Preview from "./Preview";
Expand All @@ -31,7 +31,7 @@ export default function Creator() {
const { address: accountAddress } = useAccount();

const { data: tokens } = useTokens(accountAddress);
const [token, setToken] = useState();
const [token, setToken] = useState<Token>(null);

// Set default token if non selected
useEffect(() => {
Expand Down
20 changes: 11 additions & 9 deletions src/pages/Dashboard/components/Govnft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function Govnft({ withdraw }) {
<Amount
tokenAddress={"0x4200000000000000000000000000000000000042"}
decimals={18}
amount="0"
amount={0n}
symbol="OP"
showLogo={true}
/>
Expand All @@ -61,13 +61,15 @@ export default function Govnft({ withdraw }) {

<div className="space-y-1.5">
<div className="text-xs opacity-30 dark:opacity-20">Vesting</div>
<Amount
tokenAddress={"0x4200000000000000000000000000000000000042"}
decimals={18}
amount="0"
symbol="OP"
showLogo={false}
/>
<div>
<Amount
tokenAddress={"0x4200000000000000000000000000000000000042"}
decimals={18}
amount={0n}
symbol="OP"
showLogo={false}
/>
</div>
<div className="text-xs opacity-40 pt-1">Ends in 2 years</div>
</div>

Expand All @@ -80,7 +82,7 @@ export default function Govnft({ withdraw }) {
<Amount
tokenAddress={"0x4200000000000000000000000000000000000042"}
decimals={18}
amount="0"
amount={0n}
symbol="OP"
showLogo={false}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Delegate/components/DelegateNft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function DelegateNft() {
<Amount
tokenAddress={"0x4200000000000000000000000000000000000042"}
decimals={18}
amount="0"
amount={0}
symbol="OP"
showLogo={true}
/>
Expand All @@ -62,7 +62,7 @@ export default function DelegateNft() {
<Amount
tokenAddress={"0x4200000000000000000000000000000000000042"}
decimals={18}
amount="0"
amount={0}
symbol="OP"
showLogo={false}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Transfer/components/TransferNft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function TransferNft() {
<Amount
tokenAddress={"0x4200000000000000000000000000000000000042"}
decimals={18}
amount="0"
amount={0}
symbol="OP"
showLogo={true}
/>
Expand All @@ -62,7 +62,7 @@ export default function TransferNft() {
<Amount
tokenAddress={"0x4200000000000000000000000000000000000042"}
decimals={18}
amount="0"
amount={0}
symbol="OP"
showLogo={false}
/>
Expand Down
Loading

0 comments on commit d6a3c6c

Please sign in to comment.