Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support more tags from MTS, fix exponentially small numbers #82

Merged
merged 5 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions src/components/CoinBalanceUSD.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,17 @@ export const CoinBalanceUSD = (props: ComponentProps) => {
const { tokenPriceMap, getUSDValue } = useUSDValue();
const address = tokenInfo.address;
const cgPrice = address ? tokenPriceMap[address]?.usd || 0 : 0;
const amountInUSD = useMemo(() => {
if (!amount || !hasNumericValue(amount)) return 0;

return new Decimal(amount).mul(cgPrice).toNumber();
const amountInUSD = useMemo(() => {
if (!amount || !hasNumericValue(amount)) return new Decimal(0);
return new Decimal(amount).mul(cgPrice)
}, [amount, cgPrice]);

// effects
useEffect(() => {
if (address) getUSDValue([address]);
}, [address, getUSDValue]);

if (!amountInUSD || amountInUSD <= 0) return <>{''}</>;

return (
<>
{prefix}${formatNumber.format(amountInUSD, maxDecimals || 2)}
Expand Down
201 changes: 155 additions & 46 deletions src/components/FormPairRow.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,182 @@
import React, {
CSSProperties,
useEffect,
useMemo,
useRef,
} from 'react';
import { TokenInfo } from '@solana/spl-token-registry';
import React, { CSSProperties, useMemo } from 'react';

import CoinBalance from './Coinbalance';
import { PAIR_ROW_HEIGHT } from './FormPairSelector';
import TokenIcon from './TokenIcon';
import TokenLink from './TokenLink';
import { useUSDValueProvider } from 'src/contexts/USDValueProvider';
import Decimal from 'decimal.js';
import { WRAPPED_SOL_MINT } from 'src/constants';
import { checkIsStrictOrVerified, checkIsToken2022, checkIsUnknownToken } from 'src/misc/tokenTags';
import { useAccounts } from 'src/contexts/accounts';
import { formatNumber } from 'src/misc/utils';
import TokenIcon from './TokenIcon';
import TokenLink from './TokenLink';
import CoinBalance from './Coinbalance';

export const PAIR_ROW_HEIGHT = 72;

const FormPairRow: React.FC<{
export interface IPairRow {
usdValue?: Decimal;
item: TokenInfo;
style: CSSProperties;
onSubmit(item: TokenInfo): void;
}> = ({ item, style, onSubmit }) => {
const isUnknown = useMemo(() => item.tags?.length === 0 || item.tags?.includes('unknown'), [item.tags])
suppressCloseModal?: boolean;
showExplorer?: boolean;
enableUnknownTokenWarning?: boolean;
isLST?: boolean;
}

interface IMultiTag {
isVerified: boolean;
isLST: boolean;
isUnknown: boolean;
isToken2022: boolean;
isFrozen: boolean;
}

const MultiTags: React.FC<IPairRow> = ({ item }) => {
const { accounts } = useAccounts();
const { tokenPriceMap } = useUSDValueProvider();
const isLoading = useRef<boolean>(false);
const isLoaded = useRef<boolean>(false);
// It's cheaper to slightly delay and rendering once, than rendering everything all the time
const [renderedTag, setRenderedTag] = React.useState<IMultiTag>({
isVerified: false,
isLST: false,
isUnknown: false,
isToken2022: false,
isFrozen: false,
});

useEffect(() => {
if (isLoaded.current || isLoading.current) return;

isLoading.current = true;
setTimeout(() => {
const result = {
isVerified: checkIsStrictOrVerified(item),
isLST: Boolean(item.tags?.includes('lst')),
isUnknown: checkIsUnknownToken(item),
isToken2022: Boolean(checkIsToken2022(item)),
isFrozen: accounts[item.address]?.isFrozen || false,
};
setRenderedTag(result);
isLoading.current = false;
isLoaded.current = true;
}, 0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const remainingTags = useMemo(() => {
// Filter out the tags we've already used
const filterTags = ['verified', 'strict', 'lst', 'unknown', 'token-2022', 'new'];
const otherTags = item.tags?.filter((item) => filterTags.includes(item) === false);
return otherTags;
}, [item.tags]);

if (!renderedTag) return null;

const { isVerified, isLST, isUnknown, isToken2022, isFrozen } = renderedTag;

return (
<div className="flex justify-end gap-x-1">
{isFrozen && (
<p className="border rounded-md text-xxs leading-none transition-all py-0.5 px-1 border-warning/50 text-warning/50">
<span>Frozen</span>
</p>
)}

const totalUsdValue = useMemo(() => {
const tokenPrice = tokenPriceMap[item.address]?.usd;
const balance = accounts[item.address]?.balance;
if (!tokenPrice || !balance) return null;
{isUnknown && (
<p className="rounded-md text-xxs leading-none transition-all py-0.5 px-1 bg-black/10 font-semibold text-white/20">
<span>Unknown</span>
</p>
)}

const totalAValue = new Decimal(tokenPrice).mul(balance);
return totalAValue;
}, [accounts, item.address, tokenPriceMap])
{isToken2022 && (
<p className="rounded-md text-xxs leading-none transition-all py-0.5 px-1 bg-black/10 font-semibold text-white/20">
<span>Token2022</span>
</p>
)}

{remainingTags?.map((tag, idx) => (
<div
key={idx}
className="rounded-md text-xxs leading-none transition-all py-0.5 px-1 bg-black/10 font-semibold text-white/20"
>
{tag}
</div>
))}

{isLST && (
<p className="rounded-md text-xxs leading-none transition-all py-0.5 px-1 text-v3-primary/50 border border-v3-primary/50 font-semibold">
<span>LST</span>
</p>
)}

{isVerified && (
<p className="rounded-md text-xxs leading-none transition-all py-0.5 px-1 text-v3-primary/50 border border-v3-primary/50 font-semibold">
{/* We're renaming verified to stict for now, requested by Mei */}
<span>Strict</span>
</p>
)}
</div>
);
};

const FormPairRow = (props: IPairRow) => {
const {
item,
style,
onSubmit,
suppressCloseModal,
usdValue,
showExplorer = true,
enableUnknownTokenWarning = true,
} = props;
const onClick = React.useCallback(() => {
onSubmit(item);

if (suppressCloseModal) return;
}, [onSubmit, item, suppressCloseModal]);

const usdValueDisplay =
usdValue && usdValue.gt(0.01) // If smaller than 0.01 cents, dont show
? `$${formatNumber.format(usdValue.toDP(2).toNumber())}`
: '';

return (
<li
className={`cursor-pointer list-none `}
style={{ maxHeight: PAIR_ROW_HEIGHT, height: PAIR_ROW_HEIGHT, ...style }}
className={`rounded cursor-pointer px-5 my-1 list-none flex w-full items-center bg-v2-lily/5 hover:bg-v2-lily/10`}
style={{ maxHeight: PAIR_ROW_HEIGHT - 4, height: PAIR_ROW_HEIGHT - 4, ...style }}
onClick={onClick}
translate="no"
>
<div
className="flex items-center rounded-xl space-x-4 my-2 p-3 justify-between bg-v2-lily/10 hover:bg-v2-lily/5"
onClick={() => onSubmit(item)}
>
<div className="flex h-full w-full items-center space-x-4">
<div className="flex-shrink-0">
<div className="h-6 w-6 rounded-full">
<TokenIcon info={item} width={24} height={24} />
<div className="bg-gray-200 rounded-full">
<TokenIcon info={item} width={24} height={24} enableUnknownTokenWarning={enableUnknownTokenWarning} />
</div>
</div>

<div className="flex-1 min-w-0">
<div className='flex flex-row space-x-2'>
<p className="text-sm text-white truncate">
{item.symbol}
</p>
<TokenLink tokenInfo={item} />
</div>

<div className="mt-1 text-xs text-gray-500 truncate flex space-x-1">
<CoinBalance mintAddress={item.address} />

{totalUsdValue && totalUsdValue.gt(0.01) ? (
<span className='ml-1'>
| ${totalUsdValue.toFixed(2)}
</span>
<div className="flex space-x-2">
<p className="text-sm font-medium text-white truncate">{item.symbol}</p>
{/* Intentionally higher z to be clickable */}
{showExplorer ? (
<div className="z-10" onClick={(e) => e.stopPropagation()}>
<TokenLink tokenInfo={item} />
</div>
) : null}
</div>
<p className="text-xs text-gray-500 dark:text-white-35 truncate">
{item.address === WRAPPED_SOL_MINT.toBase58() ? 'Solana' : item.name}
</p>
</div>

{isUnknown ? (
<p className="ml-auto border rounded-md text-xxs py-[1px] px-1 border-warning text-warning">
<span>Unknown</span>
</p>
) : null}
<div className="text-xs text-v2-lily/50 text-right h-full flex flex-col justify-evenly">
<CoinBalance mintAddress={item.address} hideZeroBalance />
{usdValueDisplay ? <p>{usdValueDisplay}</p> : null}
<MultiTags {...props} />
</div>
</div>
</li>
);
Expand Down
5 changes: 2 additions & 3 deletions src/components/FormPairSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const rowRenderer = memo((props: ListChildComponentProps) => {
const { data, index, style } = props;
const item = data.searchResult[index];

return <FormPairRow key={item.address} item={item} style={style} onSubmit={data.onSubmit} />;
return <FormPairRow key={item.address} item={item} style={style} onSubmit={data.onSubmit} usdValue={data.mintToUsdValue.get(item.address)} />;
}, areEqual);

const generateSearchTerm = (info: TokenInfo, searchValue: string) => {
Expand Down Expand Up @@ -126,7 +126,6 @@ const FormPairSelector = ({ onSubmit, tokenInfos, onClose }: IFormPairSelector)

const userWalletResults = useMemo(() => {
const userWalletResults: TokenInfo[] = [...tokenMap.values(), ...unknownTokenMap.values()];
getUSDValue(userWalletResults.map((item) => item.address));
return userWalletResults;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand All @@ -144,7 +143,6 @@ const FormPairSelector = ({ onSubmit, tokenInfos, onClose }: IFormPairSelector)
// Show user wallet tokens by default
if (!searchValue.current) {
setSearchResult(await sortTokenListByBalance(userWalletResults));
console.log({userWalletResults})
setIsSearching(false);
return;
}
Expand Down Expand Up @@ -285,6 +283,7 @@ const FormPairSelector = ({ onSubmit, tokenInfos, onClose }: IFormPairSelector)
itemData={{
searchResult,
onSubmit,
mintToUsdValue,
}}
className={classNames('overflow-y-scroll mr-1 min-h-[12rem] px-5 webkit-scrollbar')}
>
Expand Down
4 changes: 2 additions & 2 deletions src/components/PriceInfo/Fees.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const Fees = ({ routePlan }: IFees) => {
const decimals = tokenMint?.decimals ?? 6;

const feeAmount = formatNumber.format(
new Decimal(item.swapInfo.feeAmount.toString()).div(Math.pow(10, decimals)).toNumber(),
new Decimal(item.swapInfo.feeAmount.toString()).div(Math.pow(10, decimals)),
);
const feePct = new Decimal(item.swapInfo.feeAmount.toString())
.div(
Expand All @@ -45,7 +45,7 @@ const Fees = ({ routePlan }: IFees) => {
</span>
</div>
<div className="text-white/30 text-right">
{feeAmount} {tokenMint?.symbol} ({formatNumber.format(new Decimal(feePct).mul(100).toNumber())}
{feeAmount} {tokenMint?.symbol} ({formatNumber.format(new Decimal(feePct).mul(100))}
%)
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/SwapSettingsModal/SwapSettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useSwapContext } from 'src/contexts/SwapContext';
import { useWalletPassThrough } from 'src/contexts/WalletPassthroughProvider';
import ExternalIcon from 'src/icons/ExternalIcon';
import { SOL_TOKEN_INFO } from 'src/misc/constants';
import { detectedSeparator, formatNumber, toLamports } from 'src/misc/utils';
import { detectedSeparator, formatNumber, hasNumericValue, toLamports } from 'src/misc/utils';
import { useReferenceFeesQuery } from 'src/queries/useReferenceFeesQuery';
import { CoinBalanceUSD } from '../CoinBalanceUSD';
import JupButton from '../JupButton';
Expand Down Expand Up @@ -353,7 +353,7 @@ const SwapSettingsModal: React.FC<{ closeModal: () => void }> = ({ closeModal })
<span className="text-xxs mt-1 text-white/25 font-normal self-end">
<CoinBalanceUSD
tokenInfo={SOL_TOKEN_INFO}
amount={unsavedPriorityFee?.toString()}
amount={unsavedPriorityFee}
maxDecimals={4}
prefix="~"
/>
Expand All @@ -370,7 +370,7 @@ const SwapSettingsModal: React.FC<{ closeModal: () => void }> = ({ closeModal })
<NumericFormat
value={value}
onValueChange={({ floatValue }) => {
if (typeof floatValue !== 'number') return;
if (typeof floatValue !== 'number' || floatValue <= 0) return;
form.setValue('hasUnsavedFeeChanges', true);
onChange(floatValue);
}}
Expand Down
18 changes: 9 additions & 9 deletions src/components/useSortByValue.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { TokenInfo } from "@solana/spl-token-registry";
import Decimal from "decimal.js";
import { useCallback, useEffect, useRef } from "react";
import { WRAPPED_SOL_MINT } from "src/constants";
import { useTokenContext } from "src/contexts/TokenContextProvider";
import { useUSDValue } from "src/contexts/USDValueProvider";
import { useAccounts } from "src/contexts/accounts";
import { checkIsUnknownToken } from "src/misc/tokenTags";
import { fromLamports } from "src/misc/utils";
import { TokenInfo } from '@solana/spl-token-registry';
import Decimal from 'decimal.js';
import { useCallback, useEffect, useRef } from 'react';
import { WRAPPED_SOL_MINT } from 'src/constants';
import { useTokenContext } from 'src/contexts/TokenContextProvider';
import { useUSDValue } from 'src/contexts/USDValueProvider';
import { useAccounts } from 'src/contexts/accounts';
import { checkIsUnknownToken } from 'src/misc/tokenTags';
import { fromLamports } from 'src/misc/utils';

export const useSortByValue = () => {
const { getTokenInfo } = useTokenContext();
Expand Down
12 changes: 9 additions & 3 deletions src/contexts/SwapContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,21 @@ export const SwapContextProvider: FC<{
setForm((prev) => {
const newValue = { ...prev };

if (!fromTokenInfo || !toTokenInfo) return prev;

let { inAmount, outAmount } = quoteResponseMeta?.quoteResponse || {};
if (jupiterSwapMode === SwapMode.ExactIn) {
newValue.toValue = outAmount ? String(fromLamports(outAmount, toTokenInfo?.decimals || 0)) : '';
newValue.toValue = outAmount
? new Decimal(outAmount.toString()).div(10 ** toTokenInfo.decimals).toFixed()
: '';
} else {
newValue.fromValue = inAmount ? String(fromLamports(inAmount, fromTokenInfo?.decimals || 0)) : '';
newValue.fromValue = inAmount
? new Decimal(inAmount.toString()).div(10 ** fromTokenInfo.decimals).toFixed()
: '';
}
return newValue;
});
}, [form.fromValue, fromTokenInfo?.decimals, jupiterSwapMode, quoteResponseMeta, toTokenInfo?.decimals]);
}, [form.fromValue, fromTokenInfo, jupiterSwapMode, quoteResponseMeta, toTokenInfo]);

const [txStatus, setTxStatus] = useState<
| {
Expand Down
Loading