diff --git a/projects/dex-ui/src/wells/wellLoader.ts b/projects/dex-ui/src/wells/wellLoader.ts index 547a6d632..6c1a3a517 100644 --- a/projects/dex-ui/src/wells/wellLoader.ts +++ b/projects/dex-ui/src/wells/wellLoader.ts @@ -31,21 +31,27 @@ const WELL_BLACKLIST: Record = { "0x0adf75da6980fee8f848d52a7af1f8d6f34a8169".toLowerCase(), // BEAN:WETH duplicate, "0xc22dd977c50812f754c14319de84493cc18b6cf0".toLowerCase(), // BEAN:WETH duplicate "0x15d7a96c3dbf6b267fae741d15c3a72f331418fe".toLowerCase(), // BEAN:WETH duplicate + "0xd902f7bd849da907202d177fafc1bd39f6bbadc4".toLowerCase(), // BEAN:WETH duplicate "0xb968de36ce9c61371a82a78b715af660c2209d11".toLowerCase(), // BEAN:wstETH duplicate "0x430837acc8cfe4726453b09b8d782730654899e0".toLowerCase(), // BEAN:wstETH duplicate "0x4731431430e7febd8df6a4aa7d28867927e827a6".toLowerCase(), // BEAN:wstETH duplicate + "0xc49b38dff421622628258683444f4977078cb96b".toLowerCase(), // BEAN:wstETH duplicate "0x8d74ff8e729b4e78898488775b619c05d1ecb5e5".toLowerCase(), // BEAN:weETH duplicate "0x65709d322f9c762f9435a326c653e7393807c0bc".toLowerCase(), // BEAN:weETH duplicate "0x8dc6400022ac4304b3236f4d073053056ac24086".toLowerCase(), // BEAN:weETH duplicate + "0x45f6af24e6eb8371571dde1464a458770cbbbb65".toLowerCase(), // BEAN:weETH duplicate "0x370062BE2d6Fc8d02948fEA75fAfe471F74854CF".toLowerCase(), // BEAN:WBTC duplicate "0xee950139d7730706695a4613198ecac26e69e12d".toLowerCase(), // BEAN:WBTC duplicate "0xb147ff6e2fd05ad3db185028beb3cce4dcb12b72".toLowerCase(), // BEAN:WBTC duplicate + "0xd4baa4197aa17c7f27a2465073de33690d77ec7e".toLowerCase(), // BEAN:WBTC duplicate "0x157219b5D112F2D8aaFD3c7F3bA5D4c73343cc96".toLowerCase(), // BEAN:USDC duplicate "0xdc29769db1caa5cab41835ef9a42becde80de028".toLowerCase(), // BEAN:USDC duplicate "0xde1a4b24aa46286739c1879612c5e5445382d93d".toLowerCase(), // BEAN:USDC duplicate + "0xeaddd2848e962817fd565ea269a7fedb0588b3f4".toLowerCase(), // BEAN:USDC duplicate "0xF3e4FC5c53D5500989e68F81d070094525caC240".toLowerCase(), // BEAN:USDT duplicate "0xacfb4644b708043ad6eff1cc323fda374fe6d3ce".toLowerCase(), // BEAN:USDT duplicate - "0x704e68281325242a60515616228c668e4865694c".toLowerCase() // BEAN:USDT duplicate + "0x704e68281325242a60515616228c668e4865694c".toLowerCase(), // BEAN:USDT duplicate + "0xde8317a2a31a1684e2e4becedec17700718630d8".toLowerCase() // BEAN:USDT duplicate ] }; diff --git a/projects/ui/src/components/Common/Form/TokenInputField.tsx b/projects/ui/src/components/Common/Form/TokenInputField.tsx index c21f27a73..ecb4d0dd2 100644 --- a/projects/ui/src/components/Common/Form/TokenInputField.tsx +++ b/projects/ui/src/components/Common/Form/TokenInputField.tsx @@ -217,7 +217,7 @@ const TokenInput: FC = ({ balance: balance?.toString(), }); if (!amount) return undefined; // if no amount, exit - if (min?.gt(amount)) return min; // clamp @ min + if (amount.gt(0) && min?.gt(amount)) return min; // clamp @ min if (!allowNegative && amount?.lt(ZERO_BN)) return ZERO_BN; // clamp negative if (max?.lt(amount)) return max; // clamp @ max return amount; // no max; always return amount @@ -239,7 +239,6 @@ const TokenInput: FC = ({ /// /// FIXME: throws an error if e.target.value === '.' const newValue = e.target.value ? new BigNumber(e.target.value) : null; - /// Always update the display amount right away. setDisplayAmount(newValue?.toString() || ''); diff --git a/projects/ui/src/components/Common/Form/TokenSelectDialog.tsx b/projects/ui/src/components/Common/Form/TokenSelectDialog.tsx index 1697376cc..3d046b6f4 100644 --- a/projects/ui/src/components/Common/Form/TokenSelectDialog.tsx +++ b/projects/ui/src/components/Common/Form/TokenSelectDialog.tsx @@ -18,7 +18,7 @@ import { StyledDialogContent, StyledDialogTitle, } from '~/components/Common/Dialog'; -import { displayBN } from '~/util'; +import { displayBN, getTokenIndex } from '~/util'; import { ZERO_BN } from '~/constants'; import { FarmerBalances } from '~/state/farmer/balances'; import { FarmerSilo } from '~/state/farmer/silo'; @@ -116,17 +116,18 @@ const TokenSelectDialog: TokenSelectDialogC = React.memo( ); const getBalance = useCallback( - (addr: string) => { + (token: TokenInstance) => { if (!_balances) return ZERO_BN; if (balancesType === 'farm') return ( - (_balances as TokenBalanceMode['farm'])?.[addr]?.[ + (_balances as TokenBalanceMode['farm'])?.[getTokenIndex(token)]?.[ balanceFromInternal ] || ZERO_BN ); return ( - (_balances as TokenBalanceMode['silo-deposits'])?.[addr]?.deposited - ?.amount || ZERO_BN + (_balances as TokenBalanceMode['silo-deposits'])?.[ + getTokenIndex(token) + ]?.deposited?.amount || ZERO_BN ); }, [_balances, balancesType, balanceFromInternal] @@ -245,7 +246,7 @@ const TokenSelectDialog: TokenSelectDialogC = React.memo( {filteredTokenList ? filteredTokenList.map((_token) => { - const tokenBalance = getBalance(_token.address); + const tokenBalance = getBalance(_token); return ( The interest rate for Sowing Beans. Beanstalk logarithmically - increases the Temperature for the first 25 blocks of each Season up + increases the Temperature for the first 5 minutes of each Season up to the Max Temperature. } diff --git a/projects/ui/src/components/Nav/Buttons/PriceButton.tsx b/projects/ui/src/components/Nav/Buttons/PriceButton.tsx index 35e1aecd0..8b7be7b08 100644 --- a/projects/ui/src/components/Nav/Buttons/PriceButton.tsx +++ b/projects/ui/src/components/Nav/Buttons/PriceButton.tsx @@ -37,7 +37,6 @@ import { useTokens } from '~/hooks/beanstalk/useTokens'; import { Token } from '@beanstalk/sdk'; import BigNumber from 'bignumber.js'; import useChainId from '~/hooks/chain/useChainId'; -import { IS_DEV } from '~/util'; import FolderMenu from '../FolderMenu'; type TokenPriceEntry = { @@ -290,7 +289,7 @@ const PriceButton: FC = ({ ...props }) => { poolState={beanPools[pool.address]} useTWA={showTWA} ButtonProps={{ - href: `${IS_DEV ? 'http://localhost:2424/#/wells' : BASIN_WELL_LINK}/${chainId}/${pool.address}`, + href: `${BASIN_WELL_LINK}${chainId}/${pool.address}`, target: '_blank', rel: 'noreferrer', }} diff --git a/projects/ui/src/components/Silo/Token/TokenAbout.tsx b/projects/ui/src/components/Silo/Token/TokenAbout.tsx index 09218b28f..051a334d9 100644 --- a/projects/ui/src/components/Silo/Token/TokenAbout.tsx +++ b/projects/ui/src/components/Silo/Token/TokenAbout.tsx @@ -5,7 +5,7 @@ import BigNumber from 'bignumber.js'; import { Link } from 'react-router-dom'; import Row from '~/components/Common/Row'; import { useAppSelector } from '~/state'; -import { BASIN_WELL_LINK, ZERO_BN } from '~/constants'; +import { BASE_ARBISCAN_ADDR_LINK, BASIN_WELL_LINK, ZERO_BN } from '~/constants'; import { InfoOutlined } from '@mui/icons-material'; import usePools from '~/hooks/beanstalk/usePools'; import useSdk from '~/hooks/sdk'; @@ -42,7 +42,7 @@ const TokenAbout = ({ token }: { token: Token }) => { Token address { const seedReward = new BigNumber(token.rewards?.seeds?.toHuman() || '0'); const stalkReward = new BigNumber(token.rewards?.stalk?.toHuman() || '0'); + const navigate = useNavigate(); return ( @@ -26,6 +28,7 @@ const TokenDepositRewards = ({ token }: { token: Token }) => { color="secondary" size="small" endIcon={} + onClick={() => navigate('/analytics')} > View rewards @@ -90,6 +93,7 @@ const TokenDepositRewards = ({ token }: { token: Token }) => { color="secondary" size="small" endIcon={} + onClick={() => navigate('/analytics')} > View Bean Supply diff --git a/projects/ui/src/components/Silo/Token/TokenDepositsOverview.tsx b/projects/ui/src/components/Silo/Token/TokenDepositsOverview.tsx index 185487ef2..0065bfa49 100644 --- a/projects/ui/src/components/Silo/Token/TokenDepositsOverview.tsx +++ b/projects/ui/src/components/Silo/Token/TokenDepositsOverview.tsx @@ -63,9 +63,10 @@ const TokenDepositsOverview = ({ token }: Props) => { - {' Deposits'} + Deposits {!isBEAN && ( @@ -87,9 +88,10 @@ const TokenDepositsOverview = ({ token }: Props) => { - {' Deposits'} + Deposits )} diff --git a/projects/ui/src/components/Swap/Actions/Swap.tsx b/projects/ui/src/components/Swap/Actions/Swap.tsx index 0054a42a1..d8c205bca 100644 --- a/projects/ui/src/components/Swap/Actions/Swap.tsx +++ b/projects/ui/src/components/Swap/Actions/Swap.tsx @@ -20,6 +20,7 @@ import { NativeToken, BeanSwapNodeQuote, BeanSwapOperation, + BeanstalkSDK, } from '@beanstalk/sdk'; import { FormApprovingState, @@ -35,14 +36,13 @@ import { import { TokenSelectMode } from '~/components/Common/Form/TokenSelectDialog'; import TokenInputField from '~/components/Common/Form/TokenInputField'; import FarmModeField from '~/components/Common/Form/FarmModeField'; -import { Beanstalk } from '~/generated/index'; import { ZERO_BN } from '~/constants'; import { useBeanstalkContract } from '~/hooks/ledger/useContract'; import useFarmerBalances from '~/hooks/farmer/useFarmerBalances'; import { useSigner } from '~/hooks/ledger/useSigner'; import useAccount from '~/hooks/ledger/useAccount'; -import { MinBN, tokenIshEqual } from '~/util'; +import { getTokenIndex, MinBN, tokenIshEqual } from '~/util'; import { IconSize } from '~/components/App/muiTheme'; import TransactionToast from '~/components/Common/TxnToast'; import { useFetchFarmerBalances } from '~/state/farmer/balances/updater'; @@ -62,6 +62,7 @@ import useQuoteWithParams, { QuoteHandlerResultNew, QuoteHandlerWithParams, } from '~/hooks/ledger/useQuoteWithParams'; +import { useMinTokensIn } from '~/hooks/beanstalk/useMinTokensIn'; /// --------------------------------------------------------------- @@ -113,7 +114,7 @@ const Quoting = ( const SwapForm: FC< FormikProps & { balances: ReturnType; - beanstalk: Beanstalk; + beanstalk: BeanstalkSDK['contracts']['beanstalk']; tokenList: (ERC20Token | NativeToken)[]; defaultValues: SwapFormValues; } @@ -161,15 +162,17 @@ const SwapForm: FC< [balances] ); + const minTokenIn = useMinTokensIn(tokenIn, tokenOut); + const [balanceIn, balanceInInput, balanceInMax] = useMemo(() => { - const _balanceIn = balances[tokenIn.address]; + const _balanceIn = balances[getTokenIndex(tokenIn)]; if (tokensMatch) { const _balanceInMax = _balanceIn[modeIn === FarmFromMode.INTERNAL ? 'internal' : 'external']; return [_balanceIn, _balanceInMax, _balanceInMax] as const; } return [_balanceIn, _balanceIn, _balanceIn?.total || ZERO_BN] as const; - }, [balances, modeIn, tokenIn.address, tokensMatch]); + }, [balances, modeIn, tokenIn, tokensMatch]); const [getAmountsBySource] = useGetBalancesUsedBySource({ tokens: values.tokensIn, @@ -197,12 +200,14 @@ const SwapForm: FC< ? FarmFromMode.INTERNAL : FarmFromMode.EXTERNAL ); + } else if (tokenIn.equals(sdk.tokens.ETH)) { + setFromOptions([BalanceFrom.EXTERNAL]); } else { setFromOptions([BalanceFrom.TOTAL]); setBalanceFromIn(BalanceFrom.TOTAL); setFieldValue('modeIn', FarmFromMode.INTERNAL_EXTERNAL); } - }, [tokensMatch, modeIn, modeOut, setFieldValue]); + }, [tokensMatch, modeIn, modeOut, tokenIn, sdk, setFieldValue]); const noBalance = !balanceInMax?.gt(0); const expectedFromMode = balanceIn @@ -237,6 +242,7 @@ const SwapForm: FC< inputToken.fromHuman(_amountIn.toString()), slippage ); + if (!quoteData) { throw new Error('No route found.'); } @@ -399,8 +405,21 @@ const SwapForm: FC< /// Flip destinations. setFieldValue('modeIn', modeOut); setFieldValue('modeOut', modeIn); + } else { + setFieldValue('tokensIn.0', { ...values.tokenOut }); + setFieldValue('tokenOut', { + ...values.tokensIn[0], + amount: undefined, + }); } - }, [modeIn, modeOut, setFieldValue, tokensMatch]); + }, [ + modeIn, + modeOut, + setFieldValue, + tokensMatch, + values.tokenOut, + values.tokensIn, + ]); // if tokenIn && tokenOut are equal and no balances are found, reverse positions. // This prevents setting of internal balance of given token when there is none @@ -420,8 +439,8 @@ const SwapForm: FC< (_tokens: Set) => { if (tokenSelect === 'tokenOut') { const newTokenOut = Array.from(_tokens)[0]; - setFieldValue('tokenOut.token', newTokenOut); if (tokenIn === newTokenOut) handleTokensEqual(); + setFieldValue('tokenOut.token', newTokenOut); } else if (tokenSelect === 'tokensIn') { const newTokenIn = Array.from(_tokens)[0]; setFieldValue('tokensIn.0.token', newTokenIn); @@ -512,6 +531,7 @@ const SwapForm: FC< ] : undefined } + min={minTokenIn} balance={balanceInInput} quote={quotingOut ? Quoting : undefined} onChange={handleChangeAmountIn} @@ -811,13 +831,12 @@ function getBeanSwapOperation( 'Output token does not match quote. Please refresh the quote.' ); } - if (!quote.sellAmount.eq(amountIn.toNumber())) { + if (!amountIn.eq(quote.sellAmount.toHuman())) { throw new Error( "Input amount doesn't match quote. Please refresh the quote." ); } - const formFmountOutTV = tokenOut.fromHuman(amountOut.toString()); - if (!quote.buyAmount.eq(formFmountOutTV)) { + if (!amountOut.eq(quote.buyAmount.toHuman())) { throw new Error( "Output amount doesn't match quote. Please refresh the quote." ); diff --git a/projects/ui/src/constants/links.ts b/projects/ui/src/constants/links.ts index 203af19be..2db927990 100644 --- a/projects/ui/src/constants/links.ts +++ b/projects/ui/src/constants/links.ts @@ -48,6 +48,8 @@ export const HOW_TO_MM_PATH = /* Base Links */ export const BASE_ETHERSCAN_TX_LINK = 'https://etherscan.io/tx/'; export const BASE_ETHERSCAN_ADDR_LINK = 'https://etherscan.io/address/'; +export const BASE_ARBISCAN_TX_LINK = 'https://arbiscan.io/tx/'; +export const BASE_ARBISCAN_ADDR_LINK = 'https://arbiscan.io/address/'; /* Informational Links */ export const MEDIUM_INTEREST_LINK = diff --git a/projects/ui/src/hooks/beanstalk/useMinTokensIn.ts b/projects/ui/src/hooks/beanstalk/useMinTokensIn.ts new file mode 100644 index 000000000..410b62009 --- /dev/null +++ b/projects/ui/src/hooks/beanstalk/useMinTokensIn.ts @@ -0,0 +1,51 @@ +import { Token } from '@beanstalk/sdk'; +import BigNumber from 'bignumber.js'; +import { useAppSelector } from '~/state'; +import { getTokenIndex, tokenIshEqual } from '~/util'; +import { useMemo } from 'react'; +import { ZERO_BN } from '~/constants'; +import usePrice from './usePrice'; +import { useBalanceTokens } from './useTokens'; + +export function useMinTokensIn(tokenIn: Token, tokenOut: Token) { + const priceMap = useAppSelector((s) => s._beanstalk.tokenPrices); + const tokens = useBalanceTokens(); + const beanPrice = usePrice(); + + const min = useMemo(() => { + const getUsdValue = (t: Token) => { + if (tokenIshEqual(tokens.BEAN, t)) { + return beanPrice; + } + return priceMap[getTokenIndex(t)]; + }; + + const usdTokenIn1 = getUsdValue(tokenIn); + const usdTokenOut1 = getUsdValue(tokenOut); + + if (!usdTokenIn1 || !usdTokenOut1) { + return new BigNumber(1e-6); + } + + if (tokenIn.equals(tokens.ETH)) { + if (tokenOut.equals(tokens.WETH)) return ZERO_BN; + } + + if (tokenOut.equals(tokens.WETH)) { + if (tokenIn.equals(tokens.ETH)) return ZERO_BN; + } + + if (tokenIshEqual(tokenIn, tokenOut)) { + return ZERO_BN; + } + + if (tokenIn.decimals >= 8) { + return new BigNumber(10).pow(-8); + } + return new BigNumber(10).pow(-tokenIn.decimals); + }, [tokens, beanPrice, priceMap, tokenIn, tokenOut]); + + console.log('min: ', min.toString()); + + return min; +} diff --git a/projects/ui/src/state/bean/pools/updater.ts b/projects/ui/src/state/bean/pools/updater.ts index 11301018a..9ea1a42e2 100644 --- a/projects/ui/src/state/bean/pools/updater.ts +++ b/projects/ui/src/state/bean/pools/updater.ts @@ -37,14 +37,13 @@ export const useFetchPools = () => { const BEAN = sdk.tokens.BEAN; const priceAndBeanCalls = [ - makePriceMulticall(beanstalkPrice.address), makeTokenTotalSupplyMulticall(BEAN), makeTotalDeltaBMulticall(beanstalk.address), ]; const lpMulticall = makeLPMulticall(beanstalk.address, poolsArr); - const [priceAndBean, _lpResults] = await Promise.all([ - // fetch [price, bean.totalSupply, totalDeltaB] + const [priceResult, poolsResult, _lpResults] = await Promise.all([ + beanstalkPrice.price(), multicall(config, { contracts: priceAndBeanCalls, allowFailure: true, @@ -62,15 +61,15 @@ export const useFetchPools = () => { console.debug(`${pageContext} MULTICALL RESULTS: `, { lpMulticall, + priceResult, priceAndBeanCalls, _lpResults, - priceAndBean, + poolsResult, }); - const [priceResult, beanTotalSupply, totalDeltaB] = [ - extract(priceAndBean[0], 'price'), - extract(priceAndBean[1], 'bean.totalSupply'), - extract(priceAndBean[2], 'totalDeltaB'), + const [beanTotalSupply, totalDeltaB] = [ + extract(poolsResult[0], 'bean.totalSupply'), + extract(poolsResult[1], 'totalDeltaB'), ]; const lpResults = _lpResults.reduce>( @@ -186,22 +185,6 @@ export default PoolsUpdater; // ------------------------------------------ // Types -type PriceResultStruct = { - price: bigint; - liquidity: bigint; - deltaB: bigint; - ps: { - pool: string; - tokens: [string, string]; - balances: [bigint, bigint]; - price: bigint; - liquidity: bigint; - deltaB: bigint; - lpUsd: bigint; - lpBdv: bigint; - }[]; -}; - type LPResultType = { deltaB: bigint | null; supply: bigint | null; diff --git a/projects/ui/src/state/beanstalk/field/updater.ts b/projects/ui/src/state/beanstalk/field/updater.ts index 395e2dbeb..5f8a29939 100644 --- a/projects/ui/src/state/beanstalk/field/updater.ts +++ b/projects/ui/src/state/beanstalk/field/updater.ts @@ -34,7 +34,7 @@ export const useFetchBeanstalkField = () => { })), beanstalk.temperature().then(tokenResult(BEAN)), // FIXME beanstalk.maxTemperature().then(tokenResult(BEAN)), // FIXME - ] as const); + ]); console.debug('[beanstalk/field/useBeanstalkField] RESULT', { harvestableIndex: harvestableIndex.toString(), diff --git a/projects/ui/src/util/BigNumber.ts b/projects/ui/src/util/BigNumber.ts index c05be3ea0..2ead7ce2b 100644 --- a/projects/ui/src/util/BigNumber.ts +++ b/projects/ui/src/util/BigNumber.ts @@ -6,7 +6,7 @@ import { toTokenUnitsBN } from './Tokens'; export const BN = (v: BignumberJS.Value) => new BignumberJS(v); -BignumberJS.config({ EXPONENTIAL_AT: 1e9 }); +// BignumberJS.config({ EXPONENTIAL_AT: 1e9 }); // @ts-ignore // BigNumber.prototype.toJSON = function toJSON() { diff --git a/protocol/abi/MockBeanstalk.json b/protocol/abi/MockBeanstalk.json index 3ef119f6e..84624cca5 100644 --- a/protocol/abi/MockBeanstalk.json +++ b/protocol/abi/MockBeanstalk.json @@ -5060,6 +5060,49 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getUsedConvertCapacity", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "overallConvertCapacityUsed", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "well", + "type": "address" + }, + { + "internalType": "uint256", + "name": "wellConvertCapacityUsed", + "type": "uint256" + } + ], + "internalType": "struct ConvertGettersFacet.WellConvertCapacityUsed[]", + "name": "wellConvertCapacityUsed", + "type": "tuple[]" + } + ], + "internalType": "struct ConvertGettersFacet.ConvertCapacityUsed", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -13678,4 +13721,4 @@ "stateMutability": "nonpayable", "type": "function" } -] \ No newline at end of file +]