diff --git a/src/assets/networks/coretime.png b/src/assets/networks/coretime.png new file mode 100644 index 00000000..1e51c6f3 Binary files /dev/null and b/src/assets/networks/coretime.png differ diff --git a/src/assets/networks/kusama.png b/src/assets/networks/kusama.png new file mode 100644 index 00000000..b7d5ad82 Binary files /dev/null and b/src/assets/networks/kusama.png differ diff --git a/src/assets/networks/regionx.png b/src/assets/networks/regionx.png new file mode 100644 index 00000000..957626ab Binary files /dev/null and b/src/assets/networks/regionx.png differ diff --git a/src/assets/networks/rococo.png b/src/assets/networks/rococo.png new file mode 100644 index 00000000..a16b5fde Binary files /dev/null and b/src/assets/networks/rococo.png differ diff --git a/src/components/Elements/AmountInput/index.tsx b/src/components/Elements/AmountInput/index.tsx index c24d4ccf..14d7282c 100644 --- a/src/components/Elements/AmountInput/index.tsx +++ b/src/components/Elements/AmountInput/index.tsx @@ -3,8 +3,8 @@ import { Stack, TextField, Typography } from '@mui/material'; interface AmountInputProps { amount: string; currency: string; - title: string; - caption: string; + title?: string; + caption?: string; setAmount: (_: string) => void; } @@ -45,8 +45,8 @@ export const AmountInput = ({ return ( <> - {title} - {caption} + {title && {title}} + {caption && {caption}} { + const theme = useTheme(); + + return ( +
+ + {`Relay chain: ${formatBalance(relayBalance.toString(), false)} ${symbol}`} + + + {`Coretime chain: ${formatBalance(coretimeBalance.toString(), false)} ${symbol}`} + +
+ ); +}; + +export default Balance; diff --git a/src/components/Elements/Selectors/AssetSelector/index.module.scss b/src/components/Elements/Selectors/AssetSelector/index.module.scss new file mode 100644 index 00000000..91eb8bb9 --- /dev/null +++ b/src/components/Elements/Selectors/AssetSelector/index.module.scss @@ -0,0 +1,9 @@ +.options { + display: flex; + justify-content: center; +} + +.option { + min-width: 250px; + margin: 1em 5em; +} \ No newline at end of file diff --git a/src/components/Elements/Selectors/AssetSelector/index.tsx b/src/components/Elements/Selectors/AssetSelector/index.tsx new file mode 100644 index 00000000..9c4d95c0 --- /dev/null +++ b/src/components/Elements/Selectors/AssetSelector/index.tsx @@ -0,0 +1,45 @@ +import { ToggleButton, ToggleButtonGroup, useTheme } from '@mui/material'; +import FormControl from '@mui/material/FormControl'; + +import { AssetType } from '@/models'; + +import styles from './index.module.scss'; + +interface AssetSelectorProps { + asset: AssetType; + setAsset: (_: AssetType) => void; + symbol: string; +} + +export default function AssetSelector({ + asset, + setAsset, + symbol, +}: AssetSelectorProps) { + const theme = useTheme(); + return ( + + setAsset(parseInt(e.target.value) as AssetType)} + className={styles.options} + > + + {symbol} + + + Region + + + + ); +} diff --git a/src/components/Elements/Selectors/ChainSelector/index.tsx b/src/components/Elements/Selectors/ChainSelector/index.tsx index 1787cb4b..7ddcf621 100644 --- a/src/components/Elements/Selectors/ChainSelector/index.tsx +++ b/src/components/Elements/Selectors/ChainSelector/index.tsx @@ -1,11 +1,43 @@ -import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; +import { + Box, + FormControl, + InputLabel, + MenuItem, + Select, + Typography, +} from '@mui/material'; +import Image from 'next/image'; + +import CoretimeIcon from '@/assets/networks/coretime.png'; +import RegionXIcon from '@/assets/networks/regionx.png'; +// import KusamaIcon from '@/assets/networks/kusama.png'; +import RococoIcon from '@/assets/networks/rococo.png'; +import { ChainType } from '@/models'; interface ChainSelectorProps { - chain: string; - setChain: (_: string) => void; + chain: ChainType; + setChain: (_: ChainType) => void; } export const ChainSelector = ({ chain, setChain }: ChainSelectorProps) => { + const menuItems = [ + { + icon: RococoIcon, + label: 'Relay Chain', + value: ChainType.RELAY, + }, + { + icon: CoretimeIcon, + label: 'Coretime Chain', + value: ChainType.CORETIME, + }, + + { + icon: RegionXIcon, + label: 'RegionX Chain', + value: ChainType.REGIONX, + }, + ]; return ( Chain @@ -14,10 +46,22 @@ export const ChainSelector = ({ chain, setChain }: ChainSelectorProps) => { id='origin-selector' value={chain} label='Origin' - onChange={(e) => setChain(e.target.value)} + onChange={(e) => setChain(e.target.value as ChainType)} > - Coretime Chain - RegionX Chain + {menuItems.map(({ icon, label, value }, index) => ( + + + icon + + {label} + + + + ))} ); diff --git a/src/components/Layout/index.module.scss b/src/components/Layout/index.module.scss index 01a5c053..f405f427 100644 --- a/src/components/Layout/index.module.scss +++ b/src/components/Layout/index.module.scss @@ -19,4 +19,9 @@ flex-grow: 1; min-height: calc(100vh - 9rem); max-height: calc(100vh - 9rem); + overflow-y: scroll; } + +.main::-webkit-scrollbar { + display: none; +} \ No newline at end of file diff --git a/src/hooks/balance.tsx b/src/hooks/balance.tsx index 34cc3502..52ff25b3 100644 --- a/src/hooks/balance.tsx +++ b/src/hooks/balance.tsx @@ -1,42 +1,73 @@ +import { ApiPromise } from '@polkadot/api'; import { useInkathon } from '@scio-labs/use-inkathon'; import { useCallback, useEffect, useState } from 'react'; -import { useCoretimeApi } from '@/contexts/apis'; +import { parseHNString } from '@/utils/functions'; + +import { useCoretimeApi, useRelayApi } from '@/contexts/apis'; import { ApiState } from '@/contexts/apis/types'; import { useToast } from '@/contexts/toast'; // React hook for fetching balance. const useBalance = () => { const { - state: { api, apiState, symbol }, + state: { api: coretimeApi, apiState: coretimeApiState, symbol }, } = useCoretimeApi(); + const { + state: { api: relayApi, apiState: relayApiState }, + } = useRelayApi(); + const { activeAccount } = useInkathon(); - const [balance, setBalance] = useState(0); + const [coretimeBalance, setCoretimeBalance] = useState(0); + const [relayBalance, setRelayBalance] = useState(0); const { toastWarning } = useToast(); - const fetchBalance = useCallback(async () => { - if (api && apiState == ApiState.READY && activeAccount) { + const fetchBalance = useCallback( + async (api: ApiPromise): Promise => { + if (!activeAccount) return; + const accountData: any = ( await api.query.system.account(activeAccount.address) ).toHuman(); - const balance = parseFloat(accountData.data.free.toString()); - setBalance(balance); - - if (balance === 0) { - toastWarning( - `The selected account does not have any ${symbol} tokens on the Coretime chain.` - ); - } + const balance = parseHNString(accountData.data.free.toString()); + + return balance; + }, + [activeAccount] + ); + + const fetchBalances = useCallback(() => { + if (coretimeApi && coretimeApiState == ApiState.READY) { + fetchBalance(coretimeApi).then((balance) => { + balance !== undefined && setCoretimeBalance(balance); + balance === 0 && + toastWarning( + `The selected account does not have any ${symbol} tokens on the Coretime chain.` + ); + }); + } + if (relayApi && relayApiState == ApiState.READY) { + fetchBalance(relayApi).then((balance) => { + balance !== undefined && setRelayBalance(balance); + }); } - }, [api, apiState, activeAccount, toastWarning, symbol]); + }, [ + fetchBalance, + toastWarning, + symbol, + coretimeApi, + coretimeApiState, + relayApi, + relayApiState, + ]); useEffect(() => { - fetchBalance(); - }, [fetchBalance]); + fetchBalances(); + }); - return balance; + return { coretimeBalance, relayBalance, fetchBalances }; }; export default useBalance; diff --git a/src/models/types.ts b/src/models/types.ts index e53df5a7..73a11532 100644 --- a/src/models/types.ts +++ b/src/models/types.ts @@ -16,6 +16,26 @@ export type ParaId = number; export type BlockNumber = number; +export enum AssetType { + // eslint-disable-next-line no-unused-vars + NONE = 0, + // eslint-disable-next-line no-unused-vars + TOKEN = 1, + // eslint-disable-next-line no-unused-vars + REGION = 2, +} + +export enum ChainType { + // eslint-disable-next-line no-unused-vars + NONE = 0, + // eslint-disable-next-line no-unused-vars + CORETIME = 1, + // eslint-disable-next-line no-unused-vars + RELAY = 2, + // eslint-disable-next-line no-unused-vars + REGIONX = 3, +} + export type Sender = { address: string; signer: Signer; @@ -27,6 +47,7 @@ export type TxStatusHandlers = { finalized: () => void; success: () => void; error: () => void; + finally?: () => void; }; export enum RegionLocation { diff --git a/src/pages/purchase.tsx b/src/pages/purchase.tsx index 1fafe01d..97b53c05 100644 --- a/src/pages/purchase.tsx +++ b/src/pages/purchase.tsx @@ -8,9 +8,10 @@ import { useState } from 'react'; import useBalance from '@/hooks/balance'; import useSalePhase from '@/hooks/salePhase'; import useSalePrice from '@/hooks/salePrice'; -import { formatBalance } from '@/utils/functions'; +import { sendTx } from '@/utils/functions'; import { CoreDetailsPanel, ProgressButton, SaleInfoPanel } from '@/components'; +import Balance from '@/components/Elements/Balance'; import { useCoretimeApi } from '@/contexts/apis'; import { ApiState } from '@/contexts/apis/types'; @@ -35,7 +36,7 @@ const Purchase = () => { const { fetchRegions } = useRegions(); - const balance = useBalance(); + const { coretimeBalance, relayBalance } = useBalance(); const currentPrice = useSalePrice(); const { currentPhase, progress, saleStartTimestamp, saleEndTimestamp } = useSalePhase(); @@ -46,31 +47,18 @@ const Purchase = () => { const txPurchase = api.tx.broker.purchase(currentPrice); - try { - setWorking(true); - await txPurchase.signAndSend( - activeAccount.address, - { signer: activeSigner }, - ({ status, events }) => { - if (status.isReady) toastInfo('Transaction was initiated'); - else if (status.isInBlock) toastInfo(`In Block`); - else if (status.isFinalized) { - setWorking(false); - events.forEach(({ event: { method } }) => { - if (method === 'ExtrinsicSuccess') { - toastSuccess('Transaction successful'); - fetchRegions(); - } else if (method === 'ExtrinsicFailed') { - toastError(`Failed to purchase the region`); - } - }); - } - } - ); - } catch (e) { - toastError(`Failed to purchase the region. ${e}`); - setWorking(false); - } + sendTx(txPurchase, activeAccount.address, activeSigner, { + ready: () => toastInfo('Transaction was initiated'), + inBlock: () => toastInfo(`In Block`), + finalized: () => setWorking(false), + success: () => { + toastSuccess('Transaction successful'); + fetchRegions(); + }, + error: () => { + toastError(`Failed to purchase the region`); + }, + }); }; return ( @@ -96,12 +84,11 @@ const Purchase = () => { Buy a core straight from the Coretime chain - - {`Your balance: ${formatBalance( - balance.toString(), - false - )} ${symbol}`} - + {loading || diff --git a/src/pages/transfer.tsx b/src/pages/transfer.tsx index 9361f42f..85d40431 100644 --- a/src/pages/transfer.tsx +++ b/src/pages/transfer.tsx @@ -9,32 +9,54 @@ import { Stack, Typography, } from '@mui/material'; +import { Keyring } from '@polkadot/api'; import { useInkathon } from '@scio-labs/use-inkathon'; import { Region } from 'coretime-utils'; import { useEffect, useState } from 'react'; +import useBalance from '@/hooks/balance'; +import { + transferTokensFromCoretimeToRelay, + transferTokensFromRelayToCoretime, +} from '@/utils/crossChain/transfer'; import theme from '@/utils/muiTheme'; -import { transferRegionOnCoretimeChain } from '@/utils/native/transfer'; +import { + transferNativeToken, + transferRegionOnCoretimeChain, +} from '@/utils/native/transfer'; import { + AmountInput, ChainSelector, RecipientSelector, RegionCard, RegionSelector, } from '@/components'; +import Balance from '@/components/Elements/Balance'; +import AssetSelector from '@/components/Elements/Selectors/AssetSelector'; -import { useCoretimeApi } from '@/contexts/apis'; +import { useCoretimeApi, useRelayApi } from '@/contexts/apis'; +import { ApiState } from '@/contexts/apis/types'; import { useRegions } from '@/contexts/regions'; import { useToast } from '@/contexts/toast'; -import { RegionLocation, RegionMetadata } from '@/models'; +import { + AssetType, + ChainType, + CORETIME_DECIMALS, + RegionLocation, + RegionMetadata, +} from '@/models'; const TransferPage = () => { const { activeAccount, activeSigner } = useInkathon(); const { toastError, toastInfo, toastWarning, toastSuccess } = useToast(); const { - state: { api: coretimeApi }, + state: { api: coretimeApi, apiState: coretimeApiState, symbol }, } = useCoretimeApi(); + const { + state: { api: relayApi, apiState: relayApiState }, + } = useRelayApi(); const { regions } = useRegions(); const [filteredRegions, setFilteredRegions] = useState>( @@ -43,42 +65,123 @@ const TransferPage = () => { const [working, setWorking] = useState(false); const [newOwner, setNewOwner] = useState(''); - const [originChain, setOriginChain] = useState(''); - const [destinationChain, setDestinationChain] = useState(''); + const [originChain, setOriginChain] = useState(ChainType.NONE); + const [destinationChain, setDestinationChain] = useState( + ChainType.NONE + ); const [statusLabel, _setStatusLabel] = useState(''); const [selectedRegion, setSelectedRegion] = useState( null ); + const [asset, setAsset] = useState(AssetType.TOKEN); + const [transferAmount, setTransferAmount] = useState(''); + + const { coretimeBalance, relayBalance, fetchBalances } = useBalance(); + + const defaultHandler = { + ready: () => toastInfo('Transaction was initiated.'), + inBlock: () => toastInfo(`In Block`), + finalized: () => setWorking(false), + success: () => { + fetchBalances(); + toastSuccess('Successfully transferred.'); + }, + error: () => { + toastError(`Failed to transfer.`); + setWorking(false); + }, + }; + useEffect(() => { setFilteredRegions( regions.filter((r) => r.location != RegionLocation.MARKET) ); }, [regions]); - const handleOriginChange = (newOrigin: string) => { + const handleOriginChange = (newOrigin: ChainType) => { setOriginChain(newOrigin); - if (newOrigin === 'CoretimeChain') { - setFilteredRegions( - regions.filter((r) => r.location == RegionLocation.CORETIME_CHAIN) + setFilteredRegions( + regions.filter( + (r) => + r.location === + (newOrigin === ChainType.CORETIME + ? RegionLocation.CORETIME_CHAIN + : RegionLocation.REGIONX_CHAIN) + ) + ); + if (newOrigin === ChainType.RELAY) setAsset(AssetType.TOKEN); + }; + + const handleTransfer = async () => { + if (!activeAccount || !activeSigner) { + toastWarning('Connect wallet first'); + return; + } + if (asset === AssetType.REGION) { + handleRegionTransfer(); + } else if (asset === AssetType.TOKEN) { + handleTokenTransfer(); + } + }; + + const handleTokenTransfer = async () => { + if (!activeAccount || !activeSigner) return; + if (!originChain || !destinationChain) return; + + if (!coretimeApi || coretimeApiState !== ApiState.READY) { + toastError('Not connected to the Coretime chain'); + return; + } + if (!relayApi || relayApiState !== ApiState.READY) { + toastError('Not connected to the relay chain'); + return; + } + + const amount = Number(transferAmount) * Math.pow(10, CORETIME_DECIMALS); + if (originChain === destinationChain) { + if (!newOwner) { + toastError('Recipient must be selected'); + return; + } + transferNativeToken( + originChain === ChainType.CORETIME ? coretimeApi : relayApi, + activeSigner, + activeAccount.address, + newOwner, + amount.toString(), + defaultHandler ); } else { - setFilteredRegions( - regions.filter((r) => r.location == RegionLocation.REGIONX_CHAIN) + const receiverKeypair = new Keyring(); + receiverKeypair.addFromAddress( + newOwner ? newOwner : activeAccount.address + ); + + (originChain === ChainType.CORETIME + ? transferTokensFromCoretimeToRelay + : transferTokensFromRelayToCoretime + ).call( + this, + originChain === ChainType.CORETIME ? coretimeApi : relayApi, + { address: activeAccount.address, signer: activeSigner }, + amount.toString(), + receiverKeypair.pairs[0].publicKey, + defaultHandler ); } }; - const handleTransfer = async () => { + const handleRegionTransfer = async () => { if (!selectedRegion) { toastError('Select a region'); return; } if (originChain === destinationChain) { - originChain === 'CoretimeChain' - ? transferCoretimeRegion(selectedRegion.region) + originChain === ChainType.CORETIME + ? await transferCoretimeRegion(selectedRegion.region) : toastWarning('Currently not supported'); } else { toastWarning('Currently not supported'); @@ -98,37 +201,47 @@ const TransferPage = () => { region, activeSigner, activeAccount.address, - newOwner ? newOwner : activeAccount.address, - { - ready: () => toastInfo('Transaction was initiated.'), - inBlock: () => toastInfo(`In Block`), - finalized: () => setWorking(false), - success: () => toastSuccess('Successfully transferred the region.'), - error: () => { - toastError(`Failed to transfer the region.`); - setWorking(false); - }, - } + newOwner ?? activeAccount.address, + defaultHandler ); }; return ( - - - Cross-Chain Transfer - - - Cross-chain transfer regions - + + + + Cross-Chain Transfer + + + Cross-chain transfer regions + + + - + Origin chain: @@ -140,21 +253,36 @@ const TransferPage = () => { setChain={setDestinationChain} /> - {originChain && ( - - Region - setSelectedRegion(regions[indx])} - /> - - )} + {originChain !== ChainType.NONE && + destinationChain !== ChainType.NONE && ( + + + + )} + + {asset === AssetType.REGION && + originChain !== ChainType.NONE && + destinationChain !== ChainType.NONE && ( + + Region + setSelectedRegion(regions[indx])} + /> + + )} {selectedRegion && ( @@ -168,12 +296,24 @@ const TransferPage = () => { Transfer to: + {asset === AssetType.TOKEN && + originChain !== ChainType.NONE && + destinationChain !== ChainType.NONE && ( + + + + )} {statusLabel && ( {statusLabel} )} - + diff --git a/src/utils/crossChain/consts.tsx b/src/utils/crossChain/consts.tsx index 5f508a6a..4a79d5ad 100644 --- a/src/utils/crossChain/consts.tsx +++ b/src/utils/crossChain/consts.tsx @@ -9,6 +9,15 @@ export const CoretimeChain = { }, }; +export const CoretimeChainFromRelayPerspective = { + parents: 0, + interior: { + X1: { + Parachain: CORETIME_PARA_ID, + }, + }, +}; + export const RegionXChain = { parents: 1, interior: { @@ -33,3 +42,18 @@ export const CoretimeRegionFromRegionXPerspective = { X2: [{ Parachain: CORETIME_PARA_ID }, { PalletInstance: BROKER_PALLET_ID }], }, }; + +export const RelayChainFromParachainPerspective = { + parents: 1, + interior: 'Here', +}; + +export const RcTokenFromParachainPerspective = { + parents: 1, + interior: 'Here', +}; + +export const RcTokenFromRelayPerspective = { + parents: 0, + interior: 'Here', +}; diff --git a/src/utils/crossChain/transfer.tsx b/src/utils/crossChain/transfer.tsx index d402be3c..670e9613 100644 --- a/src/utils/crossChain/transfer.tsx +++ b/src/utils/crossChain/transfer.tsx @@ -5,11 +5,20 @@ import { Sender, TxStatusHandlers } from '@/models'; import { CoretimeChain, + CoretimeChainFromRelayPerspective, CoretimeRegionFromCoretimePerspective, CoretimeRegionFromRegionXPerspective, + RcTokenFromParachainPerspective, + RcTokenFromRelayPerspective, RegionXChain, + RelayChainFromParachainPerspective, } from './consts'; -import { versionedNonfungibleAssetWrap, versionedWrap } from './utils'; +import { + versionWrap, + versionWrappeddFungibleAsset, + versionWrappeddNonfungibleAsset, +} from './utils'; +import { sendTx } from '../functions'; export async function coretimeToRegionXTransfer( coretimeApi: ApiPromise, @@ -35,9 +44,9 @@ export async function coretimeToRegionXTransfer( const reserveTransfer = coretimeApi.tx.polkadotXcm.limitedReserveTransferAssets( - versionedWrap(RegionXChain), - versionedWrap(beneficiary), - versionedNonfungibleAssetWrap( + versionWrap(RegionXChain), + versionWrap(beneficiary), + versionWrappeddNonfungibleAsset( CoretimeRegionFromCoretimePerspective, rawRegionId.toString() ), @@ -45,28 +54,8 @@ export async function coretimeToRegionXTransfer( weightLimit ); - try { - reserveTransfer.signAndSend( - sender.address, - { signer: sender.signer }, - ({ status, events }) => { - if (status.isReady) handlers.ready(); - else if (status.isInBlock) handlers.inBlock(); - else if (status.isFinalized) { - handlers.finalized(); - events.forEach(({ event: { method } }) => { - if (method === 'ExtrinsicSuccess') { - handlers.success(); - } else if (method === 'ExtrinsicFailed') { - handlers.error(); - } - }); - } - } - ); - } catch (e) { - handlers.error(); - } + const { address, signer } = sender; + sendTx(reserveTransfer, address, signer, handlers); } export function regionXToCoretimeTransfer( @@ -92,9 +81,9 @@ export function regionXToCoretimeTransfer( const weightLimit = 'Unlimited'; const reserveTransfer = api.tx.polkadotXcm.limitedReserveTransferAssets( - versionedWrap(CoretimeChain), - versionedWrap(beneficiary), - versionedNonfungibleAssetWrap( + versionWrap(CoretimeChain), + versionWrap(beneficiary), + versionWrappeddNonfungibleAsset( CoretimeRegionFromRegionXPerspective, rawRegionId.toString() ), @@ -102,26 +91,76 @@ export function regionXToCoretimeTransfer( weightLimit ); - try { - reserveTransfer.signAndSend( - sender.address, - { signer: sender.signer }, - ({ status, events }) => { - if (status.isReady) handlers.ready(); - else if (status.isInBlock) handlers.inBlock(); - else if (status.isFinalized) { - handlers.finalized(); - events.forEach(({ event: { method } }) => { - if (method === 'ExtrinsicSuccess') { - handlers.success(); - } else if (method === 'ExtrinsicFailed') { - handlers.error(); - } - }); - } - } - ); - } catch (e) { - handlers.error(); - } + const { address, signer } = sender; + sendTx(reserveTransfer, address, signer, handlers); +} + +export function transferTokensFromCoretimeToRelay( + coretimeApi: ApiPromise, + sender: Sender, + amount: string, + receiver: Uint8Array, + handlers: TxStatusHandlers +) { + const beneficiary = { + parents: 0, + interior: { + X1: { + AccountId32: { + chain: 'Any', + id: receiver, + }, + }, + }, + }; + + const feeAssetItem = 0; + const weightLimit = 'Unlimited'; + + const teleportTransfer = coretimeApi.tx.polkadotXcm.limitedTeleportAssets( + versionWrap(RelayChainFromParachainPerspective), + versionWrap(beneficiary), + versionWrappeddFungibleAsset(RcTokenFromParachainPerspective, amount), + feeAssetItem, + weightLimit + ); + + const { address, signer } = sender; + + sendTx(teleportTransfer, address, signer, handlers); +} + +export function transferTokensFromRelayToCoretime( + coretimeApi: ApiPromise, + sender: Sender, + amount: string, + receiver: Uint8Array, + handlers: TxStatusHandlers +) { + const beneficiary = { + parents: 0, + interior: { + X1: { + AccountId32: { + chain: 'Any', + id: receiver, + }, + }, + }, + }; + + const feeAssetItem = 0; + const weightLimit = 'Unlimited'; + + const teleportTransfer = coretimeApi.tx.xcmPallet.limitedTeleportAssets( + versionWrap(CoretimeChainFromRelayPerspective), + versionWrap(beneficiary), + versionWrappeddFungibleAsset(RcTokenFromRelayPerspective, amount), + feeAssetItem, + weightLimit + ); + + const { address, signer } = sender; + + sendTx(teleportTransfer, address, signer, handlers); } diff --git a/src/utils/crossChain/utils.tsx b/src/utils/crossChain/utils.tsx index e8dd2099..dbc76f28 100644 --- a/src/utils/crossChain/utils.tsx +++ b/src/utils/crossChain/utils.tsx @@ -1,16 +1,16 @@ import { SAFE_XCM_VERSION } from '@/models'; -export const versionedWrap = (xcm: any) => { +export const versionWrap = (xcm: any) => { return { [`V${SAFE_XCM_VERSION}`]: xcm, }; }; -export const versionedNonfungibleAssetWrap = ( +export const versionWrappeddNonfungibleAsset = ( assetLocation: any, index: string ) => { - return versionedWrap([ + return versionWrap([ { id: { Concrete: assetLocation, @@ -23,3 +23,19 @@ export const versionedNonfungibleAssetWrap = ( }, ]); }; + +export const versionWrappeddFungibleAsset = ( + assetLocation: any, + amount: string +) => { + return versionWrap([ + { + id: { + Concrete: assetLocation, + }, + fun: { + Fungible: amount, + }, + }, + ]); +}; diff --git a/src/utils/functions.ts b/src/utils/functions.ts index 86cff9d8..63f0167d 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -1,9 +1,15 @@ import { ApiPromise } from '@polkadot/api'; +import { AddressOrPair, SubmittableExtrinsic } from '@polkadot/api/types'; +import { ISubmittableResult, Signer } from '@polkadot/types/types'; import { formatBalance as polkadotFormatBalance } from '@polkadot/util'; import { CoreMask, RegionId } from 'coretime-utils'; import Decimal from 'decimal.js'; -import { CORETIME_DECIMALS, REGIONX_DECIMALS } from '@/models'; +import { + CORETIME_DECIMALS, + REGIONX_DECIMALS, + TxStatusHandlers, +} from '@/models'; // parse human readable number string export const parseHNString = (str: string): number => { @@ -110,6 +116,45 @@ export const extractRegionIdFromRaw = (rawRegionId: bigint): RegionId => { }; }; +export const fetchBalance = async ( + api: ApiPromise, + address: string +): Promise => { + const coretimeAccount = ( + await api.query.system.account(address) + ).toHuman() as any; + + return parseHNString(coretimeAccount.data.free.toString()); +}; + +export const sendTx = ( + tx: SubmittableExtrinsic<'promise', ISubmittableResult>, + account: AddressOrPair, + signer: Signer, + handlers: TxStatusHandlers +) => { + try { + tx.signAndSend(account, { signer }, ({ status, events }) => { + if (status.isReady) handlers.ready(); + else if (status.isInBlock) handlers.inBlock(); + else if (status.isFinalized) { + handlers.finalized(); + events.forEach(({ event: { method } }) => { + if (method === 'ExtrinsicSuccess') { + handlers.success(); + } else if (method === 'ExtrinsicFailed') { + handlers.error(); + } + }); + } + }); + } catch { + handlers.error(); + } finally { + handlers.finally && handlers.finally(); + } +}; + export const getBlockTime = (network: any): number => { // Coretime on Rococo has async backing and due to this it has a block time of 6 seconds. const blockTime = !network || network == 'rococo' ? 6000 : 12000; diff --git a/src/utils/native/transfer.tsx b/src/utils/native/transfer.tsx index 852cc3e3..8bdf6b33 100644 --- a/src/utils/native/transfer.tsx +++ b/src/utils/native/transfer.tsx @@ -4,6 +4,20 @@ import { Region } from 'coretime-utils'; import { TxStatusHandlers } from '@/models'; +import { sendTx } from '../functions'; + +export const transferNativeToken = async ( + api: ApiPromise, + signer: Signer, + senderAddress: string, + destination: string, + amount: string, + handlers: TxStatusHandlers +) => { + const txTransfer = api.tx.balances.transferKeepAlive(destination, amount); + sendTx(txTransfer, senderAddress, signer, handlers); +}; + export const transferRegionOnCoretimeChain = async ( coretimeApi: ApiPromise, region: Region, @@ -16,27 +30,5 @@ export const transferRegionOnCoretimeChain = async ( region.getOnChainRegionId(), newOwner ); - - try { - await txTransfer.signAndSend( - senderAddress, - { signer }, - ({ status, events }) => { - if (status.isReady) handlers.ready(); - else if (status.isInBlock) handlers.inBlock(); - else if (status.isFinalized) { - handlers.finalized(); - events.forEach(({ event: { method } }) => { - if (method === 'ExtrinsicSuccess') { - handlers.success(); - } else if (method === 'ExtrinsicFailed') { - handlers.error(); - } - }); - } - } - ); - } catch (e) { - handlers.error(); - } + sendTx(txTransfer, senderAddress, signer, handlers); };