diff --git a/README.md b/README.md
index 15965466..23008ce6 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ The UI currently supports:
- Parachain Id reservation
- Parachain code registration
-### `/paras/renewal`
+### `/renew`
- Core renewal
### `/marketplace`
diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx
index 2f43bbc8..ba07db82 100644
--- a/src/components/Layout/Sidebar/index.tsx
+++ b/src/components/Layout/Sidebar/index.tsx
@@ -110,7 +110,7 @@ export const Sidebar = () => {
parachains: [
{
label: 'Renew',
- route: '/paras/renewal',
+ route: '/renew',
enabled: true,
icon: ,
},
diff --git a/src/components/Paras/ParaDisplay/index.tsx b/src/components/Paras/ParaDisplay/index.tsx
index 77c0e4d2..ea7db8f0 100644
--- a/src/components/Paras/ParaDisplay/index.tsx
+++ b/src/components/Paras/ParaDisplay/index.tsx
@@ -9,8 +9,10 @@ import Unknown from '../../../assets/unknown.svg';
interface ParaDisplayProps {
paraId: number;
network: NetworkType;
+ core?: number;
}
-export const ParaDisplay = ({ paraId, network }: ParaDisplayProps) => {
+
+export const ParaDisplay = ({ paraId, network, core }: ParaDisplayProps) => {
const data = chainData[network][paraId];
if (data === undefined)
@@ -18,6 +20,7 @@ export const ParaDisplay = ({ paraId, network }: ParaDisplayProps) => {
Parachain #{paraId}
+ {core && | Core {core}
}
);
@@ -32,6 +35,7 @@ export const ParaDisplay = ({ paraId, network }: ParaDisplayProps) => {
)}
{name}
#{paraId}
+ {core && | Core {core}
}
);
};
diff --git a/src/components/Renew/action.tsx b/src/components/Renew/action.tsx
new file mode 100644
index 00000000..ad472abe
--- /dev/null
+++ b/src/components/Renew/action.tsx
@@ -0,0 +1,70 @@
+import { Stack } from '@mui/material';
+import { useState } from 'react';
+
+import { RenewableParachain } from '@/hooks';
+import { useSubmitExtrinsic } from '@/hooks/submitExtrinsic';
+
+import { ProgressButton } from '@/components';
+
+import { useAccounts } from '@/contexts/account';
+import { useCoretimeApi } from '@/contexts/apis';
+import { useToast } from '@/contexts/toast';
+
+interface RenewActionProps {
+ parachain: RenewableParachain;
+ enabled: boolean;
+}
+
+export const RenewAction = ({ parachain, enabled }: RenewActionProps) => {
+ const [working, setWorking] = useState(false);
+
+ const {
+ state: { activeAccount, activeSigner },
+ } = useAccounts();
+ const {
+ state: { api: coretimeApi, isApiReady: isCoretimeReady, decimals, symbol },
+ } = useCoretimeApi();
+
+ const { toastError, toastInfo, toastSuccess } = useToast();
+ const { submitExtrinsicWithFeeInfo } = useSubmitExtrinsic();
+
+ const handleRenew = () => {
+ if (!activeAccount || !coretimeApi || !isCoretimeReady || !activeSigner) return;
+
+ const { core } = parachain;
+
+ const txRenewal = coretimeApi.tx.broker.renew(core);
+ submitExtrinsicWithFeeInfo(symbol, decimals, txRenewal, activeAccount.address, activeSigner, {
+ ready: () => {
+ setWorking(true);
+ toastInfo('Transaction was initiated');
+ },
+ inBlock: () => toastInfo('In Block'),
+ finalized: () => setWorking(false),
+ success: () => {
+ toastSuccess('Successfully renewed the selected parachain');
+ },
+ fail: () => {
+ toastError(`Failed to renew the selected parachain`);
+ },
+ error: (e) => {
+ toastError(`Failed to renew the selected parachain ${e}`);
+ setWorking(false);
+ },
+ });
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/src/components/Renew/info.tsx b/src/components/Renew/info.tsx
new file mode 100644
index 00000000..55545200
--- /dev/null
+++ b/src/components/Renew/info.tsx
@@ -0,0 +1,193 @@
+import { Box, Stack, Tooltip, Typography } from '@mui/material';
+import { humanizer } from 'humanize-duration';
+import { Dispatch, SetStateAction, useEffect, useState } from 'react';
+
+import { RenewableParachain } from '@/hooks';
+import { getBalanceString, timesliceToTimestamp } from '@/utils/functions';
+import theme from '@/utils/muiTheme';
+
+import { Banner } from '@/components';
+
+import { useCoretimeApi, useRelayApi } from '@/contexts/apis';
+import { useSaleInfo } from '@/contexts/sales';
+import { ContextStatus } from '@/models';
+
+interface RenewableParaInfoProps {
+ parachain: RenewableParachain;
+ setRenewalEnabled: Dispatch>;
+}
+
+export const RenewableParaInfo = ({ parachain, setRenewalEnabled }: RenewableParaInfoProps) => {
+ const [expiryTimestamp, setExpiryTimestamp] = useState(0);
+
+ const { saleInfo, saleStatus, status: saleInfoStatus, phase } = useSaleInfo();
+
+ const {
+ state: { api: relayApi, isApiReady: isRelayReady },
+ } = useRelayApi();
+ const {
+ state: { api: coretimeApi, isApiReady: isCoretimeReady },
+ timeslicePeriod,
+ } = useCoretimeApi();
+
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const getExpiry = async () => {
+ setLoading(true);
+ if (
+ !coretimeApi ||
+ !isCoretimeReady ||
+ !relayApi ||
+ !isRelayReady ||
+ saleInfoStatus !== ContextStatus.LOADED
+ )
+ return;
+
+ const now = await timesliceToTimestamp(
+ relayApi,
+ saleStatus.lastCommittedTimeslice,
+ timeslicePeriod
+ );
+ const expiry = await timesliceToTimestamp(relayApi, parachain.when, timeslicePeriod);
+
+ if (expiry - now < 0) {
+ setExpiryTimestamp(phase.endpoints.fixed.end - now);
+ } else {
+ setExpiryTimestamp(expiry - now);
+ }
+
+ setLoading(false);
+ };
+
+ getExpiry();
+ }, [
+ parachain,
+ coretimeApi,
+ isCoretimeReady,
+ relayApi,
+ isRelayReady,
+ timeslicePeriod,
+ saleInfoStatus,
+ saleStatus,
+ phase,
+ ]);
+
+ useEffect(() => {
+ // if expiry is before the next region begin it should be possible to renew.
+ setRenewalEnabled(parachain.when <= saleInfo.regionBegin);
+ }, [saleInfo.regionBegin, parachain.when]);
+
+ return (
+ <>
+
+
+ {parachain.when > saleInfo.regionBegin ? (
+
+ ) : (
+ <>
+ {/* If all cores are sold warn the user: */}
+ {saleInfo.coresSold === saleInfo.coresOffered && (
+
+ )}
+ {/* If not all cores are sold inform the user to renew: */}
+ {saleInfo.coresSold < saleInfo.coresOffered && (
+
+ )}
+ >
+ )}
+
+ >
+ );
+};
+
+interface ParachainInfoProps {
+ parachain: RenewableParachain;
+ expiryTimestamp: number;
+ expiryLoading: boolean;
+}
+
+const ParachainInfo = ({ parachain, expiryTimestamp, expiryLoading }: ParachainInfoProps) => {
+ const {
+ state: { decimals, symbol },
+ } = useCoretimeApi();
+
+ const formatDuration = humanizer({ units: ['w', 'd', 'h'], round: true });
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
+
+interface PropertyProps {
+ property: string;
+ value: string;
+ tooltip?: string;
+}
+
+const Property = ({ property, value, tooltip }: PropertyProps) => {
+ return (
+
+
+ {property}
+
+
+ {tooltip && (
+
+
+ ⓘ
+
+
+ )}
+
+ {value}
+
+
+
+ );
+};
diff --git a/src/components/Renew/renewPage.tsx b/src/components/Renew/renewPage.tsx
new file mode 100644
index 00000000..d5023b1f
--- /dev/null
+++ b/src/components/Renew/renewPage.tsx
@@ -0,0 +1,72 @@
+import { Backdrop, Box, CircularProgress, Paper, Typography, useTheme } from '@mui/material';
+import { useState } from 'react';
+
+import { useRenewableParachains } from '@/hooks/renewableParas';
+
+import { Balance } from '@/components';
+
+import { ContextStatus } from '@/models';
+
+import { RenewAction } from './action';
+import { RenewableParaInfo } from './info';
+import { SelectParachain } from './select';
+
+const Renewal = () => {
+ const theme = useTheme();
+
+ const [activeIdx, setActiveIdx] = useState(0);
+ const [renewalEnabled, setRenewalEnabled] = useState(true);
+ const { status, parachains } = useRenewableParachains();
+
+ return status !== ContextStatus.LOADED ? (
+
+
+
+ ) : parachains.length === 0 ? (
+ There are no renewable parachains.
+ ) : (
+ <>
+
+
+
+ Renew a parachain
+
+
+ Renew a parachain
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Renewal;
diff --git a/src/components/Renew/select.tsx b/src/components/Renew/select.tsx
new file mode 100644
index 00000000..d8193527
--- /dev/null
+++ b/src/components/Renew/select.tsx
@@ -0,0 +1,46 @@
+import { FormControl, InputLabel, MenuItem, Select, Stack, Typography } from '@mui/material';
+
+import { RenewableParachain } from '@/hooks';
+import theme from '@/utils/muiTheme';
+
+import { ParaDisplay } from '@/components';
+
+import { useNetwork } from '@/contexts/network';
+
+interface SelectParachainProps {
+ parachains: RenewableParachain[];
+ activeIdx: number;
+ setActiveIdx: (_: number) => void;
+}
+
+export const SelectParachain = ({ parachains, activeIdx, setActiveIdx }: SelectParachainProps) => {
+ const { network } = useNetwork();
+
+ return (
+
+
+ Select a parachain to renew
+
+
+ Parachain
+
+
+
+ );
+};
diff --git a/src/hooks/renewableParas.ts b/src/hooks/renewableParas.ts
index b4ae5b87..71f3958d 100644
--- a/src/hooks/renewableParas.ts
+++ b/src/hooks/renewableParas.ts
@@ -7,11 +7,12 @@ import { ApiState } from '@/contexts/apis/types';
import { useNetwork } from '@/contexts/network';
import { ContextStatus, NetworkType } from '@/models';
-type RenewableParachain = {
+export type RenewableParachain = {
core: number;
paraId: number;
price: number;
mask: string;
+ // The point in time that the renewable workload on `core` ends and a fresh renewal may begin.
when: number;
};
diff --git a/src/pages/paras/index.tsx b/src/pages/paras/index.tsx
index 6c3e8252..22310ca6 100644
--- a/src/pages/paras/index.tsx
+++ b/src/pages/paras/index.tsx
@@ -69,7 +69,7 @@ const ParachainManagement = () => {
// Renew coretime with the given para id
const onRenew = (paraId: number) => {
router.push({
- pathname: 'paras/renewal',
+ pathname: 'renew',
query: { network, paraId },
});
};
diff --git a/src/pages/paras/renewal.tsx b/src/pages/paras/renewal.tsx
deleted file mode 100644
index fa4e48a0..00000000
--- a/src/pages/paras/renewal.tsx
+++ /dev/null
@@ -1,295 +0,0 @@
-import {
- Backdrop,
- Box,
- CircularProgress,
- FormControl,
- InputLabel,
- MenuItem,
- Paper,
- Select,
- Stack,
- Tooltip,
- Typography,
- useTheme,
-} from '@mui/material';
-import { humanizer } from 'humanize-duration';
-import { useRouter } from 'next/router';
-import { useEffect, useState } from 'react';
-
-import { useRenewableParachains } from '@/hooks/renewableParas';
-import { useSubmitExtrinsic } from '@/hooks/submitExtrinsic';
-import { getBalanceString, timesliceToTimestamp } from '@/utils/functions';
-
-import { Balance, Banner, ParaDisplay, ProgressButton } from '@/components';
-
-import { useAccounts } from '@/contexts/account';
-import { useCoretimeApi, useRelayApi } from '@/contexts/apis';
-import { useNetwork } from '@/contexts/network';
-import { useSaleInfo } from '@/contexts/sales';
-import { useToast } from '@/contexts/toast';
-import { ContextStatus } from '@/models';
-
-const Renewal = () => {
- const router = useRouter();
- const theme = useTheme();
-
- const {
- state: { activeAccount, activeSigner },
- } = useAccounts();
- const { status, parachains } = useRenewableParachains();
- const { saleInfo, saleStatus, status: saleInfoStatus, phase } = useSaleInfo();
-
- const {
- state: { api: relayApi, isApiReady: isRelayReady },
- } = useRelayApi();
- const {
- state: { api: coretimeApi, isApiReady: isCoretimeReady, decimals, symbol },
- timeslicePeriod,
- } = useCoretimeApi();
-
- const { toastError, toastInfo, toastSuccess } = useToast();
- const { network } = useNetwork();
- const { submitExtrinsicWithFeeInfo } = useSubmitExtrinsic();
-
- const [loading, setLoading] = useState(false);
- const [activeIdx, setActiveIdx] = useState(0);
- const [working, setWorking] = useState(false);
- const [expiryTimestamp, setExpiryTimestamp] = useState(0);
-
- const formatDuration = humanizer({ units: ['w', 'd', 'h'], round: true });
-
- const handleRenew = () => {
- if (!activeAccount || !coretimeApi || !isCoretimeReady || !activeSigner) return;
-
- const { core } = parachains[activeIdx];
-
- const txRenewal = coretimeApi.tx.broker.renew(core);
- submitExtrinsicWithFeeInfo(symbol, decimals, txRenewal, activeAccount.address, activeSigner, {
- ready: () => {
- setWorking(true);
- toastInfo('Transaction was initiated');
- },
- inBlock: () => toastInfo('In Block'),
- finalized: () => setWorking(false),
- success: () => {
- toastSuccess('Successfully renewed the selected parachain');
- },
- fail: () => {
- toastError(`Failed to renew the selected parachain`);
- },
- error: (e) => {
- toastError(`Failed to renew the selected parachain ${e}`);
- setWorking(false);
- },
- });
- };
-
- useEffect(() => {
- const getExpiry = async () => {
- setLoading(true);
- if (
- !coretimeApi ||
- !isCoretimeReady ||
- !relayApi ||
- !isRelayReady ||
- !parachains[activeIdx] ||
- saleInfoStatus !== ContextStatus.LOADED
- )
- return;
-
- const now = await timesliceToTimestamp(
- relayApi,
- saleStatus.lastCommittedTimeslice,
- timeslicePeriod
- );
- const expiry = await timesliceToTimestamp(
- relayApi,
- parachains[activeIdx].when,
- timeslicePeriod
- );
-
- if (expiry - now < 0) {
- setExpiryTimestamp(phase.endpoints.fixed.end - now);
- } else {
- setExpiryTimestamp(expiry - now);
- }
-
- setLoading(false);
- };
-
- getExpiry();
- }, [
- coretimeApi,
- isCoretimeReady,
- relayApi,
- isRelayReady,
- activeIdx,
- parachains,
- timeslicePeriod,
- saleInfoStatus,
- saleStatus,
- phase,
- ]);
-
- useEffect(() => {
- if (!router.isReady || status !== ContextStatus.LOADED || parachains.length === 0) return;
- const { query } = router;
- if (query['paraId'] === undefined) return;
- const paraId = parseInt(query['paraId'] as string);
- const index = parachains.findIndex((para) => para.paraId == paraId);
- if (index === -1) {
- toastError(`No renewable parachain found with ID = ${paraId}`);
- return;
- }
- setActiveIdx(index);
- }, [router, parachains, status, parachains.length, toastError]);
-
- return status !== ContextStatus.LOADED ? (
-
-
-
- ) : parachains.length === 0 ? (
- There are no renewable parachains.
- ) : (
- <>
-
-
-
- Renew a parachain
-
-
- Renew a parachain
-
-
-
-
-
-
-
-
-
- Select a parachain to renew
-
-
- Parachain
-
-
-
-
-
-
-
-
- {saleInfo.coresSold === saleInfo.coresOffered && (
-
-
-
- )}
- {/* If not all cores are sold inform the user: */}
- {saleInfo.coresSold < saleInfo.coresOffered && (
-
-
-
- )}
-
-
-
-
-
- >
- );
-};
-
-interface PropertyProps {
- property: string;
- value: any;
- tooltip?: string;
-}
-
-export const Property = ({ property, value, tooltip }: PropertyProps) => {
- return (
-
-
- {property}
-
-
- {tooltip && (
-
-
- ⓘ
-
-
- )}
-
- {value}
-
-
-
- );
-};
-
-export default Renewal;
diff --git a/src/pages/renew.tsx b/src/pages/renew.tsx
new file mode 100644
index 00000000..82e9bd19
--- /dev/null
+++ b/src/pages/renew.tsx
@@ -0,0 +1,2 @@
+import RenewPage from '@/components/Renew/renewPage';
+export default RenewPage;