dApp different decimals

Hello,

I've created a dApp that has a form where people can "invest" (stake) their tokens. The tokens I've made available are USDC, DAI, and WETH. But, whenever I try to stake USDC I get the below error. I have a feeling it may have to do with the fact that USDC has 6 decimals and the others have 18. Further, I indeed do have enough test USDC to do the transaction.

Please let me know if you have any suggestions or would like me to clarify!

error:
image

form:
Screen Shot 2022-10-24 at 7.07.48 AM

hi @Gimmy
we can't help if you don't provide source code

Apologies for that.

Here is the staking code from the smart contract:

function stakeTokens(uint256 _amount, address _token) public nonReentrant {
        require(fund_state == FUND_STATE.OPEN);
        require(_amount > 0, "Amount must be more than zero!");
        require(tokenIsAllowed(_token), "Token is currently not allowed!");
        (uint256 price, uint256 decimals) = getTokenValue(_token);
        require(
            totalRaised + ((_amount * price) / ((10**decimals))) < goal,
            "Too much money"
        );
        IERC20(_token).transferFrom(msg.sender, address(this), _amount);
        updateUniqueTokensStaked(msg.sender, _token);
        stakingBalance[_token][msg.sender] =
            stakingBalance[_token][msg.sender] +
            _amount;
        if (uniqueTokensStaked[msg.sender] == 1) {
            stakers.push(msg.sender);
        }
        totalRaised = uint256(
            totalRaised + ((_amount * price) / ((10**decimals)))
        );
    }

Here is the code for the form:

export const Fund1Form = ({ token }: StakeFormProps) => {
    const { address: tokenAddress, name } = token
    const { account } = useEthers()
    const tokenBalance = useTokenBalance(tokenAddress, account)
    const formattedTokenBalance: number = tokenBalance ? parseFloat(formatUnits(tokenBalance, 18)) : 0

    const [amount, setAmount] = useState<number | string | Array<number | string>>(0)
    const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        const newAmount = event.target.value === "" ? "" : Number(event.target.value)
        setAmount(newAmount)
        console.log(newAmount)
    }

    const { approveAndStake, approveAndStakeErc20State } = useStakeTokens(tokenAddress)
    const handleStakeSubmit = () => {
        const amountAsWei = utils.parseEther(amount.toString())
        return approveAndStake(amountAsWei.toString())
    }

    const navigate = useNavigate();

    return (
        <>
            <Box>
                <div className="flex flex-1 justify-items-start flex-col md:mt-1 -mx-6">
                    <div className="p-6 flex flex-1 justify-items-start flex-col md:mt-1 bg-[#D8E1E6] rounded-lg mb-px">
                        <a className="flex place-content-start font-bold text-[#28282b]">
                            Investment Amount
                        </a>
                        <input
                            className="my-2 w-full rounded-md p-2 outline-none text-black border-[#000000] border-[1.5px] text-sm h-12"
                            placeholder="Amount"
                            step="0.0001"
                            type="number"
                            onChange={handleInputChange}
                        />
                        <ButtonUnstyled
                            onClick={handleStakeSubmit}
                            className="text-[#28282b] font-bold w-full mt-2 border-[2.5px] p-2 border-[#2952e3] hover:bg-[#5c8193] bg-[#f7f9fa] rounded-full cursor-pointer"
                        >
                            INVEST
                        </ButtonUnstyled>
                    </div>
                </div>
            </Box>
        </>
    )
}

and here is the code for the hook:

export const useStakeTokens = (tokenAddress: string) => {
    const { chainId } = useEthers()
    console.log({ chainId })
    const { abi } = TokenFarm
    const tokenFarmAddress = chainId ? networkMapping[String(chainId)]["TokenFarm"][0] : constants.AddressZero
    console.log(tokenFarmAddress)
    const tokenFarmInterface = new utils.Interface(abi)
    const tokenFarmContract = new Contract(tokenFarmAddress, tokenFarmInterface)

    const erc20ABI = ERC20.abi
    const erc20Interface = new utils.Interface(erc20ABI)
    const erc20Contract = new Contract(tokenAddress, erc20Interface)

    const { send: approveErc20Send, state: approveAndStakeErc20State } =
        useContractFunction(erc20Contract, "approve", {
            transactionName: "Approve ERC20 transfer",
        })

    const approveAndStake = (amount: string) => {
        setAmountToStake(amount)
        return approveErc20Send(tokenFarmAddress, amount)
    }

    const { send: stakeSend, state: stakeState } =
        useContractFunction(tokenFarmContract, "stakeTokens", {
            transactionName: "Stake Tokens",
        })
    const [amountToStake, setAmountToStake] = useState("0")

    useEffect(() => {
        if (approveAndStakeErc20State.status === "Success") {
            stakeSend(amountToStake, tokenAddress);
        }

    }, [approveAndStakeErc20State])

    const [state, setState] = useState(approveAndStakeErc20State)

    return { approveAndStake, approveErc20Send }

}

For reference, I took heavy inspiration from FreeCodeCamp's 16 hour solidity tutorial with Patrick Collins

Without seeing your code its hard to give an answer. As you mentioned you're expecting the decimals to be the issue. So that would be in amount you send as value to the smart contract.

So calculate the "correct" amount you need to use use: niceamount * (10 ** decimals) and use that as the "real" amount.

so from the token contract retreive the decimals() values. then use the above code to convert the nicely input amount of for example 10 USDC to the correct transfer amount.

1 Like

Hi CryptoWorld, thanks for the reply! So in my smart contract code above, would I have to do something like:

IERC20(_token).transferFrom(msg.sender, address(this), _amount*(10 ** decimals));

Thanks!

I haven’t looked at all the code, however inyour react code you perform: amountAsWei = utils.parseEther(amount.toString()) which is incorrect. This is using the parseether function without the correct unit, it’s assuming your unit is eth which is a 16 decimal value.

The transfer code you suggest isn’t correct, it should transfer the amount itself, not try and convert it again and the amount you received should already be in wei.

(removed the solidity decimal comment as you where using it from the token, assuming that’s what the function returned)

Ah ok I see, that makes sense.

So instead of using parseEther, what do you think I should use?

Sorry if this is a dumb question, still quite new to all this.

ethers.until.parseUnits(value.toString(), decimals)

1 Like

sounds good, I'll try that out! Thanks a lot for the help

You can't use parseEther if the token doesn't have 18 decimals
Use parseUnits() instead

EDIT
Just saw @CryptoWorld answer :sweat_smile:

Oep, yeah parseUnit not parseEther :slight_smile: