From 8f680fed221f1336cd5d264e37f86a254b236752 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 8 Aug 2023 14:06:41 -0700 Subject: [PATCH 01/22] feat: trying to make the action response state more grokkable/simpler --- .../cactiComponents/ActionResponse.tsx | 214 ++++++++---------- 1 file changed, 99 insertions(+), 115 deletions(-) diff --git a/src/components/cactiComponents/ActionResponse.tsx b/src/components/cactiComponents/ActionResponse.tsx index 44ba72ac..3a59040e 100644 --- a/src/components/cactiComponents/ActionResponse.tsx +++ b/src/components/cactiComponents/ActionResponse.tsx @@ -10,19 +10,16 @@ import { useAccount } from 'wagmi'; import { ActionStepper } from './ActionStepper'; import useApproval, { ApprovalBasicParams } from './hooks/useApproval'; import useBalance from './hooks/useBalance'; -import useSubmitTx, { SEND_ETH_FNNAME, TxBasicParams } from './hooks/useSubmitTx'; +import useSubmitTx, { TxBasicParams } from './hooks/useSubmitTx'; export enum ActionResponseState { - LOADING, // background async checks - DISABLED, // button is disabled (eg. not enough balance, or error with building tx) - - WAITING_FOR_USER, // waiting for user action on wallet, likely confirmation - - READY, // tx ready to go - not submitted. (either approval or tx) - TRANSACTING, // transaction taking place. (either approval or tx) - - SUCCESS, // transaction successful - ERROR, // transaction failed + LOADING = 'LOADING', // background async checks + DISABLED = 'DISABLED', // button is disabled (eg. not enough balance, or error with building tx) + WAITING_FOR_USER = 'WAITING', // waiting for user action on wallet, likely confirmation + READY = 'READY', // tx ready to go - not submitted. (either approval or tx) + TRANSACTING = 'TRANSACTING', // transaction taking place. (either approval or tx) + SUCCESS = 'SUCCESS', // transaction successful + ERROR = 'ERROR', // transaction failed } const StyledButton = tw.button` @@ -59,8 +56,6 @@ export type ActionResponseProps = { stepper?: boolean; onSuccess?: (txReceipt?: TransactionReceipt) => any; onError?: (txHash?: string) => any; - // assertCallParams?: AssertCallBasicParams; - // altAction?: () => Promise; }; /** @@ -82,16 +77,32 @@ export const ActionResponse = ({ const { address } = useAccount(); /** Check for the approval. If no approvalParams, hasAllowance === true and approveTx == undefined */ - const { approveTx, hasAllowance, approvalWaitingOnUser, approvalTransacting } = useApproval( + const { + write: approveTx, + hasAllowance, + isWaitingOnUser: approvalWaitingOnUser, + isPending: approvalTransacting, + isPrepareError: isPrepareApprovalError, + } = useApproval( approvalParams || { tokenAddress: AddressZero, spender: AddressZero, approvalAmount: BigNumber.from(0), - skipApproval: true, // NOTE: apporval is skipped if no approval params are passed in + skipApproval: true, // NOTE: approval is skipped if no approval params are passed in } ); - const { submitTx, isWaitingOnUser, isTransacting, error, isSuccess, receipt, hash } = useSubmitTx( + const { + write: submitTx, + isWaitingOnUser, + isPending, + error, + isSuccess, + receipt, + hash, + isError, + isPrepareError, + } = useSubmitTx( hasAllowance ? txParams : undefined, hasAllowance ? sendParams : undefined, () => null, @@ -106,11 +117,11 @@ export const ActionResponse = ({ undefined, undefined, approvalParams?.skipApproval - ); // TODO figure out better way to infer if eth + ); // button state const [label, setLabel] = useState(); - const [state, setState] = useState(ActionResponseState.LOADING); + const [state, setState] = useState(ActionResponseState.DISABLED); const [action, setAction] = useState(); /** @@ -123,135 +134,108 @@ export const ActionResponse = ({ useEffect(() => { if (approvalParams?.skipApproval || skipBalanceCheck) return setHasEnoughBalance(true); - // check value balance if skipping approval cuz we assume user is using eth - // ( explicitly showing approvalParams === undefined for clarity - as oppposed to !approvalParams) - if (approvalParams === undefined || sendParams?.value! <= ethBal!) - return setHasEnoughBalance(true); + // explicitly showing approvalParams === undefined for clarity - as oppposed to !approvalParams + if (approvalParams === undefined) return setHasEnoughBalance(true); + if (sendParams?.value! >= ethBal!) { + return setHasEnoughBalance(false); + } // check approval token balance if (balance && approvalParams?.approvalAmount) setHasEnoughBalance(balance.gte(approvalParams?.approvalAmount!)); - }, [ - approvalParams?.approvalAmount, - approvalParams?.skipApproval, - balance, - ethBal, - sendParams?.value, - ]); + }, [approvalParams, balance, ethBal, sendParams?.value, skipBalanceCheck]); /** * BUTTON FLOW: * Update all the local states on tx/approval status changes. **/ useEffect(() => { - // case:not enough balance */ - if (!hasEnoughBalance) { - setLabel('Insufficient Balance'); - setState(ActionResponseState.DISABLED); + // tx status/state + if (isSuccess) { + console.log('TX SUCCESS'); + setLabel('Transaction Complete'); + return setState(ActionResponseState.SUCCESS); } - /* -------- APPROVAL FLOW --------- */ - if (!hasAllowance && hasEnoughBalance) { - // case: enough balance, but allowance not sufficient */ - if (approveTx) { - console.log('🦄 ~ file: ActionResponse.tsx:156 ~ useEffect ~ approveTx:', approveTx); - setAction({ name: 'approve', fn: approveTx }); - console.log('READY FOR APPROVAL: Has balance.'); - setLabel(`A token approval is required`); - setState(ActionResponseState.READY); - } else { - console.log('no approval func'); - setLabel(`Could not build the approve tx`); - setState(ActionResponseState.ERROR); - } - - // ACTION: user clicks approve token button + if (isError) { + console.log('TX ERROR'); + setLabel('Transaction Failed'); + return setState(ActionResponseState.ERROR); + } - // case: waiting for wallet interaction*/ - if (approvalWaitingOnUser) { - console.log('Waiting for approval confirmation...'); - setLabel(`Please check your wallet...`); - setState(ActionResponseState.WAITING_FOR_USER); - } + if (isPending) { + console.log('TX IN PROGRESS... '); + setLabel(defaultLabel); + return setState(ActionResponseState.TRANSACTING); + } - // ACTION: user confirms approval in wallet ( or signs permit ) - // case: waiting for the approval transaction */ - if (approvalTransacting) { - console.log('Waiting for approval transaction...'); - setLabel(`Token approval pending...`); - setState(ActionResponseState.TRANSACTING); - } + if (isPrepareError) { + setLabel('Could not prepare tx'); + return setState(ActionResponseState.ERROR); } - /* -------- TRANSACTION FLOW --------- */ - if (hasAllowance && hasEnoughBalance) { - /* case tx/approval success, waiting for tx-building */ - if (!submitTx && !error) { - console.log('Building TX: Has balance and allowance.'); - // if the button is disabled, the label is controlled by the parent widget - !disabled ? setLabel('Validating the transaction...') : setLabel(defaultLabel); - setState(ActionResponseState.LOADING); - } + if (isWaitingOnUser) { + console.log('Waiting for TX confirmation...'); + setLabel(`Please check your wallet...`); + return setState(ActionResponseState.WAITING_FOR_USER); + } - /* case approval success, but trnasaction error with tx-building */ - if (!submitTx && error) { - console.log('Error Building/Validating tx'); - setLabel(`Error validating the transaction.`); - setState(ActionResponseState.ERROR); - onError?.(hash); + // approval status/state + if (!approvalParams?.skipApproval) { + if (isPrepareApprovalError) { + setLabel('Could not prepare approval'); + return setState(ActionResponseState.ERROR); } - /* case tx/approval success, waiting for tx-building */ - if (!!submitTx) { - console.log('READY FOR TX: Has balance and allowance.'); - setLabel(defaultLabel); - setState(ActionResponseState.READY); - setAction({ name: 'submit', fn: submitTx }); + if (approvalTransacting) { + console.log('Waiting for approval transaction...'); + setLabel(`Token approval pending...`); + return setState(ActionResponseState.TRANSACTING); } - // ACTION: user clicks submit button - - // case: waiting for wallet interaction*/ - if (isWaitingOnUser) { - console.log('Waiting for TX confirmation...'); + if (approvalWaitingOnUser) { + console.log('Waiting for approval confirmation...'); setLabel(`Please check your wallet...`); - setState(ActionResponseState.WAITING_FOR_USER); + return setState(ActionResponseState.WAITING_FOR_USER); } + } - // ACTION: user confirms approval in wallet ( or signs permit ) - - /* case tx/approval success */ - if (isTransacting) { - console.log('TX IN PROGRESS... '); - setLabel(defaultLabel); - setState(ActionResponseState.TRANSACTING); - } + // pre-approval and pre-tx state + if (!hasEnoughBalance) { + setLabel('Insufficient Balance'); + return setState(ActionResponseState.DISABLED); + } - if (isSuccess) { - console.log('TX SUCCESS'); - setLabel('Transaction Complete'); - setState(ActionResponseState.SUCCESS); - onSuccess?.(receipt); - } + if (!hasAllowance) { + setLabel('Token approval needed'); + setAction({ name: 'approval', fn: approveTx! }); + return setState(ActionResponseState.READY); } + + // has balance and allowance, and ready to submit tx + setLabel(defaultLabel); + setAction({ name: 'submit', fn: submitTx! }); + setState(ActionResponseState.READY); }, [ - hasEnoughBalance, - hasAllowance, - isWaitingOnUser, - isTransacting, - error, - approvalWaitingOnUser, + approvalParams?.skipApproval, approvalTransacting, - submitTx, + approvalWaitingOnUser, defaultLabel, + hasAllowance, + hasEnoughBalance, + isError, + isPending, + isPrepareApprovalError, + isPrepareError, isSuccess, - disabled, + isWaitingOnUser, + submitTx, ]); /* Set the styling based on the state (Note: always diasbled if 'disabled' from props) */ const extraStyle = stylingByState[disabled ? ActionResponseState.DISABLED : state]; - const handleAction = async () => (action ? await action?.fn() : undefined); + const handleAction = async () => (action ? await action.fn() : undefined); return (
@@ -277,12 +261,12 @@ export const ActionResponse = ({ text-white/70 group-hover:block " > - {error} + {error.message}
)} - {isSuccess && ( + {hash && (
@@ -293,7 +277,7 @@ export const ActionResponse = ({ text-white/70 group-hover:block " > - {receipt?.transactionHash} + {hash}
)} From 9852fe2f6cec04b3b6e6df07da53e830a6bfdbff Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 8 Aug 2023 14:07:08 -0700 Subject: [PATCH 02/22] feat: make the approval and submitTx return data mirror each other (clean) --- .../cactiComponents/hooks/useApproval.tsx | 30 +++++------ .../cactiComponents/hooks/useSubmitTx.tsx | 52 ++++++++----------- 2 files changed, 34 insertions(+), 48 deletions(-) diff --git a/src/components/cactiComponents/hooks/useApproval.tsx b/src/components/cactiComponents/hooks/useApproval.tsx index 6f6318fb..34b0325c 100644 --- a/src/components/cactiComponents/hooks/useApproval.tsx +++ b/src/components/cactiComponents/hooks/useApproval.tsx @@ -46,39 +46,35 @@ const useApproval = (params: ApprovalBasicParams) => { }); // Prepare the approval transaction - doesn't run if address or spender is undefined - const { config } = usePrepareContractWrite({ + const { config, isError: isPrepareError } = usePrepareContractWrite({ address: !params.skipApproval ? tokenAddress : undefined, abi: erc20ABI, functionName: 'approve', args: [spender!, amountToUse], chainId, - // enabled: !!spender && !params.skipApproval, }); - const { - writeAsync: approveTx, - data, - isLoading: approvalWaitingOnUser, - } = useContractWrite(config); + const { writeAsync: approveTx, data, isLoading: isWaitingOnUser } = useContractWrite(config); const { - isError: approvalError, - isLoading: approvalTransacting, - isSuccess: approvalSuccess, + isError, + isLoading: isPending, + isSuccess, } = useWaitForTransaction({ hash: data?.hash, onSuccess: async () => await refetchAllowance(), }); return { - approveTx, + write: !!params.skipApproval ? undefined : approveTx, refetchAllowance, - approvalReceipt: data, - approvalHash: data?.hash, - approvalWaitingOnUser, - approvalTransacting, - approvalError, - approvalSuccess, + receipt: data, + hash: data?.hash, + isPrepareError, + isWaitingOnUser, + isError, + isPending, + isSuccess, hasAllowance: params.skipApproval ? true : allowanceAmount?.gte(amountToUse), // if isETH, then hasAllowance is true, else check if allowanceAmount is greater than amount }; }; diff --git a/src/components/cactiComponents/hooks/useSubmitTx.tsx b/src/components/cactiComponents/hooks/useSubmitTx.tsx index 2bdba2d7..32051dbe 100644 --- a/src/components/cactiComponents/hooks/useSubmitTx.tsx +++ b/src/components/cactiComponents/hooks/useSubmitTx.tsx @@ -42,52 +42,44 @@ const useSubmitTx = ( ) => { const addRecentTx = useAddRecentTransaction(); const { refetch: refetchEthBal } = useBalance(); - const [error, setError] = useState(); - const handleError = (error: Error) => { - console.log(error.message); - if (onError) onError(); - setError(error.message); - }; /** * note: usePrepareContractWrite/usePrepareSend : It only runs if all params are defined - so no duplication * */ /* prepare a write transaction */ - const { config: writeConfig, error: prepareError } = usePrepareContractWrite({ + const { config: writeConfig } = usePrepareContractWrite({ ...params, - onError: handleError, + onError: (e) => console.log('prepare contract write error', e), }); /* prepare a send transaction if the fnName matches the SEND_TRANSACTION unique id */ - const { config: sendConfig } = usePrepareSendTransaction({ - request: { ...(writeConfig.request ?? sendParams) }, + const { config: sendConfig, isError: isPrepareError } = usePrepareSendTransaction({ + request: { ...(writeConfig.request ?? sendParams), gasLimit: 500000 }, enabled: true, - onError: handleError, + onError: (e) => console.log('prepare send error', e), }); /* usePrepped data to run write or send transactions */ - const { - data, - isLoading: isWaitingOnUser, - sendTransactionAsync, - isError, - } = useSendTransaction(sendConfig); + const { data, isLoading: isWaitingOnUser, sendTransactionAsync } = useSendTransaction(sendConfig); /* Use the TX hash to wait for the transaction to be mined */ const { data: receipt, - error: transactError, - isLoading: isTransacting, + error, + isLoading: isPending, + isError, isSuccess, - status, } = useWaitForTransaction({ hash: data?.hash, onSuccess: () => { if (onSuccess) onSuccess(); refetchEthBal(); }, - onError, + onError: (e) => { + console.log('tx error', e); + if (onError) onError(); + }, }); // add the transaction to the recent transactions list for rainbow @@ -102,25 +94,23 @@ const useSubmitTx = ( /* DEVELOPER logging */ useEffect(() => { - if (receipt?.status === 0) { - toast.error(`Transaction Error: ${transactError?.message}`); + if (isError) { + error && toast.error(`Transaction Error: ${error}`); } - if (receipt?.status === 1) { - toast.success(`Transaction Complete: ${receipt.transactionHash}`); + if (isSuccess) { + receipt && toast.success(`Transaction Complete: ${receipt.transactionHash}`); } - }, [receipt?.status, receipt?.transactionHash, transactError?.message]); + }, [error, isError, isSuccess, receipt]); /* Return the transaction data and states */ return { - submitTx: sendTransactionAsync, - + write: sendTransactionAsync, receipt, hash: data?.hash, - + isPrepareError, isWaitingOnUser, isError, - - isTransacting, + isPending, isSuccess, error, }; From be84e1175f52febe9c34a01b6cfb798e301d5121 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 08:54:04 -0700 Subject: [PATCH 03/22] feat: show approval amount --- .../cactiComponents/ActionResponse.tsx | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/src/components/cactiComponents/ActionResponse.tsx b/src/components/cactiComponents/ActionResponse.tsx index 1d632efe..65103de9 100644 --- a/src/components/cactiComponents/ActionResponse.tsx +++ b/src/components/cactiComponents/ActionResponse.tsx @@ -1,11 +1,14 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import Skeleton from 'react-loading-skeleton'; import { AddressZero } from '@ethersproject/constants'; import { CheckCircleIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'; import { ConnectButton } from '@rainbow-me/rainbowkit'; import { BigNumber, UnsignedTransaction, ethers } from 'ethers'; +import { formatUnits } from 'ethers/lib/utils.js'; import tw from 'tailwind-styled-components'; import { useAccount } from 'wagmi'; +import useInput from '@/hooks/useInput'; +import useToken from '@/hooks/useToken'; import { ActionStepper } from './ActionStepper'; import useApproval, { ApprovalBasicParams } from './hooks/useApproval'; import useBalance from './hooks/useBalance'; @@ -42,7 +45,7 @@ const stylingByState = { type Action = { name: string; - fn: (overrideConfig?: undefined) => any; + fn: () => void; }; export type ActionResponseProps = { @@ -54,7 +57,7 @@ export type ActionResponseProps = { skipBalanceCheck?: boolean; stepper?: boolean; onSuccess?: () => void; - onError?: (txHash?: string) => any; + onError?: () => void; }; /** @@ -74,6 +77,18 @@ export const ActionResponse = ({ }: ActionResponseProps) => { const defaultLabel = label_ || 'Submit'; const { address } = useAccount(); + const _approvalParams = useMemo( + () => + approvalParams || { + tokenAddress: AddressZero, + spender: AddressZero, + approvalAmount: BigNumber.from(0), + skipApproval: true, // NOTE: approval is skipped if no approval params are passed in + }, + [approvalParams] + ); + const { data: token } = useToken(undefined, _approvalParams.tokenAddress); + const amountFmt = formatUnits(_approvalParams.approvalAmount, token?.decimals); /** Check for the approval. If no approvalParams, hasAllowance === true and approveTx == undefined */ const { @@ -82,14 +97,7 @@ export const ActionResponse = ({ isWaitingOnUser: approvalWaitingOnUser, isPending: approvalTransacting, isPrepareError: isPrepareApprovalError, - } = useApproval( - approvalParams || { - tokenAddress: AddressZero, - spender: AddressZero, - approvalAmount: BigNumber.from(0), - skipApproval: true, // NOTE: approval is skipped if no approval params are passed in - } - ); + } = useApproval(_approvalParams); const { write: submitTx, @@ -97,7 +105,6 @@ export const ActionResponse = ({ isPending, error, isSuccess, - receipt, hash, isError, isPrepareError, @@ -112,10 +119,10 @@ export const ActionResponse = ({ const { data: ethBal } = useBalance(); const { data: balance } = useBalance( - approvalParams?.tokenAddress, + _approvalParams.tokenAddress, undefined, undefined, - approvalParams?.skipApproval + _approvalParams.skipApproval ); // button state @@ -131,18 +138,18 @@ export const ActionResponse = ({ const [hasEnoughBalance, setHasEnoughBalance] = useState(false); useEffect(() => { - if (approvalParams?.skipApproval || skipBalanceCheck) return setHasEnoughBalance(true); + if (_approvalParams.skipApproval || skipBalanceCheck) return setHasEnoughBalance(true); // explicitly showing approvalParams === undefined for clarity - as oppposed to !approvalParams - if (approvalParams === undefined) return setHasEnoughBalance(true); + if (_approvalParams === undefined) return setHasEnoughBalance(true); if (sendParams?.value! >= ethBal!) { return setHasEnoughBalance(false); } // check approval token balance - if (balance && approvalParams?.approvalAmount) - setHasEnoughBalance(balance.gte(approvalParams?.approvalAmount!)); - }, [approvalParams, balance, ethBal, sendParams?.value, skipBalanceCheck]); + if (balance && _approvalParams?.approvalAmount) + setHasEnoughBalance(balance.gte(_approvalParams.approvalAmount)); + }, [_approvalParams, balance, ethBal, sendParams?.value, skipBalanceCheck]); /** * BUTTON FLOW: @@ -163,13 +170,13 @@ export const ActionResponse = ({ } if (isPending) { - console.log('TX IN PROGRESS... '); + console.log('TX IN PROGRESS...'); setLabel(defaultLabel); return setState(ActionResponseState.TRANSACTING); } if (isPrepareError) { - setLabel('Could not prepare tx'); + setLabel('Could not prepare transaction'); return setState(ActionResponseState.ERROR); } @@ -180,7 +187,7 @@ export const ActionResponse = ({ } // approval status/state - if (!approvalParams?.skipApproval) { + if (!_approvalParams.skipApproval) { if (isPrepareApprovalError) { setLabel('Could not prepare approval'); return setState(ActionResponseState.ERROR); @@ -206,17 +213,28 @@ export const ActionResponse = ({ } if (!hasAllowance) { - setLabel('Token approval needed'); - setAction({ name: 'approval', fn: approveTx! }); - return setState(ActionResponseState.READY); + if (approveTx) { + setLabel(`Approve ${amountFmt} ${token?.symbol}`); + setAction({ name: 'approval', fn: approveTx }); + return setState(ActionResponseState.READY); + } else { + setLabel('Error Preparing Approval'); + return setState(ActionResponseState.LOADING); + } } // has balance and allowance, and ready to submit tx - setLabel(defaultLabel); - setAction({ name: 'submit', fn: submitTx! }); - setState(ActionResponseState.READY); + if (submitTx) { + setLabel(defaultLabel); + setAction({ name: 'submit', fn: submitTx }); + return setState(ActionResponseState.READY); + } else { + setLabel('Error Preparing Transaction'); + return setState(ActionResponseState.LOADING); + } }, [ - approvalParams?.skipApproval, + _approvalParams.skipApproval, + amountFmt, approvalTransacting, approvalWaitingOnUser, defaultLabel, @@ -229,6 +247,7 @@ export const ActionResponse = ({ isSuccess, isWaitingOnUser, submitTx, + token?.symbol, ]); /* Set the styling based on the state (Note: always diasbled if 'disabled' from props) */ From e82f8c3ea8390af31bea2ea019542805f627f024 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 10:09:04 -0700 Subject: [PATCH 04/22] fix: check --- src/components/cactiComponents/hooks/useApproval.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/cactiComponents/hooks/useApproval.tsx b/src/components/cactiComponents/hooks/useApproval.tsx index 34b0325c..909b1987 100644 --- a/src/components/cactiComponents/hooks/useApproval.tsx +++ b/src/components/cactiComponents/hooks/useApproval.tsx @@ -75,7 +75,7 @@ const useApproval = (params: ApprovalBasicParams) => { isError, isPending, isSuccess, - hasAllowance: params.skipApproval ? true : allowanceAmount?.gte(amountToUse), // if isETH, then hasAllowance is true, else check if allowanceAmount is greater than amount + hasAllowance: !!params.skipApproval ? true : allowanceAmount?.gte(amountToUse), // if isETH, then hasAllowance is true, else check if allowanceAmount is greater than amount }; }; From edb5e24dbca8b910d05c088336d5f13448857464 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 10:09:13 -0700 Subject: [PATCH 05/22] fix: labels --- src/components/cactiComponents/ActionResponse.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/cactiComponents/ActionResponse.tsx b/src/components/cactiComponents/ActionResponse.tsx index 65103de9..8a32741c 100644 --- a/src/components/cactiComponents/ActionResponse.tsx +++ b/src/components/cactiComponents/ActionResponse.tsx @@ -218,7 +218,7 @@ export const ActionResponse = ({ setAction({ name: 'approval', fn: approveTx }); return setState(ActionResponseState.READY); } else { - setLabel('Error Preparing Approval'); + setLabel('Preparing Approval'); return setState(ActionResponseState.LOADING); } } @@ -229,7 +229,7 @@ export const ActionResponse = ({ setAction({ name: 'submit', fn: submitTx }); return setState(ActionResponseState.READY); } else { - setLabel('Error Preparing Transaction'); + setLabel('Preparing Transaction...'); return setState(ActionResponseState.LOADING); } }, [ @@ -253,7 +253,7 @@ export const ActionResponse = ({ /* Set the styling based on the state (Note: always diasbled if 'disabled' from props) */ const extraStyle = stylingByState[disabled ? ActionResponseState.DISABLED : state]; - const handleAction = async () => (action ? await action.fn() : undefined); + const handleAction = async () => (action ? action.fn() : undefined); return (
From b1b81d0c32a08acaaaa576d8a1f5bf4135f3b35f Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 10:09:33 -0700 Subject: [PATCH 06/22] fix: handle undefineds better --- .../experimental_/widgets/uniswap/Uniswap.tsx | 193 ++++++++++++++---- src/hooks/useInput.ts | 15 +- 2 files changed, 158 insertions(+), 50 deletions(-) diff --git a/src/components/experimental_/widgets/uniswap/Uniswap.tsx b/src/components/experimental_/widgets/uniswap/Uniswap.tsx index a5a4c720..fe1d3223 100644 --- a/src/components/experimental_/widgets/uniswap/Uniswap.tsx +++ b/src/components/experimental_/widgets/uniswap/Uniswap.tsx @@ -1,8 +1,9 @@ -import { useMemo } from 'react'; +import { use, useEffect, useMemo, useState } from 'react'; import { SWAP_ROUTER_02_ADDRESSES } from '@uniswap/smart-order-router'; +import { set } from 'date-fns'; import { BigNumber, ethers } from 'ethers'; -import { formatUnits } from 'ethers/lib/utils.js'; -import { Address, useAccount, usePrepareContractWrite } from 'wagmi'; +import { UnsignedTransaction, formatUnits, parseUnits } from 'ethers/lib/utils.js'; +import { Address, useAccount, useContract, usePrepareContractWrite } from 'wagmi'; import { ActionResponse, HeaderResponse, @@ -36,7 +37,7 @@ const Uniswap = ({ tokenInSymbol, tokenOutSymbol, inputAmount }: UniswapProps) = const { data: tokenInChecked } = useToken(tokenInIsETH ? 'WETH' : tokenInSymbol); const { data: tokenOutChecked } = useToken(tokenOutIsETH ? 'WETH' : tokenOutSymbol); const input = useInput(inputAmount, tokenInChecked?.symbol!); - const slippage = 0.5; // in percentage terms + const slippage = 2.0; // in percentage terms const getSlippageAdjustedAmount = (amount: BigNumber) => BigNumber.from(amount) .mul(10000 - slippage * 100) @@ -63,9 +64,30 @@ const Uniswap = ({ tokenInSymbol, tokenOutSymbol, inputAmount }: UniswapProps) = amount: undefined, }); - const amountOut = useInput(quote?.value.toExact()!, tokenOutChecked?.symbol!); - const amountOutMinimum = getSlippageAdjustedAmount(amountOut.value || ethers.constants.Zero); - const amountOutMinimum_ = cleanValue(formatUnits(amountOutMinimum, tokenOutChecked?.decimals), 2); + const [amountOut, setAmountOut] = useState(); + const [amountOut_, setAmountOut_] = useState(); + const [amountOutMinimum, setAmountOutMinimum] = useState(); + const [amountOutMinimum_, setAmountOutMinimum_] = useState(); + + useEffect(() => { + if (!quote) { + console.log('No quote yet'); + return; + } + const quoteValue = quote.value.toExact(); + if (!tokenOutChecked) { + console.error('No tokenOutChecked'); + return; + } + const { decimals } = tokenOutChecked; + const parsed = parseUnits(quoteValue, decimals); + setAmountOut(parsed); + setAmountOut_(quoteValue); + + const slippeageAdjustedAmount = getSlippageAdjustedAmount(parsed); + setAmountOutMinimum(slippeageAdjustedAmount); + setAmountOutMinimum_(formatUnits(slippeageAdjustedAmount, decimals)); + }, [quote, tokenOutChecked]); const calcPrice = (quote: string | undefined, amount: string | undefined) => !quote || !amount ? undefined : cleanValue((+quote / +amount).toString(), 2); @@ -75,37 +97,124 @@ const Uniswap = ({ tokenInSymbol, tokenOutSymbol, inputAmount }: UniswapProps) = ? undefined : cleanValue((+amount * +tokenOutUSDRate.humanReadableAmount).toString(), 2); - const approval = useMemo( - () => ({ - tokenAddress: tokenIn?.address!, - approvalAmount: input.value!, + const approval = useMemo(() => { + if (!tokenIn) { + console.error('No tokenIn'); + return; + } + + if (!amountOut) { + console.error('No amountOut, quote is loading probably'); + return; + } + + return { + tokenAddress: tokenIn.address, + approvalAmount: amountOut, spender: SWAP_ROUTER_02_ADDRESSES(chainId) as Address, skipApproval: tokenInIsETH, - }), - [chainId, input.value, tokenIn?.address, tokenInIsETH] - ); + }; + }, [amountOut, chainId, tokenIn, tokenInIsETH]); - const { config } = usePrepareContractWrite({ + // get swap router contract + const contract = useContract({ address: SWAP_ROUTER_02_ADDRESSES(chainId) as Address, abi: SwapRouter02Abi, - functionName: 'exactInputSingle', - args: [ - { - tokenIn: tokenInChecked?.address!, - tokenOut: tokenOutChecked?.address!, - fee: 3000, - recipient: recipient!, - amountIn: input.value!, - amountOutMinimum, - sqrtPriceLimitX96: BigNumber.from(0), - }, - ], - overrides: { - value: tokenInIsETH ? input.value : ethers.constants.Zero, - }, - enabled: !!quote, // NOTE: here we are only enabling when the async call is ready!! }); + // args for func + const args = useMemo(() => { + if (!tokenInChecked) { + console.error('No tokenInChecked'); + return; + } + if (!tokenOutChecked) { + console.error('No tokenOutChecked'); + return; + } + if (!recipient) { + console.error('No recipient'); + return; + } + if (!input?.value) { + console.error('No input'); + return; + } + + if (!amountOutMinimum) { + console.error('No amountOutMinimum, quote is probably loading'); + return; + } + + return { + tokenIn: tokenInChecked.address, + tokenOut: tokenOutChecked.address, + fee: 3000, + recipient: recipient, + amountIn: input.value, + amountOutMinimum, + sqrtPriceLimitX96: BigNumber.from(0), + }; + }, [amountOutMinimum, input, recipient, tokenInChecked, tokenOutChecked]); + + const value = useMemo(() => { + if (!input) { + console.error('No input'); + return; + } + return tokenInIsETH ? input.value : ethers.constants.Zero; + }, [input, tokenInIsETH]); + + const [sendParams, setSendParams] = useState(); + + useEffect(() => { + (async () => { + if (!contract) { + console.log('No contract yet'); + return; + } + + if (!args) { + console.log('No args yet'); + return; + } + + if (!value) { + console.log('No value yet'); + return; + } + + const sendParams = await contract.populateTransaction.exactInputSingle(args, { + value, + }); + + setSendParams(sendParams); + })(); + }, [args, contract, value]); + + const label = useMemo(() => { + if (!input) { + console.error('No input'); + return; + } + if (!tokenIn) { + console.error('No tokenIn'); + return; + } + if (!tokenOutChecked) { + console.error('No tokenOut'); + return; + } + if (!amountOut) { + console.error('No amountOut'); + return; + } + + return `Swap ${input.formatted} ${tokenIn.symbol} for ${cleanValue(amountOut_, 2)} ${ + tokenOutChecked.symbol + }`; + }, [amountOut, amountOut_, input, tokenIn, tokenOutChecked]); + if (inputAmount === '*' || inputAmount === '{amount}') return ( @@ -123,19 +232,20 @@ const Uniswap = ({ tokenInSymbol, tokenOutSymbol, inputAmount }: UniswapProps) = ); diff --git a/src/hooks/useInput.ts b/src/hooks/useInput.ts index 02b6e4ec..9c720019 100644 --- a/src/hooks/useInput.ts +++ b/src/hooks/useInput.ts @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; import { BigNumber } from 'ethers'; import { formatUnits, parseUnits } from 'ethers/lib/utils.js'; import { cleanValue } from '@/utils'; @@ -25,12 +25,13 @@ const useInput = ( ) => { const { data: token } = useToken(tokenSymbol); - if (!token) { - console.error(`Token ${tokenSymbol} not found`); - } + return useMemo((): Input | undefined => { + if (!token) { + console.error(`Token ${tokenSymbol} not found`); + return; + } - return useMemo((): Input => { - const decimals = token?.decimals || 18; // TODO: this defaults to ETH decimals, but it shouldnt neccesarily. + const { decimals } = token; const inputCleaned = cleanValue(input, decimals); const inputBN = inputCleaned ? parseUnits(inputCleaned, decimals) : undefined; if (!inputBN) return { value: undefined, formatted: undefined, decimals: decimals }; @@ -38,7 +39,7 @@ const useInput = ( const value = mutate ? mutate(inputBN) : inputBN; const formatted = formatUnits(value, decimals); return { value, formatted, decimals }; - }, [tokenSymbol, input, mutate]); + }, [input, tokenSymbol, token, mutate]); }; export default useInput; From 6d80202ec930e118c896421850c1221f5c3360a4 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 10:11:39 -0700 Subject: [PATCH 07/22] fix: clean value --- src/components/experimental_/widgets/uniswap/Uniswap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/experimental_/widgets/uniswap/Uniswap.tsx b/src/components/experimental_/widgets/uniswap/Uniswap.tsx index fe1d3223..7a298a9c 100644 --- a/src/components/experimental_/widgets/uniswap/Uniswap.tsx +++ b/src/components/experimental_/widgets/uniswap/Uniswap.tsx @@ -252,7 +252,7 @@ const Uniswap = ({ tokenInSymbol, tokenOutSymbol, inputAmount }: UniswapProps) = title="Breakdown" data={[ ['Slippage', `${slippage}%`], - ['Minimum swap', amountOutMinimum_], + ['Minimum swap', cleanValue(amountOutMinimum_, 2)], ['Route', `${tokenInSymbol}-${tokenOutSymbol}`], ]} collapsible From 2135a12f9739d3efb18d1474f585623f97389fbe Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 11:07:45 -0700 Subject: [PATCH 08/22] feat: useAllowance hook --- .../cactiComponents/ActionResponse.tsx | 35 +++++++------- .../cactiComponents/hooks/useAllowance.tsx | 47 +++++++++++++++++++ 2 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 src/components/cactiComponents/hooks/useAllowance.tsx diff --git a/src/components/cactiComponents/ActionResponse.tsx b/src/components/cactiComponents/ActionResponse.tsx index 8a32741c..0d2485da 100644 --- a/src/components/cactiComponents/ActionResponse.tsx +++ b/src/components/cactiComponents/ActionResponse.tsx @@ -3,12 +3,12 @@ import Skeleton from 'react-loading-skeleton'; import { AddressZero } from '@ethersproject/constants'; import { CheckCircleIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'; import { ConnectButton } from '@rainbow-me/rainbowkit'; -import { BigNumber, UnsignedTransaction, ethers } from 'ethers'; +import { BigNumber, UnsignedTransaction } from 'ethers'; import { formatUnits } from 'ethers/lib/utils.js'; import tw from 'tailwind-styled-components'; import { useAccount } from 'wagmi'; -import useInput from '@/hooks/useInput'; import useToken from '@/hooks/useToken'; +import { cleanValue } from '@/utils'; import { ActionStepper } from './ActionStepper'; import useApproval, { ApprovalBasicParams } from './hooks/useApproval'; import useBalance from './hooks/useBalance'; @@ -156,6 +156,12 @@ export const ActionResponse = ({ * Update all the local states on tx/approval status changes. **/ useEffect(() => { + // pre-approval and pre-tx state + if (!hasEnoughBalance) { + setLabel('Insufficient Balance'); + return setState(ActionResponseState.DISABLED); + } + // tx status/state if (isSuccess) { console.log('TX SUCCESS'); @@ -204,22 +210,17 @@ export const ActionResponse = ({ setLabel(`Please check your wallet...`); return setState(ActionResponseState.WAITING_FOR_USER); } - } - - // pre-approval and pre-tx state - if (!hasEnoughBalance) { - setLabel('Insufficient Balance'); - return setState(ActionResponseState.DISABLED); - } - if (!hasAllowance) { - if (approveTx) { - setLabel(`Approve ${amountFmt} ${token?.symbol}`); - setAction({ name: 'approval', fn: approveTx }); - return setState(ActionResponseState.READY); - } else { - setLabel('Preparing Approval'); - return setState(ActionResponseState.LOADING); + console.log('🦄 ~ file: ActionResponse.tsx:215 ~ useEffect ~ hasAllowance:', hasAllowance); + if (!hasAllowance) { + if (approveTx) { + setLabel(`Approve ${cleanValue(amountFmt, 2)} ${token?.symbol}`); + setAction({ name: 'approval', fn: approveTx }); + return setState(ActionResponseState.READY); + } else { + setLabel('Preparing Approval'); + return setState(ActionResponseState.LOADING); + } } } diff --git a/src/components/cactiComponents/hooks/useAllowance.tsx b/src/components/cactiComponents/hooks/useAllowance.tsx new file mode 100644 index 00000000..e2365a06 --- /dev/null +++ b/src/components/cactiComponents/hooks/useAllowance.tsx @@ -0,0 +1,47 @@ +import { useQuery } from 'react-query'; +import { Address, erc20ABI, useAccount, useContract } from 'wagmi'; +import { readContract } from 'wagmi/actions'; +import useChainId from '@/hooks/useChainId'; + +const useAllowance = ({ + tokenAddress, + spender, +}: { + tokenAddress: Address | undefined; + spender: Address | undefined; +}) => { + const chainId = useChainId(); + const { address: account } = useAccount(); + + // Get allowance amount - doesn't run if address or spender is undefined + const { data, ...rest } = useQuery({ + queryKey: ['allowance', tokenAddress, spender, chainId], + queryFn: async () => { + if (!tokenAddress) { + console.error(`Token address not found for approval`); + return; + } + if (!account) { + console.error(`Account not found for approval`); + return; + } + + if (!spender) { + console.error(`Spender not found for approval`); + return; + } + + return await readContract({ + address: tokenAddress, + abi: erc20ABI, + functionName: 'allowance', + args: [account, spender], + }); + }, + refetchOnWindowFocus: false, + }); + + return { data, ...rest }; +}; + +export default useAllowance; From 7e58b6226d3903aa0eb11297ac1afba086be380c Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 11:08:02 -0700 Subject: [PATCH 09/22] feat: use non-async --- .../cactiComponents/hooks/useApproval.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/cactiComponents/hooks/useApproval.tsx b/src/components/cactiComponents/hooks/useApproval.tsx index 909b1987..a5693791 100644 --- a/src/components/cactiComponents/hooks/useApproval.tsx +++ b/src/components/cactiComponents/hooks/useApproval.tsx @@ -4,7 +4,6 @@ import { BigNumber } from 'ethers'; import { erc20ABI, useAccount, - useContractRead, useContractWrite, usePrepareContractWrite, useWaitForTransaction, @@ -12,6 +11,7 @@ import { import useChainId from '@/hooks/useChainId'; import useToken from '@/hooks/useToken'; import { cleanValue } from '@/utils'; +import useAllowance from './useAllowance'; export type ApprovalBasicParams = { approvalAmount: BigNumber; @@ -37,24 +37,22 @@ const useApproval = (params: ApprovalBasicParams) => { [approvalAmount, token?.decimals] ); - // Get allowance amount - doesn't run if address or spender is undefined - const { data: allowanceAmount, refetch: refetchAllowance } = useContractRead({ - address: tokenAddress, - abi: erc20ABI, - functionName: 'allowance', - args: [account!, spender!], + const { data: allowanceAmount, refetch: refetchAllowance } = useAllowance({ + tokenAddress, + spender, }); // Prepare the approval transaction - doesn't run if address or spender is undefined const { config, isError: isPrepareError } = usePrepareContractWrite({ - address: !params.skipApproval ? tokenAddress : undefined, + address: !!params.skipApproval ? undefined : tokenAddress, abi: erc20ABI, functionName: 'approve', args: [spender!, amountToUse], chainId, + onError: (e) => console.error(e), }); - const { writeAsync: approveTx, data, isLoading: isWaitingOnUser } = useContractWrite(config); + const { write: approveTx, data, isLoading: isWaitingOnUser } = useContractWrite(config); const { isError, @@ -65,6 +63,8 @@ const useApproval = (params: ApprovalBasicParams) => { onSuccess: async () => await refetchAllowance(), }); + const hasAllowance = !!params.skipApproval ? true : allowanceAmount?.gte(amountToUse); // if isETH, then hasAllowance is true, else check if allowanceAmount is greater than amount + return { write: !!params.skipApproval ? undefined : approveTx, refetchAllowance, @@ -75,7 +75,7 @@ const useApproval = (params: ApprovalBasicParams) => { isError, isPending, isSuccess, - hasAllowance: !!params.skipApproval ? true : allowanceAmount?.gte(amountToUse), // if isETH, then hasAllowance is true, else check if allowanceAmount is greater than amount + hasAllowance, }; }; From 56baa4098a315c120c15b381fae3d8d636100eb1 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 11:08:21 -0700 Subject: [PATCH 10/22] fix: approve amount --- .../experimental_/widgets/uniswap/Uniswap.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/experimental_/widgets/uniswap/Uniswap.tsx b/src/components/experimental_/widgets/uniswap/Uniswap.tsx index 7a298a9c..698d2fc1 100644 --- a/src/components/experimental_/widgets/uniswap/Uniswap.tsx +++ b/src/components/experimental_/widgets/uniswap/Uniswap.tsx @@ -1,9 +1,8 @@ -import { use, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { SWAP_ROUTER_02_ADDRESSES } from '@uniswap/smart-order-router'; -import { set } from 'date-fns'; import { BigNumber, ethers } from 'ethers'; import { UnsignedTransaction, formatUnits, parseUnits } from 'ethers/lib/utils.js'; -import { Address, useAccount, useContract, usePrepareContractWrite } from 'wagmi'; +import { Address, useAccount, useContract } from 'wagmi'; import { ActionResponse, HeaderResponse, @@ -71,7 +70,7 @@ const Uniswap = ({ tokenInSymbol, tokenOutSymbol, inputAmount }: UniswapProps) = useEffect(() => { if (!quote) { - console.log('No quote yet'); + console.log(`No quote yet for ${tokenInSymbol}-${tokenOutSymbol}`); return; } const quoteValue = quote.value.toExact(); @@ -87,7 +86,7 @@ const Uniswap = ({ tokenInSymbol, tokenOutSymbol, inputAmount }: UniswapProps) = const slippeageAdjustedAmount = getSlippageAdjustedAmount(parsed); setAmountOutMinimum(slippeageAdjustedAmount); setAmountOutMinimum_(formatUnits(slippeageAdjustedAmount, decimals)); - }, [quote, tokenOutChecked]); + }, [quote, tokenInSymbol, tokenOutChecked, tokenOutSymbol]); const calcPrice = (quote: string | undefined, amount: string | undefined) => !quote || !amount ? undefined : cleanValue((+quote / +amount).toString(), 2); @@ -103,18 +102,18 @@ const Uniswap = ({ tokenInSymbol, tokenOutSymbol, inputAmount }: UniswapProps) = return; } - if (!amountOut) { - console.error('No amountOut, quote is loading probably'); + if (!input?.value) { + console.error('No input'); return; } return { tokenAddress: tokenIn.address, - approvalAmount: amountOut, + approvalAmount: input.value, spender: SWAP_ROUTER_02_ADDRESSES(chainId) as Address, skipApproval: tokenInIsETH, }; - }, [amountOut, chainId, tokenIn, tokenInIsETH]); + }, [chainId, input?.value, tokenIn, tokenInIsETH]); // get swap router contract const contract = useContract({ From 234ecf64e001251ea700df9a52ec852f62b5fe4a Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 11:23:01 -0700 Subject: [PATCH 11/22] chore: remove unused --- src/components/cactiComponents/hooks/useApproval.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/cactiComponents/hooks/useApproval.tsx b/src/components/cactiComponents/hooks/useApproval.tsx index a5693791..b7d4ca8c 100644 --- a/src/components/cactiComponents/hooks/useApproval.tsx +++ b/src/components/cactiComponents/hooks/useApproval.tsx @@ -25,7 +25,6 @@ const validateAddress = (addr: `0x${string}`): `0x${string}` | undefined => const useApproval = (params: ApprovalBasicParams) => { const chainId = useChainId(); - const { address: account } = useAccount(); const { approvalAmount, tokenAddress: _tokenAddress, spender: _spender } = params; const tokenAddress = validateAddress(_tokenAddress); const spender = validateAddress(_spender); From e29944e1465ac38e8f9b711a070a465893b7c437 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 11:51:35 -0700 Subject: [PATCH 12/22] feat: use useBalance custom hook --- src/abi/erc1155ABI.json | 18 ----- src/abi/erc1155ABI.ts | 18 +++++ src/components/CheckNftOwner.tsx | 16 ++-- .../cactiComponents/ActionResponse.tsx | 8 +- .../cactiComponents/hooks/useBalance.ts | 79 ++++++++++++------- .../cactiComponents/hooks/useSubmitTx.tsx | 2 +- src/components/devTools/MintButton.tsx | 5 +- .../experimental_/CustomConnectButton.tsx | 2 +- .../lend-close/YieldProtocolLendClose.tsx | 4 +- src/components/widgets/BuyNFT.tsx | 2 +- src/hooks/useBalance.ts | 39 --------- src/hooks/useForkTools.ts | 5 +- src/hooks/useTokenApproval.tsx | 2 +- 13 files changed, 86 insertions(+), 114 deletions(-) delete mode 100644 src/abi/erc1155ABI.json create mode 100644 src/abi/erc1155ABI.ts delete mode 100644 src/hooks/useBalance.ts diff --git a/src/abi/erc1155ABI.json b/src/abi/erc1155ABI.json deleted file mode 100644 index b6913f79..00000000 --- a/src/abi/erc1155ABI.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "address", "name": "_owner", "type": "address" }, - { "internalType": "uint256", "name": "_id", "type": "uint256" } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/src/abi/erc1155ABI.ts b/src/abi/erc1155ABI.ts new file mode 100644 index 00000000..c3924e45 --- /dev/null +++ b/src/abi/erc1155ABI.ts @@ -0,0 +1,18 @@ +export default [ + { + inputs: [ + { internalType: 'address', name: '_owner', type: 'address' }, + { internalType: 'uint256', name: '_id', type: 'uint256' }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/components/CheckNftOwner.tsx b/src/components/CheckNftOwner.tsx index cb7119cb..8a0cdf5e 100644 --- a/src/components/CheckNftOwner.tsx +++ b/src/components/CheckNftOwner.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; import { BigNumber } from 'ethers'; -import { erc721ABI, useAccount, useContractRead } from 'wagmi'; -import erc1155ABI from '@/abi/erc1155ABI.json'; +import { Address, erc721ABI, useAccount, useContractRead } from 'wagmi'; import { shortenAddress } from '@/utils'; +import useBalance from './cactiComponents/hooks/useBalance'; interface Props { nftAddress: string; @@ -21,20 +21,14 @@ export const NftOwner = ({ nftAddress, tokenId }: Props) => { watch: true, }); - const { data: erc1155Data, isSuccess: is1155ReadSuccess } = useContractRead({ - address: nftAddress as `0x${string}`, - abi: erc1155ABI, - functionName: 'balanceOf', - args: [account, BigNumber.from(tokenId)], - watch: true, - }); + const { data: erc1155Data } = useBalance(nftAddress as Address, undefined, tokenId); const isOwner = owner === account; useEffect(() => { if (is721ReadSuccess) setOwner(`${erc721Data?.toString()}` || ''); - if (is1155ReadSuccess) setOwner(`Your balance: ` + erc1155Data?.toString() || '0'); - }, [erc721Data, erc1155Data, is721ReadSuccess, is1155ReadSuccess]); + if (erc1155Data) setOwner(`Your balance: ` + erc1155Data.toString() || '0'); + }, [erc721Data, erc1155Data, is721ReadSuccess]); return (
diff --git a/src/components/cactiComponents/ActionResponse.tsx b/src/components/cactiComponents/ActionResponse.tsx index 0d2485da..3c735e56 100644 --- a/src/components/cactiComponents/ActionResponse.tsx +++ b/src/components/cactiComponents/ActionResponse.tsx @@ -117,13 +117,7 @@ export const ActionResponse = ({ ); const { data: ethBal } = useBalance(); - - const { data: balance } = useBalance( - _approvalParams.tokenAddress, - undefined, - undefined, - _approvalParams.skipApproval - ); + const { data: balance } = useBalance(_approvalParams.tokenAddress, undefined, undefined); // button state const [label, setLabel] = useState(); diff --git a/src/components/cactiComponents/hooks/useBalance.ts b/src/components/cactiComponents/hooks/useBalance.ts index 509b42a3..45ddf66b 100644 --- a/src/components/cactiComponents/hooks/useBalance.ts +++ b/src/components/cactiComponents/hooks/useBalance.ts @@ -1,8 +1,9 @@ import { useEffect, useState } from 'react'; -import { AddressZero } from '@ethersproject/constants'; -import { BigNumber, ethers } from 'ethers'; -import { useAccount, useBalance as useBalanceWagmi, useContractRead } from 'wagmi'; -import erc1155ABI from '@/abi/erc1155ABI.json'; +import { useQuery } from 'react-query'; +import { BigNumber } from 'ethers'; +import { useAccount } from 'wagmi'; +import { fetchBalance, readContract } from 'wagmi/actions'; +import erc1155ABI from '@/abi/erc1155ABI'; import useChainId from '@/hooks/useChainId'; /** @@ -12,47 +13,67 @@ import useChainId from '@/hooks/useChainId'; const useBalance = ( tokenAddress?: `0x${string}`, compareAmount?: BigNumber, - erc1155TokenId?: string, - isEth?: boolean + erc1155TokenId?: string ) => { const chainId = useChainId(); const { address: account } = useAccount(); - /* erc20 or eth if zero or no address is specified */ - const { data, isLoading } = useBalanceWagmi({ - address: account, - token: isEth || tokenAddress === AddressZero ? undefined : tokenAddress, - enabled: !erc1155TokenId, // if erc1155TokenId is specified, don't get erc20 balance - chainId, - }); + const { data, ...rest } = useQuery({ + queryKey: ['balance', account, tokenAddress, erc1155TokenId, chainId], + queryFn: async () => { + if (!account) { + console.error('account is required to fetch balance'); + return; + } + + // fetch native balance + if (!tokenAddress) { + return ( + await fetchBalance({ + address: account, + chainId, + }) + ).value; + } + + if (erc1155TokenId) { + const erc1155Bal = await readContract({ + address: tokenAddress, + chainId, + abi: erc1155ABI, + functionName: 'balanceOf', + args: [account, BigNumber.from(erc1155TokenId)], + }); + return erc1155Bal; + } - /* erc1155 */ - const { data: erc1155_data, isLoading: erc1155_Loading } = useContractRead({ - address: tokenAddress, - abi: erc1155ABI, - functionName: 'balanceOf', - args: [account, erc1155TokenId], - enabled: !!erc1155TokenId, // if erc1155TokenId is specified, get erc1155 balance + return ( + await fetchBalance({ + address: account, + token: tokenAddress, + chainId, + }) + ).value; + }, }); const [comparisons, setComparisons] = useState(); /* keep the comparisons in sync with the balance */ useEffect(() => { - const data_bn = erc1155TokenId ? (erc1155_data as BigNumber) : data?.value; - if (compareAmount && data_bn) { + if (compareAmount && data) { setComparisons({ - isZero: data_bn.isZero(), - isGTEcompared: data_bn.gt(compareAmount), - isEQcompared: data_bn.eq(compareAmount), - isLTcompared: data_bn.lt(compareAmount), + isZero: data.isZero(), + isGTEcompared: data.gt(compareAmount), + isEQcompared: data.eq(compareAmount), + isLTcompared: data.lt(compareAmount), }); } - }, [compareAmount, data, erc1155TokenId, erc1155_data]); + }, [compareAmount, data, erc1155TokenId]); return { - data: erc1155TokenId ? (erc1155_data as BigNumber) : data?.value, - isLoading: erc1155TokenId ? erc1155_Loading : isLoading, + data, + ...rest, // comparisons isZero: comparisons?.isZero, diff --git a/src/components/cactiComponents/hooks/useSubmitTx.tsx b/src/components/cactiComponents/hooks/useSubmitTx.tsx index 32051dbe..ca2d4769 100644 --- a/src/components/cactiComponents/hooks/useSubmitTx.tsx +++ b/src/components/cactiComponents/hooks/useSubmitTx.tsx @@ -8,7 +8,7 @@ import { useSendTransaction, useWaitForTransaction, } from 'wagmi'; -import useBalance from '@/hooks/useBalance'; +import useBalance from './useBalance'; export type TxBasicParams = { address?: `0x${string}`; diff --git a/src/components/devTools/MintButton.tsx b/src/components/devTools/MintButton.tsx index e20aa42b..5db9cba6 100644 --- a/src/components/devTools/MintButton.tsx +++ b/src/components/devTools/MintButton.tsx @@ -1,15 +1,16 @@ import { useEffect, useState } from 'react'; import { JsonRpcProvider } from '@ethersproject/providers'; import { ethers } from 'ethers'; -import { useAccount, useBalance, useNetwork, useProvider } from 'wagmi'; +import { useAccount, useNetwork, useProvider } from 'wagmi'; import { Button } from '@/components/Button'; +import useBalance from '../cactiComponents/hooks/useBalance'; export const MintButton = () => { const { address } = useAccount(); const [isLoading, setLoading] = useState(false); const [isVisible, setVisible] = useState(false); const { chain } = useNetwork(); - const { refetch } = useBalance({ address }); + const { refetch } = useBalance(); const provider = useProvider() as JsonRpcProvider; useEffect(() => { diff --git a/src/components/experimental_/CustomConnectButton.tsx b/src/components/experimental_/CustomConnectButton.tsx index ec702028..11c021c5 100644 --- a/src/components/experimental_/CustomConnectButton.tsx +++ b/src/components/experimental_/CustomConnectButton.tsx @@ -1,10 +1,10 @@ import { PowerIcon } from '@heroicons/react/24/outline'; import { ConnectButton } from '@rainbow-me/rainbowkit'; import { formatEther } from 'ethers/lib/utils.js'; -import useBalance from '@/hooks/useBalance'; import { abbreviateHash, cleanValue } from '@/utils'; import Avatar from '../Avatar'; import SkeletonWrap from '../SkeletonWrap'; +import useBalance from '../cactiComponents/hooks/useBalance'; import useEnsName from '../cactiComponents/hooks/useEnsName'; import { buttonStyle } from './layout/sidebar/NewChatButton'; diff --git a/src/components/experimental_/widgets/yield-protocol/actions/lend-close/YieldProtocolLendClose.tsx b/src/components/experimental_/widgets/yield-protocol/actions/lend-close/YieldProtocolLendClose.tsx index 4db1b61e..8cecfb71 100644 --- a/src/components/experimental_/widgets/yield-protocol/actions/lend-close/YieldProtocolLendClose.tsx +++ b/src/components/experimental_/widgets/yield-protocol/actions/lend-close/YieldProtocolLendClose.tsx @@ -13,7 +13,7 @@ import { } from '@/components/cactiComponents'; import { ResponseGrid, ResponseTitle } from '@/components/cactiComponents/helpers/layout'; import { ApprovalBasicParams } from '@/components/cactiComponents/hooks/useApproval'; -import useBalance from '@/hooks/useBalance'; +import useBalance from '@/components/cactiComponents/hooks/useBalance'; // CUSTOM IMPORTS import useChainId from '@/hooks/useChainId'; import useToken from '@/hooks/useToken'; @@ -139,7 +139,7 @@ const SingleItem = ({ const ladleAddress = contractAddresses.addresses.get(chainId)?.get(ContractNames.LADLE); const fyTokenAddress = item.fyToken.id as Address; const poolAddress = item.fyToken.pools[0].id as Address; - const { data: fyTokenBalance } = useBalance(item.fyToken.id); + const { data: fyTokenBalance } = useBalance(fyTokenAddress); const { data } = useContractReads({ contracts: [ diff --git a/src/components/widgets/BuyNFT.tsx b/src/components/widgets/BuyNFT.tsx index 9a0104fd..46f217c0 100644 --- a/src/components/widgets/BuyNFT.tsx +++ b/src/components/widgets/BuyNFT.tsx @@ -14,10 +14,10 @@ import { } from 'wagmi'; import SeaportAbi from '@/abi/SeaportAbi.json'; import SubmitButton from '@/components/widgets/common/SubmitButton'; -import useBalance from '@/hooks/useBalance'; import { Order } from '@/types'; import { ETHEREUM_NETWORK } from '@/utils/constants'; import { NftOwner } from '../CheckNftOwner'; +import useBalance from '../cactiComponents/hooks/useBalance'; // @ts-ignore const JSONbig = JSONbigint({ storeAsString: true }); diff --git a/src/hooks/useBalance.ts b/src/hooks/useBalance.ts deleted file mode 100644 index 36af2e07..00000000 --- a/src/hooks/useBalance.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { BigNumber, ethers } from 'ethers'; -import { useAccount, useBalance as useBalanceWagmi, useContractRead } from 'wagmi'; -import erc1155ABI from '@/abi/erc1155ABI.json'; - -/** - * @description gets the balance of a an account for a token address, or if no address is specified, get's eth balance - * @param address - */ -const useBalance = (address?: string, erc1155TokenId?: string) => { - const { address: account } = useAccount(); - - // erc20 or eth if zero or no address is specified - const { data, isLoading, refetch } = useBalanceWagmi({ - address: account, - token: - address === ethers.constants.AddressZero || !address ? undefined : (address as `0x${string}`), - }); - - // erc1155 - const { - data: erc1155Bal, - isLoading: erc1155BBalLoading, - refetch: refetchErc1155, - } = useContractRead({ - address: address as `0x${string}`, - abi: erc1155ABI, - functionName: 'balanceOf', - args: [account, erc1155TokenId], - enabled: !!erc1155TokenId, - }); - - return { - data: erc1155TokenId ? (erc1155Bal as BigNumber) : data?.value, - isLoading: erc1155TokenId ? erc1155BBalLoading : isLoading, - refetch: erc1155TokenId ? refetchErc1155 : refetch, - }; -}; - -export default useBalance; diff --git a/src/hooks/useForkTools.ts b/src/hooks/useForkTools.ts index 42ba6715..cd7db50c 100644 --- a/src/hooks/useForkTools.ts +++ b/src/hooks/useForkTools.ts @@ -3,7 +3,8 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import axios from 'axios'; import { ethers } from 'ethers'; import useSWRImmutable from 'swr/immutable'; -import { useAccount, useBalance, useProvider } from 'wagmi'; +import { useAccount, useProvider } from 'wagmi'; +import useBalance from '@/components/cactiComponents/hooks/useBalance'; import SettingsContext from '@/contexts/SettingsContext'; type ForkTools = { @@ -26,7 +27,7 @@ const useForkTools = (id?: string): ForkTools => { /* parameters from wagmi */ const { address: account } = useAccount(); - const { refetch } = useBalance({ address: account }); + const { refetch } = useBalance(); const provider = useProvider(); const forkProvider = useMemo( () => (forkUrl ? new ethers.providers.JsonRpcProvider(forkUrl) : undefined), diff --git a/src/hooks/useTokenApproval.tsx b/src/hooks/useTokenApproval.tsx index 789d643b..4fd8b9b3 100644 --- a/src/hooks/useTokenApproval.tsx +++ b/src/hooks/useTokenApproval.tsx @@ -9,8 +9,8 @@ import { usePrepareContractWrite, useWaitForTransaction, } from 'wagmi'; +import useBalance from '@/components/cactiComponents/hooks/useBalance'; import SettingsContext from '@/contexts/SettingsContext'; -import useBalance from '@/hooks/useBalance'; import useForkTools from '@/hooks/useForkTools'; import useSigner from '@/hooks/useSigner'; import useToken from '@/hooks/useToken'; From ef83513c908941fde7b127ef78fa13eadfe90b85 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 11:52:43 -0700 Subject: [PATCH 13/22] fix: check --- .../experimental_/widgets/uniswap/useUniswapQuote.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/experimental_/widgets/uniswap/useUniswapQuote.tsx b/src/components/experimental_/widgets/uniswap/useUniswapQuote.tsx index f41bc789..be5c2197 100644 --- a/src/components/experimental_/widgets/uniswap/useUniswapQuote.tsx +++ b/src/components/experimental_/widgets/uniswap/useUniswapQuote.tsx @@ -55,13 +55,18 @@ const useUniswapQuote = ({ baseTokenSymbol, quoteTokenSymbol, amount }: UseUnisw return; } + if (!oneInput || !oneInput.value) { + console.error(`Amount not able to be parsed for swapping`); + return; + } + /* If the token are the same, simply return 1:1 as the rate (without going through the actual fetching process via router) */ if (experimentalUi && tokenIn.symbol === tokenOut.symbol) return { humanReadableAmount: '1.0000', value: CurrencyAmount.fromRawAmount( new Token(chainId, tokenIn.address, tokenIn.decimals, tokenIn.symbol, tokenIn.name), - oneInput.value?.toString()! + oneInput.value.toString() ), }; From 7b11a72fc0f597188fb7bc7de1e913d1cdb2b85c Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 11:55:55 -0700 Subject: [PATCH 14/22] fix: handle new useInput output format --- .../actions/borrow/YieldProtocolBorrow.tsx | 18 +++++++++--------- .../actions/lend/YieldProtocolLend.tsx | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/experimental_/widgets/yield-protocol/actions/borrow/YieldProtocolBorrow.tsx b/src/components/experimental_/widgets/yield-protocol/actions/borrow/YieldProtocolBorrow.tsx index 895ec893..6b858ee2 100644 --- a/src/components/experimental_/widgets/yield-protocol/actions/borrow/YieldProtocolBorrow.tsx +++ b/src/components/experimental_/widgets/yield-protocol/actions/borrow/YieldProtocolBorrow.tsx @@ -60,8 +60,8 @@ const YieldProtocolBorrow = ({ )} ${borrowAmount} ${borrowTokenSymbol} using ${collateralAmount} ${collateralTokenSymbol} on ${toTitleCase( projectName )}`; - const { value: _borrowAmount } = useInput(borrowAmount, borrowToken?.symbol!); - const { value: _collateralAmount } = useInput(collateralAmount, collateralToken?.symbol!); + const _borrowAmount = useInput(borrowAmount, borrowToken?.symbol!); + const _collateralAmount = useInput(collateralAmount, collateralToken?.symbol!); /***************INPUTS******************************************/ @@ -126,7 +126,7 @@ const YieldProtocolBorrow = ({ console.error('No join address'); return undefined; } - if (!_collateralAmount) { + if (!_collateralAmount?.value) { console.error('No collateral amount'); return undefined; } @@ -134,7 +134,7 @@ const YieldProtocolBorrow = ({ return { tokenAddress: collateralTokenToUse?.address, spender: joinAddress, - approvalAmount: _collateralAmount, + approvalAmount: _collateralAmount.value, skipApproval: collateralTokenIsEth, }; }, [_collateralAmount, collateralTokenIsEth, collateralTokenToUse?.address, joinAddress]); @@ -157,12 +157,12 @@ const YieldProtocolBorrow = ({ const getSendParams = useCallback( async (seriesEntity: YieldGraphResSeriesEntity) => { - if (!_borrowAmount) { + if (!_borrowAmount?.value) { console.error('No borrow amount'); return undefined; } - if (!_collateralAmount) { + if (!_collateralAmount?.value) { console.error('No collateral amount'); return undefined; } @@ -174,12 +174,12 @@ const YieldProtocolBorrow = ({ const maxAmountToBorrow = await getMaxBorrowAmount( seriesEntity.fyToken?.pools[0]?.id as Address, - _borrowAmount + _borrowAmount.value ); return await borrow({ - borrowAmount: _borrowAmount, - collateralAmount: _collateralAmount, + borrowAmount: _borrowAmount.value, + collateralAmount: _collateralAmount.value, seriesEntityId: seriesEntity.id, ilkId, borrowTokenIsEth, diff --git a/src/components/experimental_/widgets/yield-protocol/actions/lend/YieldProtocolLend.tsx b/src/components/experimental_/widgets/yield-protocol/actions/lend/YieldProtocolLend.tsx index f32d0839..54fbf0ed 100644 --- a/src/components/experimental_/widgets/yield-protocol/actions/lend/YieldProtocolLend.tsx +++ b/src/components/experimental_/widgets/yield-protocol/actions/lend/YieldProtocolLend.tsx @@ -99,7 +99,7 @@ const YieldProtocolLend = ({ const { data: tokenInToUse } = useToken(tokenInIsETH ? 'WETH' : tokenInSymbol); const label = ` ${toTitleCase(action)} ${inputAmount} ${tokenInSymbol} on ${toTitleCase(projectName)}`; - const { value: amount } = useInput(inputAmount, tokenInSymbol); + const amount = useInput(inputAmount, tokenInSymbol); /***************INPUTS******************************************/ // Yield Protocol specific spender handling @@ -127,7 +127,7 @@ const YieldProtocolLend = ({ () => ({ tokenAddress: tokenInToUse?.address!, spender: ladle!, - approvalAmount: amount!, + approvalAmount: amount?.value!, skipApproval: tokenInIsETH, }), [amount, ladle, tokenInIsETH, tokenInToUse?.address] @@ -143,7 +143,7 @@ const YieldProtocolLend = ({ return await lend({ tokenInAddress: tokenInToUse?.address!, - amount: amount!, + amount: amount?.value!, poolAddress: poolAddress as Address, isEthBase: tokenInIsETH, }); From dc93e8027ae8e72abe5fb0ed2dd2126fdcf40937 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 11:59:07 -0700 Subject: [PATCH 15/22] chore: remove log --- src/components/cactiComponents/ActionResponse.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/cactiComponents/ActionResponse.tsx b/src/components/cactiComponents/ActionResponse.tsx index 3c735e56..d280297a 100644 --- a/src/components/cactiComponents/ActionResponse.tsx +++ b/src/components/cactiComponents/ActionResponse.tsx @@ -205,7 +205,6 @@ export const ActionResponse = ({ return setState(ActionResponseState.WAITING_FOR_USER); } - console.log('🦄 ~ file: ActionResponse.tsx:215 ~ useEffect ~ hasAllowance:', hasAllowance); if (!hasAllowance) { if (approveTx) { setLabel(`Approve ${cleanValue(amountFmt, 2)} ${token?.symbol}`); From 52f5956e54e40b2fcfc23e2a366a3579c2ebf85e Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 12:09:14 -0700 Subject: [PATCH 16/22] fix: don't refetch on window focus --- src/components/cactiComponents/hooks/useBalance.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/cactiComponents/hooks/useBalance.ts b/src/components/cactiComponents/hooks/useBalance.ts index 45ddf66b..ba6de0ae 100644 --- a/src/components/cactiComponents/hooks/useBalance.ts +++ b/src/components/cactiComponents/hooks/useBalance.ts @@ -55,6 +55,7 @@ const useBalance = ( }) ).value; }, + refetchOnWindowFocus: false, }); const [comparisons, setComparisons] = useState(); From e2b3853fec0c670150e87b3efcdfab7e79d8aa20 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 12:20:40 -0700 Subject: [PATCH 17/22] fix: use ens name hook --- src/components/cactiComponents/hooks/useEnsName.tsx | 1 + src/components/experimental_/containers/SingleStepContainer.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/cactiComponents/hooks/useEnsName.tsx b/src/components/cactiComponents/hooks/useEnsName.tsx index 9e63a955..79aa9236 100644 --- a/src/components/cactiComponents/hooks/useEnsName.tsx +++ b/src/components/cactiComponents/hooks/useEnsName.tsx @@ -17,6 +17,7 @@ const useEnsName = () => { })), refetchOnWindowFocus: false, refetchOnReconnect: false, + refetchOnMount: false, }); return { diff --git a/src/components/experimental_/containers/SingleStepContainer.tsx b/src/components/experimental_/containers/SingleStepContainer.tsx index 6e2a9617..52de20d4 100644 --- a/src/components/experimental_/containers/SingleStepContainer.tsx +++ b/src/components/experimental_/containers/SingleStepContainer.tsx @@ -1,5 +1,5 @@ import { UnsignedTransaction } from 'ethers'; -import { useAccount, useEnsAvatar } from 'wagmi'; +import useEnsAvatar from '@/components/cactiComponents/hooks/useEnsAvatar'; import useEnsName from '@/components/cactiComponents/hooks/useEnsName'; import { ActionResponse, HeaderResponse } from '../../cactiComponents'; import { WidgetError } from '../widgets/helpers'; From 383875d7526b585dd7a432c354727076b716125c Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 12:49:21 -0700 Subject: [PATCH 18/22] chore: log --- src/components/cactiComponents/hooks/useAllowance.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/cactiComponents/hooks/useAllowance.tsx b/src/components/cactiComponents/hooks/useAllowance.tsx index e2365a06..5609050a 100644 --- a/src/components/cactiComponents/hooks/useAllowance.tsx +++ b/src/components/cactiComponents/hooks/useAllowance.tsx @@ -17,10 +17,12 @@ const useAllowance = ({ const { data, ...rest } = useQuery({ queryKey: ['allowance', tokenAddress, spender, chainId], queryFn: async () => { + // is eth probably? if (!tokenAddress) { - console.error(`Token address not found for approval`); + console.log('🦄 ~ file: useAllowance.tsx:21 ~ queryFn: ~ tokenAddress:', tokenAddress); return; } + if (!account) { console.error(`Account not found for approval`); return; From 795a5d67d35f287cec859ed9edf57aa53f733fca Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 12:50:49 -0700 Subject: [PATCH 19/22] feat: get config use react query instead --- .../cactiComponents/hooks/useApproval.tsx | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/components/cactiComponents/hooks/useApproval.tsx b/src/components/cactiComponents/hooks/useApproval.tsx index b7d4ca8c..127bb46a 100644 --- a/src/components/cactiComponents/hooks/useApproval.tsx +++ b/src/components/cactiComponents/hooks/useApproval.tsx @@ -1,13 +1,9 @@ import { useMemo } from 'react'; +import { useQuery } from 'react-query'; import { AddressZero } from '@ethersproject/constants'; import { BigNumber } from 'ethers'; -import { - erc20ABI, - useAccount, - useContractWrite, - usePrepareContractWrite, - useWaitForTransaction, -} from 'wagmi'; +import { erc20ABI, useContractWrite, useWaitForTransaction } from 'wagmi'; +import { prepareWriteContract } from 'wagmi/actions'; import useChainId from '@/hooks/useChainId'; import useToken from '@/hooks/useToken'; import { cleanValue } from '@/utils'; @@ -42,13 +38,30 @@ const useApproval = (params: ApprovalBasicParams) => { }); // Prepare the approval transaction - doesn't run if address or spender is undefined - const { config, isError: isPrepareError } = usePrepareContractWrite({ - address: !!params.skipApproval ? undefined : tokenAddress, - abi: erc20ABI, - functionName: 'approve', - args: [spender!, amountToUse], - chainId, - onError: (e) => console.error(e), + const { data: config, isError: isPrepareError } = useQuery({ + queryKey: ['prepareApprove', tokenAddress, spender, chainId], + queryFn: async () => { + if (!spender) { + console.error(`Spender not found for approval`); + return; + } + + // is eth + if (!tokenAddress) { + return; + } + + const config = await prepareWriteContract({ + address: tokenAddress, + abi: erc20ABI, + functionName: 'approve', + args: [spender, amountToUse], + chainId, + }); + + return config; + }, + refetchOnWindowFocus: false, }); const { write: approveTx, data, isLoading: isWaitingOnUser } = useContractWrite(config); @@ -62,7 +75,11 @@ const useApproval = (params: ApprovalBasicParams) => { onSuccess: async () => await refetchAllowance(), }); - const hasAllowance = !!params.skipApproval ? true : allowanceAmount?.gte(amountToUse); // if isETH, then hasAllowance is true, else check if allowanceAmount is greater than amount + const hasAllowance = useMemo(() => { + if (!!params.skipApproval) return true; + if (!allowanceAmount) return false; + return allowanceAmount.gte(amountToUse); + }, [allowanceAmount, amountToUse, params.skipApproval]); return { write: !!params.skipApproval ? undefined : approveTx, From 3c479216841f6497437c4a111098262cdc04582a Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 12:51:00 -0700 Subject: [PATCH 20/22] fix: approveTx dep --- src/components/cactiComponents/ActionResponse.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/cactiComponents/ActionResponse.tsx b/src/components/cactiComponents/ActionResponse.tsx index d280297a..83ab0d8f 100644 --- a/src/components/cactiComponents/ActionResponse.tsx +++ b/src/components/cactiComponents/ActionResponse.tsx @@ -231,6 +231,7 @@ export const ActionResponse = ({ amountFmt, approvalTransacting, approvalWaitingOnUser, + approveTx, defaultLabel, hasAllowance, hasEnoughBalance, From bc0c66747117f5ca8c553b0aa5f707797a0d1e01 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 9 Aug 2023 12:51:12 -0700 Subject: [PATCH 21/22] chore: log --- .../actions/lend/YieldProtocolLend.tsx | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/components/experimental_/widgets/yield-protocol/actions/lend/YieldProtocolLend.tsx b/src/components/experimental_/widgets/yield-protocol/actions/lend/YieldProtocolLend.tsx index 54fbf0ed..1088ec3d 100644 --- a/src/components/experimental_/widgets/yield-protocol/actions/lend/YieldProtocolLend.tsx +++ b/src/components/experimental_/widgets/yield-protocol/actions/lend/YieldProtocolLend.tsx @@ -123,15 +123,28 @@ const YieldProtocolLend = ({ const [data, setData] = useState<{ seriesEntities: YieldSeriesEntity[] | undefined }>(); // all series entities use the same approval params - const approvalParams = useMemo( - () => ({ - tokenAddress: tokenInToUse?.address!, - spender: ladle!, - approvalAmount: amount?.value!, + const approvalParams = useMemo(() => { + if (!tokenInToUse) { + console.error(`No token address found for ${tokenInSymbol}`); + return undefined; + } + if (!ladle) { + console.error(`No ladle address found for ${tokenInSymbol}`); + return undefined; + } + + if (!amount?.value) { + console.error(`No amount found for ${tokenInSymbol}`); + return undefined; + } + + return { + tokenAddress: tokenInToUse.address, + spender: ladle, + approvalAmount: amount.value, skipApproval: tokenInIsETH, - }), - [amount, ladle, tokenInIsETH, tokenInToUse?.address] - ); + }; + }, [amount, ladle, tokenInIsETH, tokenInSymbol, tokenInToUse]); const getSendParams = useCallback( async (seriesEntity: YieldGraphResSeriesEntity) => { @@ -141,14 +154,24 @@ const YieldProtocolLend = ({ return undefined; } + if (!tokenInToUse?.address) { + console.error(`No token address found for ${tokenInSymbol}`); + return undefined; + } + + if (!amount?.value) { + console.error(`No amount found for ${tokenInSymbol}`); + return undefined; + } + return await lend({ - tokenInAddress: tokenInToUse?.address!, - amount: amount?.value!, + tokenInAddress: tokenInToUse.address, + amount: amount.value, poolAddress: poolAddress as Address, isEthBase: tokenInIsETH, }); }, - [amount, tokenInIsETH, tokenInToUse] // intentionally omitting lend cuz of infinite render issue; TODO make more kosher + [amount?.value, tokenInIsETH, tokenInSymbol, tokenInToUse?.address] // intentionally omitting lend cuz of infinite render issue; TODO make more kosher ); useEffect(() => { From b6d1eca39dc5c99b0a889ca5b8c803338dad9430 Mon Sep 17 00:00:00 2001 From: brucedonovan Date: Thu, 10 Aug 2023 10:06:36 +0100 Subject: [PATCH 22/22] bugfix: useBalance comparison gt->gte --- src/components/cactiComponents/hooks/useBalance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/cactiComponents/hooks/useBalance.ts b/src/components/cactiComponents/hooks/useBalance.ts index ba6de0ae..f9e59dc9 100644 --- a/src/components/cactiComponents/hooks/useBalance.ts +++ b/src/components/cactiComponents/hooks/useBalance.ts @@ -65,7 +65,7 @@ const useBalance = ( if (compareAmount && data) { setComparisons({ isZero: data.isZero(), - isGTEcompared: data.gt(compareAmount), + isGTEcompared: data.gte(compareAmount), isEQcompared: data.eq(compareAmount), isLTcompared: data.lt(compareAmount), });