diff --git a/src/abi/xtokens_moonbeam_abi.json b/src/abi/xtokens_moonbeam_abi.json new file mode 100644 index 00000000..55284058 --- /dev/null +++ b/src/abi/xtokens_moonbeam_abi.json @@ -0,0 +1,312 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "currencyAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "destination", + "type": "tuple" + }, + { + "internalType": "uint64", + "name": "weight", + "type": "uint64" + } + ], + "name": "transfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "location", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Xtokens.MultiAsset[]", + "name": "assets", + "type": "tuple[]" + }, + { + "internalType": "uint32", + "name": "feeItem", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "destination", + "type": "tuple" + }, + { + "internalType": "uint64", + "name": "weight", + "type": "uint64" + } + ], + "name": "transferMultiAssets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "currencyAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Xtokens.Currency[]", + "name": "currencies", + "type": "tuple[]" + }, + { + "internalType": "uint32", + "name": "feeItem", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "destination", + "type": "tuple" + }, + { + "internalType": "uint64", + "name": "weight", + "type": "uint64" + } + ], + "name": "transferMultiCurrencies", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "asset", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "destination", + "type": "tuple" + }, + { + "internalType": "uint64", + "name": "weight", + "type": "uint64" + } + ], + "name": "transferMultiasset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "asset", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "destination", + "type": "tuple" + }, + { + "internalType": "uint64", + "name": "weight", + "type": "uint64" + } + ], + "name": "transferMultiassetWithFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "currencyAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "parents", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "interior", + "type": "bytes[]" + } + ], + "internalType": "struct Xtokens.Multilocation", + "name": "destination", + "type": "tuple" + }, + { + "internalType": "uint64", + "name": "weight", + "type": "uint64" + } + ], + "name": "transferWithFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/src/constants/assets.ts b/src/constants/assets.ts index e8fa2d13..1557e408 100644 --- a/src/constants/assets.ts +++ b/src/constants/assets.ts @@ -1,8 +1,37 @@ import { BN } from "bn.js"; import { BigNumber } from "ethers"; +import { PARACHAINS } from "./chains"; export const REF_TIME = new BN("1000000000000"); export const PROOF_SIZE = new BN("1000000000000"); export const BigNumber0 = BigNumber.from(0); export const BN0 = new BN("0"); + +export const defaultAssetConfig = { + [PARACHAINS.MOONBEAM]: [ + { + address: "0xFfFFfFff1FcaCBd218EDc0EbA20Fc2308C778080", + symbol: "xcDOT", + decimals: 10, + }, + { + address: "0xFfFFFfffA893AD19e540E172C10d78D4d479B5Cf", + symbol: "xcASTR", + decimals: 18, + }, + ], + + [PARACHAINS.MOONRIVER]: [ + { + address: "0xFfFFfFff1FcaCBd218EDc0EbA20Fc2308C778080", + symbol: "xcKSM", + decimals: 12, + }, + { + address: "0xFFFfffFF0Ca324C842330521525E7De111F38972", + symbol: "xcSDN", + decimals: 18, + }, + ], +}; diff --git a/src/constants/xcm.ts b/src/constants/xcm.ts index c3f0d1ee..be976e72 100644 --- a/src/constants/xcm.ts +++ b/src/constants/xcm.ts @@ -1,4 +1,4 @@ -import { BN, u8aToHex } from "@polkadot/util"; +import { BN, numberToHex, u8aToHex } from "@polkadot/util"; import { KUSAMA_PARACHAINS, PARACHAINS, @@ -7,6 +7,8 @@ import { } from "./chains"; import { decodeAddress } from "@polkadot/util-crypto"; import { BN0 } from "./assets"; +import xTokensAbi from "@src/abi/xtokens_moonbeam_abi.json"; +import { BigNumberish } from "ethers"; export const XCM = { pallets: { @@ -56,7 +58,7 @@ export const getAssets = ({ parents = 0, }: { version?: "V0" | "V1" | "V2" | "V3"; - fungible: BN; + fungible: BN | BigNumberish | string; interior?: "Here" | any; parents?: 0 | 1; }) => { @@ -94,7 +96,7 @@ export const getAssets = ({ interface ExtrinsicValues { address: string; - amount: BN; + amount: BN | BigNumberish | string; assetSymbol?: string; } @@ -124,13 +126,20 @@ export const getBeneficiary = ({ }; }; -interface MapResponse { +export interface MapResponseXCM { pallet: string; method: string; extrinsicValues: any; } -type Map = (props: ExtrinsicValues) => MapResponse; +export interface MapResponseEVM { + contractAddress: string; + abi: string; + method: string; + extrinsicValues: any; +} + +type Map = (props: ExtrinsicValues) => MapResponseXCM | MapResponseEVM; interface IXCM_MAPPING { [key: string]: { @@ -139,8 +148,8 @@ interface IXCM_MAPPING { } export const XCM_MAPPING: IXCM_MAPPING = { - [RELAY_CHAINS.POLKADOT as string]: { - [POLKADOT_PARACHAINS.ASTAR.name as string]: ({ address, amount }) => ({ + [RELAY_CHAINS.POLKADOT]: { + [POLKADOT_PARACHAINS.ASTAR.name]: ({ address, amount }) => ({ pallet: XCM.pallets.XCM_PALLET.NAME, method: XCM.pallets.XCM_PALLET.methods.RESERVE_TRANSFER_ASSETS, extrinsicValues: { @@ -156,7 +165,7 @@ export const XCM_MAPPING: IXCM_MAPPING = { feeAssetItem: 0, }, }), - [POLKADOT_PARACHAINS.MOONBEAM.name as string]: ({ address, amount }) => ({ + [POLKADOT_PARACHAINS.MOONBEAM.name]: ({ address, amount }) => ({ pallet: XCM.pallets.XCM_PALLET.NAME, method: XCM.pallets.XCM_PALLET.methods.LIMITED_RESERVE_TRANSFER_ASSETS, extrinsicValues: { @@ -179,6 +188,130 @@ export const XCM_MAPPING: IXCM_MAPPING = { }), }, + [PARACHAINS.MOONBEAM]: { + [RELAY_CHAINS.POLKADOT]: ({ address, amount }) => { + const _address = + "0x01" + u8aToHex(decodeAddress(address), undefined, false) + "00"; + + return { + contractAddress: "0x0000000000000000000000000000000000000804", + abi: xTokensAbi as any, + method: "transfer", + extrinsicValues: { + currency_address: "0xFfFFfFff1FcaCBd218EDc0EbA20Fc2308C778080", //asset address + amount: amount.toString(), + destination: [1, [_address]], + weight: "4000000000", // Weight + }, + }; + }, + + [PARACHAINS.ASTAR]: ({ address, amount, assetSymbol }) => { + const addressIsHex = address.startsWith("0x"); + + const _address = addressIsHex + ? "0x03" + address.slice(2) + "00" + : "0x01" + u8aToHex(decodeAddress(address), undefined, false) + "00"; + + let currency_address = ""; + + switch (assetSymbol?.toLowerCase()) { + case "astr": { + currency_address = "0x0000000000000000000000000000000000000802"; + break; + } + case "xcastr": { + currency_address = "0xFfFFFfffA893AD19e540E172C10d78D4d479B5Cf"; + break; + } + default: + throw new Error("Invalid asset symbol"); + } + + return { + contractAddress: "0x0000000000000000000000000000000000000804", + abi: xTokensAbi as any, + method: "transfer", + extrinsicValues: { + currency_address, //asset address + amount: amount.toString(), + destination: [ + 1, + [ + "0x00" + + "0000" + + numberToHex(POLKADOT_PARACHAINS.ASTAR.id).split("0x")[1], + _address, + ], + ], + weight: "4000000000", // Weight + }, + }; + }, + }, + + [PARACHAINS.MOONRIVER]: { + [RELAY_CHAINS.KUSAMA]: ({ address, amount }) => { + const _address = + "0x01" + u8aToHex(decodeAddress(address), undefined, false) + "00"; + + return { + contractAddress: "0x0000000000000000000000000000000000000804", + abi: xTokensAbi as any, + method: "transfer", + extrinsicValues: { + currency_address: "0xFfFFfFff1FcaCBd218EDc0EbA20Fc2308C778080", //asset address + amount: amount.toString(), + destination: [1, [_address]], + weight: "4000000000", // Weight + }, + }; + }, + + [PARACHAINS.SHIDEN]: ({ address, amount, assetSymbol }) => { + const addressIsHex = address.startsWith("0x"); + + const _address = addressIsHex + ? "0x03" + address.slice(2) + "00" + : "0x01" + u8aToHex(decodeAddress(address), undefined, false) + "00"; + + let currency_address = ""; + + switch (assetSymbol?.toLowerCase()) { + case "sdn": { + currency_address = "0x0000000000000000000000000000000000000802"; + break; + } + case "xcsdn": { + currency_address = "0xFFFfffFF0Ca324C842330521525E7De111F38972"; + break; + } + default: + throw new Error("Invalid asset symbol"); + } + + return { + contractAddress: "0x0000000000000000000000000000000000000804", + abi: xTokensAbi as any, + method: "transfer", + extrinsicValues: { + currency_address, //asset address + amount: amount.toString(), + destination: [ + 1, + [ + "0x00" + + "0000" + + numberToHex(POLKADOT_PARACHAINS.ASTAR.id).split("0x")[1], + _address, + ], + ], + weight: "4000000000", // Weight + }, + }; + }, + }, + [PARACHAINS.ASTAR]: { [RELAY_CHAINS.POLKADOT]: ({ address, amount }) => ({ pallet: XCM.pallets.POLKADOT_XCM.NAME, @@ -333,3 +466,22 @@ export const XCM_MAPPING: IXCM_MAPPING = { }, }, }; + +export const XCM_ASSETS_MAPPING = { + [PARACHAINS.MOONBEAM]: { + [RELAY_CHAINS.POLKADOT]: ["xcDOT"], + [PARACHAINS.ASTAR]: ["GLMR", "xcASTR"], + }, + [PARACHAINS.ASTAR]: { + [RELAY_CHAINS.POLKADOT]: ["DOT"], + [PARACHAINS.MOONBEAM]: ["ASTR", "GLMR"], + }, + [PARACHAINS.MOONRIVER]: { + [RELAY_CHAINS.KUSAMA]: ["xcKSM"], + [PARACHAINS.SHIDEN]: ["MOVR", "xcSDN"], + }, + [PARACHAINS.SHIDEN]: { + [RELAY_CHAINS.KUSAMA]: ["KSM"], + [PARACHAINS.MOONRIVER]: ["SDN", "MOVR"], + }, +}; diff --git a/src/pages/balance/Balance.tsx b/src/pages/balance/Balance.tsx index 8496f5b9..93ff19aa 100644 --- a/src/pages/balance/Balance.tsx +++ b/src/pages/balance/Balance.tsx @@ -4,13 +4,6 @@ import { Tab } from "@headlessui/react"; import { useTranslation } from "react-i18next"; import { Activity, Assets, Header, Footer, TotalBalance } from "./components"; import { useLocation } from "react-router-dom"; -import { useNetworkContext } from "@src/providers"; -import { ethers } from "ethers"; -import erc20abi from "@src/constants/erc20.abi.json"; -import Extension from "@src/Extension"; -import xTokensABi from "./xTokenx"; -import { decodeAddress } from "@polkadot/util-crypto"; -import { u8aToHex } from "@polkadot/util"; export interface Asset { name: string; @@ -24,10 +17,6 @@ export const Balance = () => { const { t } = useTranslation("balance"); const { state } = useLocation(); - const { - state: { api }, - } = useNetworkContext(); - const TABS = useMemo(() => { return [ { @@ -41,94 +30,9 @@ export const Balance = () => { ]; }, []); - const test = async () => { - const a = u8aToHex( - decodeAddress("5GWpSdqkkKGZmdKQ9nkSF7TmHp6JWt28BMGQNuG4MXtSvq3e"), - undefined, - false - ); - - console.log("a", a); - - try { - const pk = await Extension.showPrivateKey(); - - const wallet = new ethers.Wallet( - pk as string, - api as ethers.providers.JsonRpcProvider - ); - - const contract = new ethers.Contract( - "0x0000000000000000000000000000000000000804", - xTokensABi, - wallet - ); - - console.log("erc", contract.estimateGas); - - const feeData = await api.getFeeData(); - // const gasLimit = await contract.estimateGas.transferMultiasset( - // [1, []], - // "1000000000000", - // [ - // 1, - // [ - // u8aToHex( - // decodeAddress("5FFKXA5vsYjHSoLsmg1mLJybL7qPXVnVwAuTanSK5FesYrCH") - // ), - // ], - // ], - // "1000000000" - // ); - // console.log("gastLimit", gasLimit.toString()); - - // const _gasLimit = feeData; - const _maxFeePerGas = feeData.maxFeePerGas as ethers.BigNumber; - const _maxPriorityFeePerGas = - feeData.maxPriorityFeePerGas as ethers.BigNumber; - - const avg = _maxFeePerGas.add(_maxPriorityFeePerGas).div(2); - // const estimatedTotal = avg.mul(_gasLimit); - - console.log("fee data", feeData); - - const tx = await contract.transferMultiasset( - [1, []], - "1000000000000", - [ - 1, - [ - "0x01" + - u8aToHex( - decodeAddress( - "5GvJw6rCGjXGoBxSERbGsUCQceaL4bGCq7TPsuNdPckHfYif" - ), - undefined, - false - ) + - "00", - ], - ], - "1000000000", - { - gasLimit: ethers.BigNumber.from("210000"), - maxFeePerGas: _maxFeePerGas, - maxPriorityFeePerGas: _maxPriorityFeePerGas, - type: 2, - } - ); - - await tx.wait(); - console.log(tx); - } catch (error) { - console.log(error); - } - }; - return ( <>
-
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 {