• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

blockcoders / kuma-wallet / ebbd3c69-fda1-4bf1-80ea-77bef87d3d87

pending completion
ebbd3c69-fda1-4bf1-80ea-77bef87d3d87

Pull #8

circleci

Ruben
fix tests
Pull Request #8: Milestone 2

876 of 1103 branches covered (79.42%)

Branch coverage included in aggregate %.

3452 of 3452 new or added lines in 44 files covered. (100.0%)

6647 of 7185 relevant lines covered (92.51%)

6.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.84
/src/pages/send/components/EvmForm.tsx
1
import { FC, useEffect, useMemo, useState } from "react";
2✔
2
import { AccountType } from "@src/accounts/types";
2✔
3
import { Loading, LoadingButton } from "@src/components/common";
2✔
4
import Extension from "@src/Extension";
2✔
5
import { useToast } from "@src/hooks";
2✔
6
import { useAssetContext, useNetworkContext } from "@src/providers";
2✔
7
import { Contract, ethers, Wallet, BigNumber } from "ethers";
2✔
8
import { useFormContext } from "react-hook-form";
2✔
9
import { useTranslation } from "react-i18next";
2✔
10
import { CommonFormFields } from "./CommonFormFields";
2✔
11
import erc20abi from "@src/constants/erc20.abi.json";
2✔
12
import { Fees } from "./Fees";
2✔
13
import { confirmTx, evmTx, EVMFee } from "@src/types";
2✔
14
import { BN } from "bn.js";
2✔
15
import { BigNumber0 } from "@src/constants/assets";
2✔
16

2✔
17
interface EvmFormProps {
2✔
18
  confirmTx: confirmTx;
2✔
19
}
2✔
20

2✔
21
export const EvmForm: FC<EvmFormProps> = ({ confirmTx }) => {
2✔
22
  const { t } = useTranslation("send");
6✔
23

6✔
24
  const {
6✔
25
    state: { api, selectedChain },
6✔
26
  } = useNetworkContext();
6✔
27

6✔
28
  const {
6✔
29
    state: { assets },
6✔
30
  } = useAssetContext();
6✔
31

6✔
32
  const {
6✔
33
    handleSubmit,
6✔
34
    watch,
6✔
35
    formState: { errors },
6✔
36
  } = useFormContext();
6✔
37

6✔
38
  const { showErrorToast } = useToast();
6✔
39

6✔
40
  const [fee, setFee] = useState<EVMFee>({
6✔
41
    "gas limit": BigNumber0,
6✔
42
    "max fee per gas": BigNumber0,
6✔
43
    "max priority fee per gas": BigNumber0,
6✔
44
    "estimated fee": BigNumber0,
6✔
45
    "estimated total": BigNumber0,
6✔
46
  });
6✔
47
  const [isLoadingFee, setIsLoadingFee] = useState(false);
6✔
48
  const [wallet, setWallet] = useState<ethers.Wallet | null>(null);
6✔
49
  const [evmTx, setEvmTx] = useState<evmTx | Contract | null>(null);
6✔
50

6✔
51
  const _api = api as ethers.providers.JsonRpcProvider;
6✔
52
  const decimals = selectedChain?.nativeCurrency.decimals || 1;
6!
53
  const currencyUnits = 10 ** decimals;
6✔
54
  const amount = watch("amount");
6✔
55
  const asset = watch("asset");
6✔
56
  const isNativeAsset = asset?.id === "-1";
6✔
57
  const destinationAccount = watch("destinationAccount");
6✔
58
  const destinationIsInvalid = Boolean(errors?.destinationAccount?.message);
6!
59

6✔
60
  useEffect(() => {
6✔
61
    (async () => {
2✔
62
      const pk = await Extension.showPrivateKey();
2✔
63

2✔
64
      const wallet = new ethers.Wallet(
2✔
65
        pk as string,
2✔
66
        api as ethers.providers.JsonRpcProvider
2✔
67
      );
2✔
68

2✔
69
      setWallet(wallet);
2✔
70
    })();
2✔
71
  }, []);
6✔
72

6✔
73
  useEffect(() => {
6✔
74
    if (destinationIsInvalid || !destinationAccount || amount <= 0) return;
2!
75
    (async () => {
2✔
76
      setIsLoadingFee(true);
2✔
77

2✔
78
      try {
2✔
79
        const _amount = isNativeAsset
2✔
80
          ? amount * currencyUnits
2!
81
          : amount * 10 ** asset.decimals;
×
82

2✔
83
        const bnAmount = ethers.BigNumber.from(
2✔
84
          _amount.toLocaleString("fullwide", { useGrouping: false })
2✔
85
        );
2✔
86
        if (isNativeAsset) {
2✔
87
          let tx: evmTx = {
2✔
88
            to: destinationAccount,
2✔
89
            value: bnAmount,
2✔
90
          };
2✔
91

2✔
92
          const [feeData, gasLimit] = await Promise.all([
2✔
93
            _api.getFeeData(),
2✔
94
            _api.estimateGas(tx),
2✔
95
          ]);
2✔
96

2✔
97
          const _gasLimit = gasLimit;
2✔
98
          const _maxFeePerGas = feeData.maxFeePerGas as ethers.BigNumber;
2✔
99
          const _maxPriorityFeePerGas =
2✔
100
            feeData.maxPriorityFeePerGas as ethers.BigNumber;
2✔
101
          tx = {
2✔
102
            ...tx,
2✔
103
            gasLimit: _gasLimit,
2✔
104
            maxFeePerGas: _maxFeePerGas,
2✔
105
            maxPriorityFeePerGas: _maxPriorityFeePerGas,
2✔
106
            type: 2,
2✔
107
            value: bnAmount,
2✔
108
          };
2✔
109

2✔
110
          const avg = _maxFeePerGas
2✔
111
            .add(_maxPriorityFeePerGas)
2✔
112
            .div(ethers.BigNumber.from(2));
2✔
113
          const estimatedTotal = avg.mul(_gasLimit).add(bnAmount);
2✔
114

2✔
115
          setFee({
2✔
116
            "gas limit": _gasLimit,
2✔
117
            "max fee per gas": _maxFeePerGas,
2✔
118
            "max priority fee per gas": _maxPriorityFeePerGas,
2✔
119
            "estimated fee": avg,
2✔
120
            "estimated total": estimatedTotal,
2✔
121
          });
2✔
122

2✔
123
          setEvmTx(tx);
2✔
124
        } else {
2!
125
          const contract = new ethers.Contract(
×
126
            asset?.address,
×
127
            erc20abi,
×
128
            wallet as Wallet
×
129
          );
×
130

×
131
          const feeData = await _api.getFeeData();
×
132
          const gasLimit = await contract.estimateGas.transfer(
×
133
            destinationAccount,
×
134
            bnAmount
×
135
          );
×
136

×
137
          const _gasLimit = gasLimit;
×
138
          const _maxFeePerGas = feeData.maxFeePerGas as ethers.BigNumber;
×
139
          const _maxPriorityFeePerGas =
×
140
            feeData.maxPriorityFeePerGas as ethers.BigNumber;
×
141

×
142
          const avg = _maxFeePerGas.add(_maxPriorityFeePerGas).div(2);
×
143
          const estimatedTotal = avg.mul(_gasLimit);
×
144

×
145
          setFee({
×
146
            "gas limit": _gasLimit,
×
147
            "max fee per gas": feeData.maxFeePerGas as BigNumber,
×
148
            "max priority fee per gas":
×
149
              feeData.maxPriorityFeePerGas as BigNumber,
×
150
            "estimated fee": avg,
×
151
            "estimated total": estimatedTotal,
×
152
          });
×
153

×
154
          setEvmTx(contract);
×
155
        }
×
156
      } catch (error) {
2!
157
        showErrorToast(error);
×
158
      } finally {
2✔
159
        setIsLoadingFee(false);
2✔
160
      }
2✔
161
    })();
2✔
162
  }, [destinationAccount, destinationIsInvalid, amount, asset?.id]);
6✔
163

6✔
164
  const canContinue = Number(amount) > 0 && destinationAccount && !isLoadingFee;
6✔
165

6✔
166
  const onSubmit = handleSubmit(async () => {
6✔
167
    confirmTx({
6✔
168
      type: AccountType.EVM,
6✔
169
      tx: evmTx as evmTx,
6✔
170
      fee,
6✔
171
      sender: wallet as ethers.Wallet,
6✔
172
    });
6✔
173
  });
6✔
174

6✔
175
  const isEnoughToPay = useMemo(() => {
6✔
176
    if (!amount || !currencyUnits) return false;
6!
177

6✔
178
    try {
6✔
179
      const _amount = isNativeAsset
6✔
180
        ? amount * currencyUnits
6!
181
        : amount * 10 ** asset.decimals;
×
182

6✔
183
      const bnAmount = new BN(
6✔
184
        _amount.toLocaleString("fullwide", { useGrouping: false })
6✔
185
      );
6✔
186
      const estimatedTotal = fee["estimated total"];
6✔
187
      const BN0 = new BN("0");
6✔
188
      const nativeBalance = assets[0].balance;
6✔
189

6✔
190
      if (isNativeAsset) {
6✔
191
        return bnAmount.gt(BN0) && estimatedTotal.lte(nativeBalance);
6✔
192
      } else {
6!
193
        const BNBalance = new BN(asset?.balance);
×
194

×
195
        return (
×
196
          bnAmount.lte(BNBalance) &&
×
197
          estimatedTotal.gt(BigNumber0) &&
×
198
          estimatedTotal.lte(nativeBalance)
×
199
        );
×
200
      }
×
201
    } catch (error) {
6✔
202
      return false;
4✔
203
    }
4✔
204
  }, [fee, asset, amount]);
6✔
205

6✔
206
  return (
6✔
207
    <>
6✔
208
      <CommonFormFields />
6✔
209

6✔
210
      {isLoadingFee ? <Loading /> : <Fees fee={fee} />}
6✔
211

6✔
212
      {canContinue && !isEnoughToPay && (
6✔
213
        <p className="text-sm mt-2 text-red-500 text-center">
2✔
214
          {t("insufficient_balance")}
2✔
215
        </p>
2✔
216
      )}
6✔
217

6✔
218
      <LoadingButton
6✔
219
        classname="font-medium text-base bg-[#212529] hover:bg-custom-green-bg transition-all w-full py-2 md:py-4 rounded-md mt-7"
6✔
220
        isDisabled={!canContinue || !isEnoughToPay}
6✔
221
        onClick={onSubmit}
6✔
222
        style={{
6✔
223
          boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.25)",
6✔
224
        }}
6✔
225
      >
6✔
226
        {t("continue")}
6✔
227
      </LoadingButton>
6✔
228
    </>
6✔
229
  );
6✔
230
};
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc