From fa004c7e59a0df87e77db19d8fbacc1457e243cf Mon Sep 17 00:00:00 2001 From: Spacebean Date: Fri, 20 Sep 2024 01:17:30 -0600 Subject: [PATCH] feat: add base convert + update gql schema --- .../src/components/Silo/Actions/Convert.tsx | 960 ------------------ projects/ui/src/graph/graphql.schema.json | 46 +- .../ui/src/graph/schema-snapshot1.graphql | 1 + .../lib/PipelineConvert/usePipelineConvert.ts | 61 +- 4 files changed, 81 insertions(+), 987 deletions(-) delete mode 100644 projects/ui/src/components/Silo/Actions/Convert.tsx diff --git a/projects/ui/src/components/Silo/Actions/Convert.tsx b/projects/ui/src/components/Silo/Actions/Convert.tsx deleted file mode 100644 index 4b82de42f6..0000000000 --- a/projects/ui/src/components/Silo/Actions/Convert.tsx +++ /dev/null @@ -1,960 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Box, Stack, Typography, Tooltip, TextField } from '@mui/material'; -import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; -import { Form, Formik, FormikHelpers, FormikProps } from 'formik'; -import BigNumber from 'bignumber.js'; -import { - Token, - ERC20Token, - NativeToken, - DataSource, - BeanstalkSDK, - TokenValue, - ConvertDetails, - FarmToMode, - FarmFromMode, -} from '@beanstalk/sdk'; -import { useSelector } from 'react-redux'; -import { - FormStateNew, - FormTxnsFormState, - SettingInput, - SmartSubmitButton, - TxnSettings, -} from '~/components/Common/Form'; -import TxnPreview from '~/components/Common/Form/TxnPreview'; -import TxnSeparator from '~/components/Common/Form/TxnSeparator'; -import PillRow from '~/components/Common/Form/PillRow'; -import { TokenSelectMode } from '~/components/Common/Form/TokenSelectDialog'; -import { displayBN, displayFullBN, MaxBN, MinBN } from '~/util/Tokens'; -import { ZERO_BN } from '~/constants'; -import useToggle from '~/hooks/display/useToggle'; -import { tokenValueToBN, bnToTokenValue, transform } from '~/util'; -import { FarmerSilo } from '~/state/farmer/silo'; -import useSeason from '~/hooks/beanstalk/useSeason'; -import TransactionToast from '~/components/Common/TxnToast'; -import useBDV from '~/hooks/beanstalk/useBDV'; -import TokenIcon from '~/components/Common/TokenIcon'; -import { useFetchPools } from '~/state/bean/pools/updater'; -import { ActionType } from '~/util/Actions'; -import useFarmerSilo from '~/hooks/farmer/useFarmerSilo'; -import { FC } from '~/types'; -import useFormMiddleware from '~/hooks/ledger/useFormMiddleware'; -import TokenSelectDialogNew from '~/components/Common/Form/TokenSelectDialogNew'; -import TokenQuoteProviderWithParams from '~/components/Common/Form/TokenQuoteProviderWithParams'; -import useSdk from '~/hooks/sdk'; -import { QuoteHandlerWithParams } from '~/hooks/ledger/useQuoteWithParams'; -import useAccount from '~/hooks/ledger/useAccount'; -import WarningAlert from '~/components/Common/Alert/WarningAlert'; -import TokenOutput from '~/components/Common/Form/TokenOutput'; -import TxnAccordion from '~/components/Common/TxnAccordion'; -import AdditionalTxnsAccordion from '~/components/Common/Form/FormTxn/AdditionalTxnsAccordion'; -import useFarmerFormTxnsActions from '~/hooks/farmer/form-txn/useFarmerFormTxnActions'; -import useAsyncMemo from '~/hooks/display/useAsyncMemo'; -import AddPlantTxnToggle from '~/components/Common/Form/FormTxn/AddPlantTxnToggle'; -import FormTxnProvider from '~/components/Common/Form/FormTxnProvider'; -import useFormTxnContext from '~/hooks/sdk/useFormTxnContext'; -import { FormTxn, ConvertFarmStep } from '~/lib/Txn'; -import usePlantAndDoX from '~/hooks/farmer/form-txn/usePlantAndDoX'; -import StatHorizontal from '~/components/Common/StatHorizontal'; -import { BeanstalkPalette, FontSize } from '~/components/App/muiTheme'; -import { AppState } from '~/state'; - -// ----------------------------------------------------------------------- - -type ConvertFormValues = FormStateNew & { - settings: { - slippage: number; - }; - maxAmountIn: BigNumber | undefined; - tokenOut: Token | undefined; -} & FormTxnsFormState; - -type ConvertQuoteHandlerParams = { - slippage: number; - isConvertingPlanted: boolean; -}; - -// ----------------------------------------------------------------------- - -const ConvertForm: FC< - FormikProps & { - /** List of tokens that can be converted to. */ - tokenList: (ERC20Token | NativeToken)[]; - /** Farmer's silo balances */ - siloBalances: FarmerSilo['balances']; - handleQuote: QuoteHandlerWithParams; - currentSeason: BigNumber; - /** other */ - sdk: BeanstalkSDK; - conversion: ConvertDetails; - plantAndDoX: ReturnType; - } -> = ({ - tokenList, - siloBalances, - handleQuote, - plantAndDoX, - sdk, - // Formik - values, - isSubmitting, - setFieldValue, - conversion, -}) => { - /// Local state - const [isTokenSelectVisible, showTokenSelect, hideTokenSelect] = useToggle(); - const getBDV = useBDV(); - const [isChopping, setIsChopping] = useState(false); - const [confirmText, setConfirmText] = useState(''); - const [choppingConfirmed, setChoppingConfirmed] = useState(false); - const unripeTokens = useSelector( - (_state) => _state._bean.unripe - ); - - const plantCrate = plantAndDoX?.crate?.bn; - - /// Extract values from form state - const tokenIn = values.tokens[0].token; // converting from token - const amountIn = values.tokens[0].amount; // amount of from token - const tokenOut = values.tokenOut; // converting to token - const amountOut = values.tokens[0].amountOut; // amount of to token - const maxAmountIn = values.maxAmountIn; - const canConvert = maxAmountIn?.gt(0) || false; - - // FIXME: these use old structs instead of SDK - const siloBalance = siloBalances[tokenIn.address]; - const depositedAmount = siloBalance?.deposited.convertibleAmount || ZERO_BN; - - const isQuoting = values.tokens[0].quoting || false; - const slippage = values.settings.slippage; - - const isUsingPlanted = Boolean( - values.farmActions.primary?.includes(FormTxn.PLANT) && - sdk.tokens.BEAN.equals(tokenIn) - ); - - const totalAmountIn = - isUsingPlanted && plantCrate - ? (amountIn || ZERO_BN).plus(plantCrate.amount) - : amountIn; - - /// Derived form state - let isReady = false; - let buttonLoading = false; - let buttonContent = 'Convert'; - let bdvOut: BigNumber; // the BDV received after re-depositing `amountOut` of `tokenOut`. - let bdvIn: BigNumber; // BDV of amountIn. - let depositsBDV: BigNumber; // BDV of the deposited crates. - let deltaBDV: BigNumber | undefined; // the change in BDV during the convert. should always be >= 0. - let deltaStalk; // the change in Stalk during the convert. should always be >= 0. - let deltaSeedsPerBDV; // change in seeds per BDV for this pathway. ex: bean (2 seeds) -> bean:3crv (4 seeds) = +2 seeds. - let deltaSeeds; // the change in seeds during the convert. - - const txnActions = useFarmerFormTxnsActions({ mode: 'plantToggle' }); - - /// Change button state and prepare outputs - if (depositedAmount.eq(0) && (!plantCrate || plantCrate.amount.eq(0))) { - buttonContent = 'Nothing to Convert'; - } else if (values.maxAmountIn === null) { - if (values.tokenOut) { - buttonContent = 'Refreshing convert data...'; - buttonLoading = false; - } else { - buttonContent = 'No output selected'; - buttonLoading = false; - } - } else if (!canConvert) { - // buttonContent = 'Pathway unavailable'; - } else { - buttonContent = isChopping ? 'Chop and Convert' : 'Convert'; - if ( - tokenOut && - (amountOut?.gt(0) || isUsingPlanted) && - totalAmountIn?.gt(0) - ) { - isReady = true; - bdvOut = getBDV(tokenOut).times(amountOut || ZERO_BN); - bdvIn = getBDV(tokenIn).times(totalAmountIn || ZERO_BN); - depositsBDV = transform(conversion.bdv.abs(), 'bnjs'); - deltaBDV = MaxBN(bdvOut.minus(depositsBDV), ZERO_BN); - deltaStalk = MaxBN( - tokenValueToBN(tokenOut.getStalk(bnToTokenValue(tokenOut, deltaBDV))), - ZERO_BN - ); - deltaSeedsPerBDV = tokenOut - .getSeeds() - .sub(tokenValueToBN(tokenIn.getSeeds()).toNumber()); - deltaSeeds = tokenValueToBN( - tokenOut - .getSeeds(bnToTokenValue(tokenOut, bdvOut)) // seeds for depositing this token with new BDV - .sub(bnToTokenValue(tokenOut, conversion.seeds.abs())) - ); // seeds lost when converting - } - } - - useEffect(() => { - if (isChopping) { - if (confirmText.toUpperCase() === 'CHOP MY ASSETS') { - setChoppingConfirmed(true); - } else { - setChoppingConfirmed(false); - } - } else { - setChoppingConfirmed(true); - } - }, [isChopping, confirmText, setChoppingConfirmed]); - - function getBDVTooltip(instantBDV: BigNumber, depositBDV: BigNumber) { - return ( - - - ~{displayFullBN(instantBDV, 2, 2)} - - - ~{displayFullBN(depositBDV, 2, 2)} - - - ); - } - - function showOutputBDV() { - if (isChopping) return bdvOut || ZERO_BN; - return MaxBN(depositsBDV || ZERO_BN, bdvOut || ZERO_BN); - } - - /// When a new output token is selected, reset maxAmountIn. - const handleSelectTokenOut = useCallback( - async (_tokens: Set) => { - const arr = Array.from(_tokens); - if (arr.length !== 1) throw new Error(); - const _tokenOut = arr[0]; - /// only reset if the user clicked a different token - if (tokenOut !== _tokenOut) { - setFieldValue('tokenOut', _tokenOut); - setFieldValue('maxAmountIn', null); - setConfirmText(''); - } - }, - [setFieldValue, tokenOut] - ); - - useEffect(() => { - setConfirmText(''); - }, [amountIn]); - - /// When `tokenIn` or `tokenOut` changes, refresh the - /// max amount that the user can input of `tokenIn`. - /// FIXME: flash when clicking convert tab - useEffect(() => { - (async () => { - if (tokenOut) { - const maxAmount = await ConvertFarmStep.getMaxConvert( - sdk, - tokenIn, - tokenOut - ); - const _maxAmountIn = tokenValueToBN(maxAmount); - setFieldValue('maxAmountIn', _maxAmountIn); - - const _maxAmountInStr = tokenIn.amount(_maxAmountIn.toString()); - console.debug('[Convert][maxAmountIn]: ', _maxAmountInStr); - - // Figure out if we're chopping - const chopping = - (tokenIn.address === sdk.tokens.UNRIPE_BEAN.address && - tokenOut?.address === sdk.tokens.BEAN.address) || - (tokenIn.address === sdk.tokens.UNRIPE_BEAN_WSTETH.address && - tokenOut?.address === sdk.tokens.BEAN_WSTETH_WELL_LP.address); - - setIsChopping(chopping); - } - })(); - }, [sdk, setFieldValue, tokenIn, tokenOut]); - - const quoteHandlerParams = useMemo( - () => ({ - slippage: slippage, - isConvertingPlanted: isUsingPlanted, - }), - [slippage, isUsingPlanted] - ); - const maxAmountUsed = - totalAmountIn && maxAmountIn ? totalAmountIn.div(maxAmountIn) : null; - - const disabledFormActions = useMemo( - () => (tokenIn.isUnripe ? [FormTxn.ENROOT] : undefined), - [tokenIn.isUnripe] - ); - - const getConvertWarning = () => { - let pool = tokenIn.isLP ? tokenIn.symbol : tokenOut!.symbol; - if (tokenOut && !tokenOut.equals(sdk.tokens.BEAN_CRV3_LP)) { - pool += ' Well'; - } else { - pool += ' pool'; - } - if (['urBEANETH', 'urBEAN'].includes(tokenIn.symbol)) pool = 'BEANETH Well'; - - const lowerOrGreater = - tokenIn.isLP || tokenIn.symbol === 'urBEANETH' ? 'less' : 'greater'; - - const message = `${tokenIn.symbol} can only be Converted to ${tokenOut?.symbol} when deltaB in the ${pool} is ${lowerOrGreater} than 0.`; - - return message; - }; - - const chopPercent = unripeTokens[tokenIn?.address || 0]?.chopPenalty || 0; - - return ( -
- - - {/* User Input: token amount */} - - _amountOut && - deltaBDV && ( - - - - ~{displayFullBN(depositsBDV, 2)} BDV - - - - - ) - } - tokenSelectLabel={tokenIn.symbol} - disabled={ - !values.maxAmountIn || // still loading `maxAmountIn` - values.maxAmountIn.eq(0) // = 0 means we can't make this conversion - } - params={quoteHandlerParams} - /> - {!canConvert && tokenOut && maxAmountIn ? null : ( - - )} - {/* User Input: destination token */} - {depositedAmount.gt(0) ? ( - - {tokenOut ? : null} - {tokenOut?.symbol || 'Select token'} - - ) : null} - - {/* Warning Alert */} - {!canConvert && tokenOut && maxAmountIn && depositedAmount.gt(0) ? ( - - - {getConvertWarning()} -
-
-
- ) : null} - {/* Outputs */} - {totalAmountIn && - tokenOut && - maxAmountIn && - (amountOut?.gt(0) || isUsingPlanted) ? ( - <> - - - {isChopping && ( - - You will forfeit {displayBN(chopPercent)}% your claim to - future Ripe assets through this transaction -
-
- )} - - - Converting will increase the BDV of your Deposit by{' '} - {displayFullBN(deltaBDV || ZERO_BN, 6)} - {deltaBDV?.gt(0) ? ', resulting in a gain of Stalk' : ''}. - - ) : ( - <> - The BDV of your Deposit won't change with this - Convert. - - ) - } - /> - - Converting from {tokenIn.symbol} to {tokenOut.symbol}{' '} - results in{' '} - {!deltaSeedsPerBDV || deltaSeedsPerBDV.eq(0) - ? 'no change in SEEDS per BDV' - : `a ${ - deltaSeedsPerBDV.gt(0) ? 'gain' : 'loss' - } of ${deltaSeedsPerBDV.abs().toHuman()} Seeds per BDV`} - . - - } - /> -
- - {/* Warnings */} - {maxAmountUsed && maxAmountUsed.gt(0.9) ? ( - - - You are converting{' '} - {displayFullBN(maxAmountUsed.times(100), 4, 0)}% of the way to - the peg. When Converting all the way to the peg, the Convert - may fail due to a small amount of slippage in the direction of - the peg. - - - ) : null} - - {/* Add-on transactions */} - {!isUsingPlanted && ( - - )} - - {/* Transation preview */} - - - - - - - ) : null} - - {isReady && isChopping && ( - - - This conversion will effectively perform a CHOP opperation. Please - confirm you understand this by typing{' '} - "CHOP MY ASSETS"below. - - setConfirmText(e.target.value)} - sx={{ - background: '#f5d1d1', - borderRadius: '10px', - border: '1px solid red', - input: { color: '#880202', textTransform: 'uppercase' }, - }} - /> - - )} - - {/* Submit */} - - {buttonContent} - -
- - ); -}; - -// ----------------------------------------------------------------------- - -const ConvertPropProvider: FC<{ - fromToken: ERC20Token | NativeToken; -}> = ({ fromToken }) => { - const sdk = useSdk(); - - /// Token List - const [tokenList, initialTokenOut] = useMemo(() => { - // We don't support native token converts - if (fromToken instanceof NativeToken) return [[], undefined]; - const paths = sdk.silo.siloConvert.getConversionPaths(fromToken); - const _tokenList = paths.filter((_token) => !_token.equals(fromToken)); - return [ - _tokenList, // all available tokens to convert to - _tokenList?.[0], // tokenOut is the first available token that isn't the fromToken - ]; - }, [sdk, fromToken]); - - /// Beanstalk - const season = useSeason(); - const [refetchPools] = useFetchPools(); - - /// Farmer - const farmerSilo = useFarmerSilo(); - const farmerSiloBalances = farmerSilo.balances; - const account = useAccount(); - - /// Temporary solution. Remove this when we move the site to use the new sdk types. - const [farmerBalances, refetchFarmerBalances] = useAsyncMemo(async () => { - if (!account) return undefined; - console.debug( - `[Convert] Fetching silo balances for SILO:${fromToken.symbol}` - ); - return sdk.silo.getBalance(fromToken, account, { - source: DataSource.LEDGER, - }); - }, [account, sdk]); - - /// Form - const middleware = useFormMiddleware(); - const { txnBundler, plantAndDoX, refetch } = useFormTxnContext(); - const [conversion, setConversion] = useState({ - actions: [], - amount: TokenValue.ZERO, - bdv: TokenValue.ZERO, - crates: [], - seeds: TokenValue.ZERO, - stalk: TokenValue.ZERO, - }); - - const initialValues: ConvertFormValues = useMemo( - () => ({ - // Settings - settings: { - slippage: 0.05, - }, - // Token Inputs - tokens: [ - { - token: fromToken, - amount: undefined, - quoting: false, - amountOut: undefined, - }, - ], - // Convert data - maxAmountIn: undefined, - // Token Outputs - tokenOut: initialTokenOut, - farmActions: { - preset: fromToken.isLP || fromToken.isUnripe ? 'noPrimary' : 'plant', - primary: undefined, - secondary: undefined, - implied: [FormTxn.MOW], - }, - }), - [fromToken, initialTokenOut] - ); - - /// Handlers - // This handler does not run when _tokenIn = _tokenOut (direct deposit) - const handleQuote = useCallback< - QuoteHandlerWithParams - >( - async (tokenIn, _amountIn, tokenOut, { slippage, isConvertingPlanted }) => { - try { - if (!farmerBalances?.convertibleDeposits) { - throw new Error('No balances found'); - } - const { plantAction } = plantAndDoX; - - const includePlant = !!(isConvertingPlanted && plantAction); - - const result = await ConvertFarmStep._handleConversion( - sdk, - farmerBalances.convertibleDeposits, - tokenIn, - tokenOut, - tokenIn.amount(_amountIn.toString() || '0'), - season.toNumber(), - slippage, - includePlant ? plantAction : undefined - ); - - setConversion(result.conversion); - - return tokenValueToBN(result.minAmountOut); - } catch (e) { - console.debug('[Convert/handleQuote]: FAILED: ', e); - return new BigNumber('0'); - } - }, - [farmerBalances?.convertibleDeposits, sdk, season, plantAndDoX] - ); - - const onSubmit = useCallback( - async ( - values: ConvertFormValues, - formActions: FormikHelpers - ) => { - let txToast; - try { - middleware.before(); - - /// FormData - const slippage = values?.settings?.slippage; - const tokenIn = values.tokens[0].token; - const tokenOut = values.tokenOut; - const _amountIn = values.tokens[0].amount; - - /// Validation - if (!account) throw new Error('Wallet connection required'); - if (!slippage) throw new Error('No slippage value set.'); - if (!tokenOut) throw new Error('Conversion pathway not set'); - if (!farmerBalances) throw new Error('No balances found'); - - txToast = new TransactionToast({ - loading: 'Converting...', - success: 'Convert successful.', - }); - - let txn; - - const { plantAction } = plantAndDoX; - - const amountIn = tokenIn.amount(_amountIn?.toString() || '0'); // amount of from token - const isPlanting = - plantAndDoX && values.farmActions.primary?.includes(FormTxn.PLANT); - - const convertTxn = new ConvertFarmStep( - sdk, - tokenIn, - tokenOut, - season.toNumber(), - farmerBalances.convertibleDeposits - ); - - const { getEncoded, minAmountOut } = await convertTxn.handleConversion( - amountIn, - slippage, - isPlanting ? plantAction : undefined - ); - - convertTxn.build(getEncoded, minAmountOut); - const actionsPerformed = txnBundler.setFarmSteps(values.farmActions); - - if (!isPlanting) { - const { execute } = await txnBundler.bundle( - convertTxn, - amountIn, - slippage, - 1.2 - ); - - txn = await execute(); - } else { - // Create Advanced Farm operation for alt-route Converts - const farm = sdk.farm.createAdvancedFarm('Alternative Convert'); - - // Get Earned Beans data - const stemTips = await sdk.silo.getStemTip(tokenIn); - const earnedBeans = await sdk.silo.getEarnedBeans(account); - const earnedStem = stemTips.toString(); - const earnedAmount = earnedBeans.toBlockchain(); - - // Plant - farm.add(new sdk.farm.actions.Plant()); - - // Withdraw Planted deposit crate - farm.add( - new sdk.farm.actions.WithdrawDeposit( - tokenIn.address, - earnedStem, - earnedAmount, - FarmToMode.INTERNAL - ) - ); - - // Transfer to Well - farm.add( - new sdk.farm.actions.TransferToken( - tokenIn.address, - sdk.pools.BEAN_ETH_WELL.address, - FarmFromMode.INTERNAL, - FarmToMode.EXTERNAL - ) - ); - - // Create Pipeline operation - const pipe = sdk.farm.createAdvancedPipe('pipelineDeposit'); - - // (Pipeline) - Call sync on Well - pipe.add( - new sdk.farm.actions.WellSync( - sdk.pools.BEAN_ETH_WELL, - tokenIn, - sdk.contracts.pipeline.address - ), - { tag: 'amountToDeposit' } - ); - - // (Pipeline) - Approve transfer of sync output - const approveClipboard = { - tag: 'amountToDeposit', - copySlot: 0, - pasteSlot: 1, - }; - pipe.add( - new sdk.farm.actions.ApproveERC20( - sdk.pools.BEAN_ETH_WELL.lpToken, - sdk.contracts.beanstalk.address, - approveClipboard - ) - ); - - // (Pipeline) - Transfer sync output to Beanstalk - const transferClipboard = { - tag: 'amountToDeposit', - copySlot: 0, - pasteSlot: 2, - }; - pipe.add( - new sdk.farm.actions.TransferToken( - sdk.tokens.BEAN_ETH_WELL_LP.address, - account, - FarmFromMode.EXTERNAL, - FarmToMode.INTERNAL, - transferClipboard - ) - ); - - // Add Pipeline operation to the Advanced Pipe operation - farm.add(pipe); - - // Deposit Advanced Pipe output to Silo - farm.add( - new sdk.farm.actions.Deposit( - sdk.tokens.BEAN_ETH_WELL_LP, - FarmFromMode.INTERNAL - ) - ); - - // Convert the other Deposits as usual - if (amountIn.gt(0)) { - const convertData = sdk.silo.siloConvert.calculateConvert( - tokenIn, - tokenOut, - amountIn, - farmerBalances.convertibleDeposits, - season.toNumber() - ); - const amountOut = await sdk.contracts.beanstalk.getAmountOut( - tokenIn.address, - tokenOut.address, - convertData.amount.toBlockchain() - ); - const _minAmountOut = TokenValue.fromBlockchain( - amountOut.toString(), - tokenOut.decimals - ).mul(1 - slippage); - farm.add( - new sdk.farm.actions.Convert( - sdk.tokens.BEAN, - sdk.tokens.BEAN_ETH_WELL_LP, - amountIn, - _minAmountOut, - convertData.crates - ) - ); - } - - // Mow Grown Stalk - const tokensWithStalk: Map = new Map(); - farmerSilo.stalk.grownByToken.forEach((value, token) => { - if (value.gt(0)) { - tokensWithStalk.set(token, value); - } - }); - if (tokensWithStalk.size > 0) { - farm.add(new sdk.farm.actions.Mow(account, tokensWithStalk)); - } - - const gasEstimate = await farm.estimateGas(earnedBeans, { - slippage: slippage, - }); - const adjustedGas = Math.round( - gasEstimate.toNumber() * 1.2 - ).toString(); - txn = await farm.execute( - earnedBeans, - { slippage: slippage }, - { gasLimit: adjustedGas } - ); - } - - txToast.confirming(txn); - - const receipt = await txn.wait(); - - await refetch(actionsPerformed, { farmerSilo: true }, [ - refetchPools, // update prices to account for pool conversion - refetchFarmerBalances, - ]); - - txToast.success(receipt); - - /// Reset the max Amount In - const _maxAmountIn = await ConvertFarmStep.getMaxConvert( - sdk, - tokenIn, - tokenOut - ); - - formActions.resetForm({ - values: { - ...initialValues, - maxAmountIn: tokenValueToBN(_maxAmountIn), - }, - }); - } catch (err) { - console.error(err); - if (txToast) { - txToast.error(err); - } else { - const errorToast = new TransactionToast({}); - errorToast.error(err); - } - formActions.setSubmitting(false); - } - }, - [ - sdk, - season, - account, - txnBundler, - middleware, - plantAndDoX, - initialValues, - farmerBalances, - farmerSilo, - refetch, - refetchPools, - refetchFarmerBalances, - ] - ); - - return ( - - {(formikProps) => ( - <> - - - - - - )} - - ); -}; - -const Convert: FC<{ - fromToken: ERC20Token | NativeToken; -}> = (props) => ( - - - -); - -export default Convert; diff --git a/projects/ui/src/graph/graphql.schema.json b/projects/ui/src/graph/graphql.schema.json index e0262f1d38..54ea1099c9 100644 --- a/projects/ui/src/graph/graphql.schema.json +++ b/projects/ui/src/graph/graphql.schema.json @@ -128883,6 +128883,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "proposalsCount30d", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "rank", "description": null, @@ -166576,9 +166588,7 @@ "name": "derivedFrom", "description": "creates a virtual field on the entity that may be queried but cannot be set manually through the mappings API.", "isRepeatable": false, - "locations": [ - "FIELD_DEFINITION" - ], + "locations": ["FIELD_DEFINITION"], "args": [ { "name": "field", @@ -166602,20 +166612,14 @@ "name": "entity", "description": "Marks the GraphQL type as indexable entity. Each type that should be an entity is required to be annotated with this directive.", "isRepeatable": false, - "locations": [ - "OBJECT" - ], + "locations": ["OBJECT"], "args": [] }, { "name": "include", "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", "isRepeatable": false, - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "args": [ { "name": "if", @@ -166639,20 +166643,14 @@ "name": "oneOf", "description": "Indicates exactly one field must be supplied and this field must not be `null`.", "isRepeatable": false, - "locations": [ - "INPUT_OBJECT" - ], + "locations": ["INPUT_OBJECT"], "args": [] }, { "name": "skip", "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", "isRepeatable": false, - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "args": [ { "name": "if", @@ -166676,9 +166674,7 @@ "name": "specifiedBy", "description": "Exposes a URL that specifies the behavior of this scalar.", "isRepeatable": false, - "locations": [ - "SCALAR" - ], + "locations": ["SCALAR"], "args": [ { "name": "url", @@ -166702,9 +166698,7 @@ "name": "subgraphId", "description": "Defined a Subgraph ID for an object type", "isRepeatable": false, - "locations": [ - "OBJECT" - ], + "locations": ["OBJECT"], "args": [ { "name": "id", @@ -166726,4 +166720,4 @@ } ] } -} \ No newline at end of file +} diff --git a/projects/ui/src/graph/schema-snapshot1.graphql b/projects/ui/src/graph/schema-snapshot1.graphql index 232815531a..167bab37fc 100644 --- a/projects/ui/src/graph/schema-snapshot1.graphql +++ b/projects/ui/src/graph/schema-snapshot1.graphql @@ -320,6 +320,7 @@ type Space { proposalsCount: Int proposalsCount1d: Int proposalsCount7d: Int + proposalsCount30d: Int rank: Float skin: String strategies: [Strategy] diff --git a/projects/ui/src/lib/PipelineConvert/usePipelineConvert.ts b/projects/ui/src/lib/PipelineConvert/usePipelineConvert.ts index badea22832..eadb058081 100644 --- a/projects/ui/src/lib/PipelineConvert/usePipelineConvert.ts +++ b/projects/ui/src/lib/PipelineConvert/usePipelineConvert.ts @@ -1 +1,60 @@ -export function usePipelineConvert() {} +import { useCallback, useEffect, useState } from 'react'; +import { ERC20Token, AdvancedPipeStruct } from '@beanstalk/sdk'; +import useSdk from '~/hooks/sdk'; + +function getAdvancedPipeCalls( + inputToken: ERC20Token, + outputToken: ERC20Token +): AdvancedPipeStruct[] { + return []; +} + +export interface IUsePipelineConvertReturn { + // Whether the pipeline convert can run it's estimates + runMode: boolean; + // The target token to convert to + setTarget: (token: ERC20Token) => void; +} + +export function usePipelineConvert( + inputToken: ERC20Token, + _outputToken: ERC20Token +): IUsePipelineConvertReturn { + const sdk = useSdk(); + + const [target, handleSetTarget] = useState(_outputToken); + const [runMode, setRunMode] = useState(false); + + const zeroX = sdk.zeroX; + + useEffect(() => { + setRunMode(target.isLP && inputToken.isLP); + }, [inputToken.isLP, target.isLP]); + + // Error validation + useEffect(() => { + const tk = sdk.tokens.findByAddress(inputToken.address); + if (!tk || !sdk.tokens.siloWhitelist.has(tk)) { + throw new Error( + `Token ${inputToken.address} is not whitelisted in the Silo.` + ); + } + }, [inputToken.address, sdk.tokens]); + + const setTarget = useCallback( + (token: ERC20Token) => { + if (sdk.tokens.siloWhitelist.has(token)) { + handleSetTarget(token); + } else { + throw new Error( + `Token ${token.symbol} is not whitelisted in the Silo.` + ); + } + }, + [sdk.tokens.siloWhitelist] + ); + + return { runMode, setTarget }; +} + +export function useConvertPaths() {}