diff --git a/src/pages/send/Send.tsx b/src/pages/send/Send.tsx
index 0c8a6aaa..4a3ebd10 100644
--- a/src/pages/send/Send.tsx
+++ b/src/pages/send/Send.tsx
@@ -17,6 +17,7 @@ import { FiChevronLeft } from "react-icons/fi";
import { IAsset, SendForm, Tx, TxToProcess } from "@src/types";
import { BigNumber, Contract } from "ethers";
import { getWebAPI } from "@src/utils/env";
+import { MapResponseEVM, XCM_MAPPING } from "@src/constants/xcm";
const WebAPI = getWebAPI();
@@ -97,8 +98,8 @@ export const Send = () => {
const originAddress = selectedAccount.value.address;
const asset = getValues("asset") as IAsset;
const destinationNetwork = getValues("to").name;
-
- const { id } = await WebAPI.windows.getCurrent();
+ const isXcm = getValues("isXcm");
+ const to = getValues("to");
const txToSend: Partial
= {
amount,
@@ -130,7 +131,25 @@ export const Send = () => {
_amount.toLocaleString("fullwide", { useGrouping: false })
);
- if (isNativeAsset) {
+ if (isXcm) {
+ const { method, extrinsicValues } = XCM_MAPPING[selectedChain.name][
+ to.name
+ ]({
+ address: destinationAddress,
+ amount: bnAmount,
+ }) as MapResponseEVM;
+
+ // TODO: refactor
+ _tx = await (tx?.tx as Contract)[method](
+ ...Object.keys(extrinsicValues).map((key) => extrinsicValues[key]),
+ {
+ gasLimit: tx?.fee["gas limit"],
+ maxFeePerGas: tx?.fee["max fee per gas"],
+ maxPriorityFeePerGas: tx?.fee["max priority fee per gas"],
+ type: 2,
+ }
+ );
+ } else if (isNativeAsset) {
_tx = await tx?.sender.sendTransaction({
...tx.tx,
});
@@ -142,7 +161,6 @@ export const Send = () => {
gasLimit: tx?.fee["gas limit"],
maxFeePerGas: tx?.fee["max fee per gas"],
maxPriorityFeePerGas: tx?.fee["max priority fee per gas"],
- type: 2,
}
);
}
@@ -153,6 +171,8 @@ export const Send = () => {
};
}
+ const { id } = await WebAPI.windows.getCurrent();
+
await WebAPI.runtime.sendMessage({
from: "popup",
origin: "kuma",
@@ -168,7 +188,6 @@ export const Send = () => {
},
});
} catch (error) {
- // const _err = String(error).split('\\"message\\":\\"')[1].split('\\"}')[0];
showErrorToast(error);
}
endLoading();
diff --git a/src/pages/send/components/CommonFormFields.tsx b/src/pages/send/components/CommonFormFields.tsx
index 180ed0fb..f9cf1079 100644
--- a/src/pages/send/components/CommonFormFields.tsx
+++ b/src/pages/send/components/CommonFormFields.tsx
@@ -8,8 +8,7 @@ import { SelectableAsset } from "./SelectableAsset";
import { SelectableChain } from "./SelectableChain";
import { useNetworkContext } from "@src/providers";
import { useEffect, useState } from "react";
-import { XCM_MAPPING } from "@src/constants/xcm";
-import Extension from "../../../Extension";
+import Extension from "@src/Extension";
export const CommonFormFields = () => {
const { t } = useTranslation("send");
@@ -21,9 +20,12 @@ export const CommonFormFields = () => {
const {
setValue,
getValues,
+ watch,
formState: { errors },
} = useFormContext();
+ const to = watch("to");
+
const [destinationChains, setDestinationChains] = useState([]);
const getDestinationChains = async () => {
@@ -43,6 +45,10 @@ export const CommonFormFields = () => {
}
}, [selectedChain]);
+ useEffect(() => {
+ setValue("isXcm", to.name !== selectedChain.name);
+ }, [to]);
+
return (
<>
diff --git a/src/pages/send/components/EvmForm.tsx b/src/pages/send/components/EvmForm.tsx
index 19bcdfbf..98cf8bcc 100644
--- a/src/pages/send/components/EvmForm.tsx
+++ b/src/pages/send/components/EvmForm.tsx
@@ -11,8 +11,8 @@ import { CommonFormFields } from "./CommonFormFields";
import erc20abi from "@src/constants/erc20.abi.json";
import { Fees } from "./Fees";
import { confirmTx, evmTx, EVMFee } from "@src/types";
-import { BN } from "bn.js";
import { BigNumber0 } from "@src/constants/assets";
+import { MapResponseEVM, XCM_MAPPING } from "@src/constants/xcm";
interface EvmFormProps {
confirmTx: confirmTx;
@@ -32,6 +32,7 @@ export const EvmForm: FC
= ({ confirmTx }) => {
const {
handleSubmit,
watch,
+ getValues,
formState: { errors },
} = useFormContext();
@@ -83,7 +84,54 @@ export const EvmForm: FC = ({ confirmTx }) => {
const bnAmount = ethers.BigNumber.from(
_amount.toLocaleString("fullwide", { useGrouping: false })
);
- if (isNativeAsset) {
+
+ const isXcm = getValues("isXcm");
+ const to = getValues("to");
+
+ if (isXcm) {
+ const { method, abi, contractAddress, extrinsicValues } = XCM_MAPPING[
+ selectedChain.name
+ ][to.name]({
+ address: destinationAccount,
+ amount: bnAmount,
+ }) as MapResponseEVM;
+
+ const contract = new ethers.Contract(
+ contractAddress,
+ abi,
+ wallet as Wallet
+ );
+
+ const feeData = await _api.getFeeData();
+ const gasLimit = await contract.estimateGas[method](
+ ...Object.keys(extrinsicValues).map((key) => extrinsicValues[key])
+ ).catch((e) => {
+ return BigNumber.from("21000");
+ });
+
+ const _gasLimit = gasLimit;
+ const _maxFeePerGas = feeData.maxFeePerGas as ethers.BigNumber;
+ const _maxPriorityFeePerGas =
+ feeData.maxPriorityFeePerGas as ethers.BigNumber;
+
+ const avg = _maxFeePerGas.add(_maxPriorityFeePerGas).div(2);
+ let estimatedTotal = avg.mul(_gasLimit);
+
+ if (isNativeAsset) {
+ estimatedTotal = estimatedTotal.add(bnAmount);
+ }
+
+ setFee({
+ "gas limit": _gasLimit,
+ "max fee per gas": feeData.maxFeePerGas as BigNumber,
+ "max priority fee per gas":
+ feeData.maxPriorityFeePerGas as BigNumber,
+ "estimated fee": avg,
+ "estimated total": estimatedTotal,
+ });
+
+ setEvmTx(contract);
+ } else if (isNativeAsset) {
let tx: evmTx = {
to: destinationAccount,
value: bnAmount,
@@ -154,6 +202,7 @@ export const EvmForm: FC = ({ confirmTx }) => {
setEvmTx(contract);
}
} catch (error) {
+ console.error(error);
showErrorToast(error);
} finally {
setIsLoadingFee(false);
@@ -177,20 +226,19 @@ export const EvmForm: FC = ({ confirmTx }) => {
try {
const _amount = isNativeAsset
- ? amount * currencyUnits
- : amount * 10 ** asset.decimals;
+ ? Number(amount) * currencyUnits
+ : Number(amount) * 10 ** asset.decimals;
- const bnAmount = new BN(
+ const bnAmount = BigNumber.from(
_amount.toLocaleString("fullwide", { useGrouping: false })
);
const estimatedTotal = fee["estimated total"];
- const BN0 = new BN("0");
const nativeBalance = assets[0].balance;
if (isNativeAsset) {
- return bnAmount.gt(BN0) && estimatedTotal.lte(nativeBalance);
+ return bnAmount.gt(BigNumber0) && estimatedTotal.lte(nativeBalance);
} else {
- const BNBalance = new BN(asset?.balance);
+ const BNBalance = BigNumber.from(asset?.balance);
return (
bnAmount.lte(BNBalance) &&
@@ -201,7 +249,7 @@ export const EvmForm: FC = ({ confirmTx }) => {
} catch (error) {
return false;
}
- }, [fee, asset, amount]);
+ }, [fee, asset, amount, isNativeAsset]);
return (
<>
diff --git a/src/pages/send/components/SelectableAsset.tsx b/src/pages/send/components/SelectableAsset.tsx
index 2b6810cc..a2fe6f7b 100644
--- a/src/pages/send/components/SelectableAsset.tsx
+++ b/src/pages/send/components/SelectableAsset.tsx
@@ -4,6 +4,8 @@ import { useAssetContext } from "@src/providers";
import { FiChevronDown } from "react-icons/fi";
import { Asset } from "@src/providers/assetProvider/types";
import { AssetIcon } from "@src/components/common/AssetIcon";
+import { useFormContext } from "react-hook-form";
+import { XCM_ASSETS_MAPPING } from "@src/constants/xcm";
interface SelectableAssetProps {
onChangeAsset: (asset: Asset) => void;
@@ -16,8 +18,12 @@ export const SelectableAsset: FC = ({
state: { assets },
} = useAssetContext();
+ const { watch, getValues } = useFormContext();
+
const [selectedAsset, setSelectedAsset] = useState(null);
+ const to = watch("to");
+
const _onChangeAsset = (asset: Asset) => {
setSelectedAsset(asset);
onChangeAsset?.(asset);
@@ -28,11 +34,24 @@ export const SelectableAsset: FC = ({
);
useEffect(() => {
- if (assets.length > 0) {
- setSelectedAsset(assets[0]);
- onChangeAsset(assets[0]);
+ const from = getValues("from");
+ const isXcm = to.name !== getValues("from").name;
+ if (isXcm) {
+ const xcmAssets = XCM_ASSETS_MAPPING[from.name]?.[to.name] || [];
+
+ const filteredAssets = assets.filter(({ symbol }) =>
+ xcmAssets.includes(symbol)
+ );
+
+ setSelectedAsset(filteredAssets[0]);
+ onChangeAsset(filteredAssets[0]);
+ } else {
+ if (assets.length > 0) {
+ setSelectedAsset(assets[0]);
+ onChangeAsset(assets[0]);
+ }
}
- }, [assets]);
+ }, [assets, to]);
return (
diff --git a/src/pages/send/components/WasmForm.tsx b/src/pages/send/components/WasmForm.tsx
index 39ff741a..6caa6701 100644
--- a/src/pages/send/components/WasmForm.tsx
+++ b/src/pages/send/components/WasmForm.tsx
@@ -211,11 +211,6 @@ export const WasmForm: FC = ({ confirmTx }) => {
return () => clearTimeout(loadFees);
}, [amount, destinationAccount, destinationIsInvalid, asset?.id, to?.name]);
- useEffect(() => {
- // setIsXcm(to.name !== selectedChain.name);
- setValue("isXcm", to.name !== selectedChain.name);
- }, [to]);
-
const canContinue = Number(amount) > 0 && destinationAccount && !isLoadingFee;
const isEnoughToPay = useMemo(() => {
@@ -247,7 +242,7 @@ export const WasmForm: FC = ({ confirmTx }) => {
} catch (error) {
return false;
}
- }, [fee, asset, amount]);
+ }, [fee, asset, amount, isNativeAsset]);
return (
<>
diff --git a/src/providers/assetProvider/AssetProvider.tsx b/src/providers/assetProvider/AssetProvider.tsx
index 172b79e4..f0d4f161 100644
--- a/src/providers/assetProvider/AssetProvider.tsx
+++ b/src/providers/assetProvider/AssetProvider.tsx
@@ -170,7 +170,7 @@ export const AssetProvider: FC = ({ children }) => {
setUnsubscribers((state) => [...state, unsub]);
} else {
const _api = api as ethers.providers.JsonRpcProvider;
- _api.removeAllListeners("block");
+ _api.off("block");
_api.on("block", () => {
_api.getBalance(account.value.address).then((balance) => {
@@ -346,6 +346,7 @@ export const AssetProvider: FC = ({ children }) => {
const selfAddress = selectedAccount?.value?.address;
if (from === selfAddress || to === selfAddress) {
const balance = await contract.balanceOf(accountAddress);
+
dispatch({
type: "update-one-asset",
payload: {
diff --git a/src/providers/networkProvider/NetworkProvider.tsx b/src/providers/networkProvider/NetworkProvider.tsx
index cbf6d67a..a513f2be 100644
--- a/src/providers/networkProvider/NetworkProvider.tsx
+++ b/src/providers/networkProvider/NetworkProvider.tsx
@@ -129,7 +129,9 @@ export const NetworkProvider: FC = ({ children }) => {
network.rpc[accountType.toLowerCase() as "evm" | "wasm"] || "";
if (state.api && "getBalance" in state.api) {
- (state.api as ethers.providers.JsonRpcProvider).removeAllListeners();
+ (state.api as ethers.providers.JsonRpcProvider).removeAllListeners(
+ "block"
+ );
}
dispatch({
diff --git a/src/storage/entities/Assets.ts b/src/storage/entities/Assets.ts
index 30d07c84..0a374f28 100644
--- a/src/storage/entities/Assets.ts
+++ b/src/storage/entities/Assets.ts
@@ -1,3 +1,4 @@
+import { defaultAssetConfig } from "@src/constants/assets";
import BaseEntity from "./BaseEntity";
interface Asset {
@@ -13,7 +14,7 @@ export default class Assets extends BaseEntity {
constructor() {
super();
- this.data = {};
+ this.data = defaultAssetConfig;
}
static async getDefaultValue(): Promise {