• 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

90.6
/src/pages/send/components/WasmForm.tsx
1
import { FC, useEffect, useState, useMemo } from "react";
2✔
2
import { LoadingButton, Loading } from "@src/components/common";
2✔
3
import { useTranslation } from "react-i18next";
2✔
4
import { CommonFormFields } from "./CommonFormFields";
2✔
5
import { useFormContext } from "react-hook-form";
2✔
6
import {
2✔
7
  useAccountContext,
2✔
8
  useAssetContext,
2✔
9
  useNetworkContext,
2✔
10
} from "@src/providers";
2✔
11
import { ApiPromise } from "@polkadot/api";
2✔
12
import Extension from "@src/Extension";
2✔
13
import { Keyring } from "@polkadot/keyring";
2✔
14
import { KeyringPair } from "@polkadot/keyring/types";
2✔
15
import { useToast } from "@src/hooks";
2✔
16
import { AccountType } from "@src/accounts/types";
2✔
17
import { NumericFormat } from "react-number-format";
2✔
18
import { SubmittableExtrinsic } from "@polkadot/api/types";
2✔
19
import { BN } from "bn.js";
2✔
20
import { ContractPromise } from "@polkadot/api-contract";
2✔
21
import metadata from "@src/constants/metadata.json";
2✔
22
import { Fees } from "./Fees";
2✔
23
import { confirmTx, polkadotExtrinsic } from "@src/types";
2✔
24

2✔
25
const defaultFees = {
2✔
26
  "estimated fee": new BN("0"),
2✔
27
  "estimated total": new BN("0"),
2✔
28
};
2✔
29

2✔
30
interface WasmFormProps {
2✔
31
  confirmTx: confirmTx;
2✔
32
}
2✔
33

2✔
34
export const WasmForm: FC<WasmFormProps> = ({ confirmTx }) => {
2✔
35
  const { t } = useTranslation("send");
16✔
36

16✔
37
  const {
16✔
38
    state: { selectedAccount },
16✔
39
  } = useAccountContext();
16✔
40

16✔
41
  const {
16✔
42
    state: { selectedChain, api },
16✔
43
  } = useNetworkContext();
16✔
44

16✔
45
  const {
16✔
46
    state: { assets },
16✔
47
  } = useAssetContext();
16✔
48

16✔
49
  const {
16✔
50
    handleSubmit,
16✔
51
    watch,
16✔
52
    formState: { errors },
16✔
53
  } = useFormContext();
16✔
54

16✔
55
  const { showErrorToast } = useToast();
16✔
56

16✔
57
  const [fee, setFee] = useState(defaultFees);
16✔
58
  const [isLoadingFee, setIsLoadingFee] = useState(false);
16✔
59
  const [sender, setSender] = useState<KeyringPair | null>(null);
16✔
60
  const [extrinsic, setExtrinsic] = useState<polkadotExtrinsic | null>(null);
16✔
61
  const [aditional, setAditional] = useState({
16✔
62
    tip: "0",
16✔
63
  });
16✔
64

16✔
65
  const _api = api as ApiPromise;
16✔
66
  const decimals = selectedChain?.nativeCurrency.decimals || 1;
16!
67
  const currencyUnits = 10 ** decimals;
16✔
68
  const amount = watch("amount");
16✔
69
  const asset = watch("asset");
16✔
70
  const isNativeAsset = asset?.id === "-1";
16✔
71
  const destinationAccount = watch("destinationAccount");
16✔
72
  const destinationIsInvalid = Boolean(errors?.destinationAccount?.message);
16!
73

16✔
74
  useEffect(() => {
16✔
75
    (async () => {
4✔
76
      const seed = await Extension.showSeed();
4✔
77
      const keyring = new Keyring({ type: "sr25519" });
4✔
78
      const sender = keyring.addFromMnemonic(seed as string);
4✔
79
      setSender(sender);
4✔
80
    })();
4✔
81
  }, []);
16✔
82

16✔
83
  const onSubmit = handleSubmit(async () => {
16✔
84
    confirmTx({
16✔
85
      type: AccountType.WASM,
16✔
86
      tx: extrinsic as polkadotExtrinsic,
16✔
87
      aditional: {
16✔
88
        tip: Number(aditional.tip) * currencyUnits || "0",
16✔
89
      },
16✔
90
      sender: sender as KeyringPair,
16✔
91
      fee,
16✔
92
    });
16✔
93
  });
16✔
94

16✔
95
  useEffect(() => {
16✔
96
    if (destinationIsInvalid) {
4!
97
      setFee(defaultFees);
×
98
      return;
×
99
    }
×
100

4✔
101
    if (!destinationAccount || amount <= 0) return;
4!
102

4✔
103
    setIsLoadingFee(true);
4✔
104

4✔
105
    const loadFees = setTimeout(async () => {
4✔
106
      await getFeeData();
4✔
107
      setIsLoadingFee(false);
4✔
108
    }, 1000);
4✔
109

4✔
110
    return () => clearTimeout(loadFees);
4✔
111
  }, [amount, destinationAccount, destinationIsInvalid, asset?.id]);
16✔
112

16✔
113
  const getFeeData = async () => {
16✔
114
    if (!destinationAccount) return;
4!
115
    try {
4✔
116
      const _amount = isNativeAsset
4✔
117
        ? amount * currencyUnits
2✔
118
        : amount * 10 ** asset.decimals;
2✔
119

4✔
120
      const bnAmount = new BN(
4✔
121
        String(_amount.toLocaleString("fullwide", { useGrouping: false }))
4✔
122
      );
4✔
123

4✔
124
      let extrinsic: SubmittableExtrinsic<"promise"> | unknown;
4✔
125
      let estimatedFee = new BN("0");
4✔
126

4✔
127
      if (asset?.address) {
4✔
128
        // extrinsic from contract assets
2✔
129
        const refTime = new BN("1000000000000");
2✔
130
        const proofSize = new BN("1000000000000");
2✔
131
        const contract = new ContractPromise(api, metadata, asset.address);
2✔
132
        const { gasRequired } = await contract.query.transfer(
2✔
133
          selectedAccount.value.address,
2✔
134
          {
2✔
135
            gasLimit: api.registry.createType("WeightV2", {
2✔
136
              refTime,
2✔
137
              proofSize,
2✔
138
            }),
2✔
139
          },
2✔
140
          destinationAccount,
2✔
141
          bnAmount
2✔
142
        );
2✔
143

2✔
144
        extrinsic = contract.tx.transfer(
2✔
145
          {
2✔
146
            gasLimit: api.registry.createType("WeightV2", gasRequired),
2✔
147
          },
2✔
148
          destinationAccount,
2✔
149
          bnAmount
2✔
150
        );
2✔
151

2✔
152
        const { proofSize: _proofSize, refTime: _refTime } =
2✔
153
          gasRequired.toJSON();
2✔
154

2✔
155
        estimatedFee = new BN(String(_proofSize)).add(new BN(String(_refTime)));
2✔
156
      } else {
2✔
157
        if (asset?.id === "-1") {
2✔
158
          extrinsic = _api.tx.balances.transfer(destinationAccount, bnAmount);
2✔
159
        } else {
2!
160
          extrinsic = _api.tx.assets.transfer(
×
161
            asset.id,
×
162
            destinationAccount,
×
163
            bnAmount
×
164
          );
×
165
        }
×
166

2✔
167
        const { partialFee } = await (
2✔
168
          extrinsic as SubmittableExtrinsic<"promise">
2✔
169
        ).paymentInfo(sender as KeyringPair);
2!
170
        estimatedFee = partialFee;
×
171
      }
×
172

2✔
173
      setExtrinsic(extrinsic);
2✔
174

2✔
175
      const amounToShow =
2✔
176
        asset?.id === "-1" ? bnAmount.add(estimatedFee) : estimatedFee;
4!
177

4✔
178
      setFee({
4✔
179
        "estimated fee": estimatedFee,
4✔
180
        "estimated total": amounToShow,
4✔
181
      });
4✔
182
    } catch (error) {
4✔
183
      showErrorToast(error);
2✔
184
      setFee(defaultFees);
2✔
185
    }
2✔
186
  };
4✔
187

16✔
188
  const canContinue = Number(amount) > 0 && destinationAccount && !isLoadingFee;
16✔
189

16✔
190
  const isEnoughToPay = useMemo(() => {
16✔
191
    if (!amount || !currencyUnits) return false;
16!
192

16✔
193
    try {
16✔
194
      const _amount = isNativeAsset
16✔
195
        ? amount * currencyUnits
8✔
196
        : amount * 10 ** asset.decimals;
8✔
197

16✔
198
      const bnAmount = new BN(
16✔
199
        _amount.toLocaleString("fullwide", { useGrouping: false })
16✔
200
      );
16✔
201
      const estimatedTotal = fee["estimated total"];
16✔
202
      const BN0 = new BN("0");
16✔
203
      const nativeBalance = assets[0].balance;
16✔
204

16✔
205
      if (isNativeAsset) {
16✔
206
        return bnAmount.gt(BN0) && estimatedTotal.lte(nativeBalance);
8✔
207
      } else {
8✔
208
        const BNBalance = new BN(asset?.balance);
8✔
209

8✔
210
        return (
8✔
211
          bnAmount.lte(BNBalance) &&
8✔
212
          estimatedTotal.gt(BN0) &&
8✔
213
          estimatedTotal.lte(nativeBalance)
2✔
214
        );
8✔
215
      }
8✔
216
    } catch (error) {
16!
217
      return false;
×
218
    }
×
219
  }, [fee, asset, amount]);
16✔
220

16✔
221
  return (
16✔
222
    <>
16✔
223
      <CommonFormFields />
16✔
224

16✔
225
      <div className="mb-3">
16✔
226
        <p>{t("tip")}</p>
16✔
227
        <div className="text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 flex w-full p-2.5 bg-[#343A40] border-gray-600 placeholder-gray-400 text-white">
16✔
228
          <NumericFormat
16✔
229
            className="bg-transparent w-8/12 outline-0"
16✔
230
            allowNegative={false}
16✔
231
            allowLeadingZeros={false}
16✔
232
            value={aditional.tip}
16✔
233
            onValueChange={({ value }) => {
16✔
234
              setAditional((state) => ({
×
235
                ...state,
×
236
                tip: value,
×
237
              }));
×
238
            }}
×
239
            allowedDecimalSeparators={["%"]}
16✔
240
          />
16✔
241
        </div>
16✔
242
      </div>
16✔
243

16✔
244
      {isLoadingFee ? <Loading /> : <Fees fee={fee} />}
16✔
245

16✔
246
      {canContinue && !isEnoughToPay && (
16✔
247
        <p className="text-sm mt-2 text-red-500 text-center">
2✔
248
          {t("insufficient_balance")}
2✔
249
        </p>
2✔
250
      )}
16✔
251

16✔
252
      <LoadingButton
16✔
253
        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"
16✔
254
        isDisabled={!canContinue || !isEnoughToPay}
16✔
255
        onClick={onSubmit}
16✔
256
        style={{
16✔
257
          boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.25)",
16✔
258
        }}
16✔
259
      >
16✔
260
        {t("continue")}
16✔
261
      </LoadingButton>
16✔
262
    </>
16✔
263
  );
16✔
264
};
4✔
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