From 184e7eb8834f5637bbe6f578e360b7810b759a60 Mon Sep 17 00:00:00 2001 From: cuteolaf Date: Fri, 26 Apr 2024 10:05:25 -0700 Subject: [PATCH] Page: Renewal (#86) * renew parachains * Update src/hooks/useRenewableParas.ts Co-authored-by: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> * network names * small fixes * lint error fix --------- Co-authored-by: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Co-authored-by: Sergej --- src/chaindata/index.ts | 25 +++ src/chaindata/kusama.json | 266 +++++++++++++++++++++++++++++++ src/chaindata/rococo.json | 262 ++++++++++++++++++++++++++++++ src/components/Elements/index.ts | 1 + src/components/Sidebar/index.tsx | 9 ++ src/hooks/useRenewableParas.ts | 76 +++++++++ src/icons/Renew.tsx | 20 +++ src/icons/index.ts | 1 + src/pages/renewal.tsx | 164 +++++++++++++++++++ 9 files changed, 824 insertions(+) create mode 100644 src/chaindata/index.ts create mode 100644 src/chaindata/kusama.json create mode 100644 src/chaindata/rococo.json create mode 100644 src/hooks/useRenewableParas.ts create mode 100644 src/icons/Renew.tsx create mode 100644 src/pages/renewal.tsx diff --git a/src/chaindata/index.ts b/src/chaindata/index.ts new file mode 100644 index 00000000..29a570bf --- /dev/null +++ b/src/chaindata/index.ts @@ -0,0 +1,25 @@ +import { NetworkType } from '@/models'; + +import KusamaChains from './kusama.json'; +import RococoChains from './rococo.json'; + +type ParachainRecord = { + name: string; + paraId: number; +}; + +const transformData = (data: ParachainRecord[]) => { + const mapping: Record = {}; + data.forEach(({ paraId, name }) => { + mapping[paraId] = name; + }); + return mapping; +}; + +const parachains = { + [NetworkType.NONE]: {} as Record, + [NetworkType.KUSAMA]: transformData(KusamaChains), + [NetworkType.ROCOCO]: transformData(RococoChains), +}; + +export default parachains; diff --git a/src/chaindata/kusama.json b/src/chaindata/kusama.json new file mode 100644 index 00000000..f46e6cc7 --- /dev/null +++ b/src/chaindata/kusama.json @@ -0,0 +1,266 @@ +[ + { + "name": "Aband", + "paraId": 2257 + }, + { + "name": "Acurast Canary", + "paraId": 2239 + }, + { + "name": "Altair", + "paraId": 2088 + }, + { + "name": "Amplitude", + "paraId": 2124 + }, + { + "name": "Bajun", + "paraId": 2119 + }, + { + "name": "Basilisk", + "paraId": 2090 + }, + { + "name": "Bifrost Kusama", + "paraId": 2001 + }, + { + "name": "Pioneer", + "paraId": 2096 + }, + { + "name": "Calamari", + "paraId": 2084 + }, + { + "name": "Darwinia Crab", + "paraId": 2105 + }, + { + "name": "Curio", + "paraId": 3339 + }, + { + "name": "Dora Factory", + "paraId": 2115 + }, + { + "name": "Encointer", + "paraId": 1001 + }, + { + "name": "Genshiro", + "paraId": 2024 + }, + { + "name": "Genshiro", + "paraId": 2226 + }, + { + "name": "GM", + "paraId": 2123 + }, + { + "name": "Parallel Heiko", + "paraId": 2085 + }, + { + "name": "Parallel Heiko", + "paraId": 2126 + }, + { + "name": "Hyperbridge", + "paraId": 3340 + }, + { + "name": "Imbue", + "paraId": 2121 + }, + { + "name": "Integritee", + "paraId": 2015 + }, + { + "name": "DAO IPCI", + "paraId": 2222 + }, + { + "name": "K-Laos", + "paraId": 3336 + }, + { + "name": "Kabocha", + "paraId": 2113 + }, + { + "name": "Karura", + "paraId": 2000 + }, + { + "name": "Khala", + "paraId": 2004 + }, + { + "name": "Kico", + "paraId": 2107 + }, + { + "name": "Kico", + "paraId": 2235 + }, + { + "name": "Kintsugi", + "paraId": 2092 + }, + { + "name": "Kpron", + "paraId": 2019 + }, + { + "name": "Kreivo", + "paraId": 2281 + }, + { + "name": "Krest", + "paraId": 2241 + }, + { + "name": "Kusama Asset Hub", + "paraId": 1000 + }, + { + "name": "Kusama Bridge Hub", + "paraId": 1002 + }, + { + "name": "Listen", + "paraId": 2118 + }, + { + "name": "Litmus", + "paraId": 2106 + }, + { + "name": "Loom", + "paraId": 2080 + }, + { + "name": "MangataX", + "paraId": 2110 + }, + { + "name": "Mars", + "paraId": 2008 + }, + { + "name": "Moonriver", + "paraId": 2023 + }, + { + "name": "Picasso", + "paraId": 2087 + }, + { + "name": "Pichiu", + "paraId": 2102 + }, + { + "name": "PolkaSmith", + "paraId": 2009 + }, + { + "name": "Quantum Portal", + "paraId": 2274 + }, + { + "name": "Quartz", + "paraId": 2095 + }, + { + "name": "RioDeFi", + "paraId": 2227 + }, + { + "name": "Robonomics", + "paraId": 2048 + }, + { + "name": "Robonomics", + "paraId": 2240 + }, + { + "name": "Sakura", + "paraId": 2016 + }, + { + "name": "Crust Shadow", + "paraId": 2012 + }, + { + "name": "Crust Shadow", + "paraId": 2225 + }, + { + "name": "Shiden", + "paraId": 2007 + }, + { + "name": "Shiden", + "paraId": 2120 + }, + { + "name": "Snow", + "paraId": 2129 + }, + { + "name": "Sora", + "paraId": 2011 + }, + { + "name": "SubGame Gamma Kusama", + "paraId": 2018 + }, + { + "name": "SubsocialX", + "paraId": 2100 + }, + { + "name": "t1rn", + "paraId": 3334 + }, + { + "name": "Tanganika", + "paraId": 2116 + }, + { + "name": "InvArch Tinkernet", + "paraId": 2125 + }, + { + "name": "TrustBase", + "paraId": 2078 + }, + { + "name": "Turing", + "paraId": 2114 + }, + { + "name": "Unorthodox", + "paraId": 2094 + }, + { + "name": "Xode", + "paraId": 3344 + }, + { + "name": "Yerba", + "paraId": 3345 + }, + { + "name": "Subzero", + "paraId": 2236 + } +] diff --git a/src/chaindata/rococo.json b/src/chaindata/rococo.json new file mode 100644 index 00000000..c6ac1e9b --- /dev/null +++ b/src/chaindata/rococo.json @@ -0,0 +1,262 @@ +[ + { + "name": "Arctic", + "paraId": 3015 + }, + { + "name": "Oli", + "paraId": 4023 + }, + { + "name": "Encointer Lietaer", + "paraId": 1003 + }, + { + "name": "Giant", + "paraId": 4227 + }, + { + "name": "Helixstreet", + "paraId": 3025 + }, + { + "name": "Robonomics Testnet", + "paraId": 2048 + }, + { + "name": "Rocfinity", + "paraId": 2021 + }, + { + "name": "Acurast", + "paraId": 2239 + }, + { + "name": "Amplitude Testnet", + "paraId": 2124 + }, + { + "name": "Rococo Asset Hub", + "paraId": 1000 + }, + { + "name": "Aventus Testnet", + "paraId": 2056 + }, + { + "name": "Bajun Testnet", + "paraId": 2119 + }, + { + "name": "Basilisk Testnet", + "paraId": 2090 + }, + { + "name": "Bifrost Testnet", + "paraId": 2030 + }, + { + "name": "Bitgreen Testnet", + "paraId": 20048 + }, + { + "name": "Rococo Bridge Hub", + "paraId": 1013 + }, + { + "name": "Catalyst", + "paraId": 2031 + }, + { + "name": "Confti", + "paraId": 4094 + }, + { + "name": "Contracts", + "paraId": 1002 + }, + { + "name": "Coretime", + "paraId": 1005 + }, + { + "name": "Crust Testnet", + "paraId": 2012 + }, + { + "name": "Curio Testnet", + "paraId": 3339 + }, + { + "name": "Dolphin", + "paraId": 2084 + }, + { + "name": "Ethos", + "paraId": 2095 + }, + { + "name": "Rex", + "paraId": 3345 + }, + { + "name": "Frequency Testnet", + "paraId": 4044 + }, + { + "name": "Genshiro Testnet", + "paraId": 2024 + }, + { + "name": "HydraDX Testnet", + "paraId": 2034 + }, + { + "name": "Hyperbridge Testnet", + "paraId": 4374 + }, + { + "name": "Idiyanale", + "paraId": 4222 + }, + { + "name": "Imbue Testnet", + "paraId": 2121 + }, + { + "name": "Integritee Testnet", + "paraId": 3002 + }, + { + "name": "Invo", + "paraId": 4377 + }, + { + "name": "Kabocha Testnet", + "paraId": 2113 + }, + { + "name": "Kaizen", + "paraId": 2040 + }, + { + "name": "Rilt", + "paraId": 2086 + }, + { + "name": "Litentry Testnet", + "paraId": 2106 + }, + { + "name": "MangataX Testnet", + "paraId": 2110 + }, + { + "name": "MD5", + "paraId": 2093 + }, + { + "name": "Moonsama Testnet", + "paraId": 2055 + }, + { + "name": "NeuroWeb Testnet", + "paraId": 2043 + }, + { + "name": "Nodle Testnet", + "paraId": 2026 + }, + { + "name": "OpenZeppelin Runtime Template", + "paraId": 4354 + }, + { + "name": "Pangolin2", + "paraId": 2105 + }, + { + "name": "People", + "paraId": 1004 + }, + { + "name": "Rhala", + "paraId": 2004 + }, + { + "name": "Picasso Testnet", + "paraId": 2087 + }, + { + "name": "Societal", + "paraId": 4253 + }, + { + "name": "Sora Testnet", + "paraId": 2011 + }, + { + "name": "Spreehafen", + "paraId": 2116 + }, + { + "name": "SoonsocialX", + "paraId": 2100 + }, + { + "name": "Subzero Testnet", + "paraId": 4040 + }, + { + "name": "Tinkernet", + "paraId": 2125 + }, + { + "name": "Trappist", + "paraId": 1836 + }, + { + "name": "Turing Testnet", + "paraId": 2114 + }, + { + "name": "Unit", + "paraId": 4168 + }, + { + "name": "Virto", + "paraId": 3003 + }, + { + "name": "Watr Testnet", + "paraId": 2058 + }, + { + "name": "Yerba Testnet", + "paraId": 4292 + }, + { + "name": "Zeitgeist Battery Station", + "paraId": 2101 + }, + { + "name": "Giedi", + "paraId": 4343 + }, + { + "name": "Snowbridge Asset Hub", + "paraId": 3416 + }, + { + "name": "Snowbridge Bridge Hub", + "paraId": 3016 + }, + { + "name": "Stagex", + "paraId": 2007 + }, + { + "name": "t0rn", + "paraId": 3333 + } +] diff --git a/src/components/Elements/index.ts b/src/components/Elements/index.ts index 0456f2fd..47ea6d01 100644 --- a/src/components/Elements/index.ts +++ b/src/components/Elements/index.ts @@ -1,4 +1,5 @@ export * from './AmountInput'; +export * from './Balance'; export * from './Banner'; export * from './Buttons'; export * from './CoreDetailsPanel'; diff --git a/src/components/Sidebar/index.tsx b/src/components/Sidebar/index.tsx index 770e3685..786567ae 100644 --- a/src/components/Sidebar/index.tsx +++ b/src/components/Sidebar/index.tsx @@ -11,6 +11,7 @@ import React from 'react'; import Logo from '@/assets/logo.png'; import { useCoretimeApi, useRelayApi } from '@/contexts/apis'; +import { RenewIcon } from '@/icons'; import styles from './index.module.scss'; import { StatusIndicator } from '../Elements'; @@ -98,6 +99,14 @@ export const Sidebar = () => { icon: , }, ], + 'parachain management': [ + { + label: 'Renew', + route: '/renewal', + enabled: true, + icon: , + }, + ], 'primary market': [ { label: 'Purchase a core', diff --git a/src/hooks/useRenewableParas.ts b/src/hooks/useRenewableParas.ts new file mode 100644 index 00000000..361a9e1e --- /dev/null +++ b/src/hooks/useRenewableParas.ts @@ -0,0 +1,76 @@ +import { CoreMask } from 'coretime-utils'; +import { useEffect, useState } from 'react'; + +import { parseHNString } from '@/utils/functions'; + +import { useCoretimeApi } from '@/contexts/apis'; +import { ApiState } from '@/contexts/apis/types'; + +type RenewableParachain = { + core: number; + paraID: number; + price: number; + mask: CoreMask; + when: number; +}; + +export const useRenewableParachains = () => { + const { + state: { api, apiState }, + } = useCoretimeApi(); + + const [loading, setLoading] = useState(false); + const [parachains, setParachains] = useState([]); + + useEffect(() => { + if (apiState !== ApiState.READY) { + setLoading(false); + setParachains([]); + } + + const asyncFetchParaIds = async () => { + if (!api || apiState !== ApiState.READY) return; + + setLoading(true); + + const renewals = await api.query.broker.allowedRenewals.entries(); + for (const [key, value] of renewals) { + const data: any = key.toHuman(); + const core = parseHNString(data[0].core); + const when = parseHNString(data[0].when); + + const record: any = value.toHuman(); + const price = parseHNString(record.price); + const { + completion: { Complete }, + } = record; + if (Complete === undefined) continue; + if (Complete.length !== 1) continue; + const [ + { + mask, + assignment: { Task }, + }, + ] = Complete; + + if (Task === undefined) continue; + + parachains.push({ + core, + price, + mask: new CoreMask(mask), + paraID: parseHNString(Task), + when, + }); + } + + setParachains(parachains); + + setLoading(false); + }; + + asyncFetchParaIds(); + }, [api, apiState]); + + return { loading, parachains }; +}; diff --git a/src/icons/Renew.tsx b/src/icons/Renew.tsx new file mode 100644 index 00000000..66a1ef49 --- /dev/null +++ b/src/icons/Renew.tsx @@ -0,0 +1,20 @@ +type IconProps = { + color: string; +}; + +export const RenewIcon = ({ color }: IconProps) => { + return ( + + + + ); +}; diff --git a/src/icons/index.ts b/src/icons/index.ts index b19345af..02ec36b7 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -1,4 +1,5 @@ export * from './Assign'; export * from './Interlace'; export * from './Partition'; +export * from './Renew'; export * from './Transfer'; diff --git a/src/pages/renewal.tsx b/src/pages/renewal.tsx new file mode 100644 index 00000000..fbddf24e --- /dev/null +++ b/src/pages/renewal.tsx @@ -0,0 +1,164 @@ +import { + Backdrop, + Box, + Button, + CircularProgress, + FormControl, + InputLabel, + MenuItem, + Select, + Stack, + Typography, + useTheme, +} from '@mui/material'; +import Link from 'next/link'; +import { useState } from 'react'; + +import { useRenewableParachains } from '@/hooks/useRenewableParas'; +import { formatBalance, sendTx } from '@/utils/functions'; + +import { ProgressButton } from '@/components'; +import Balance from '@/components/Elements/Balance'; + +import chainData from '@/chaindata'; +import { useAccounts } from '@/contexts/account'; +import { useCoretimeApi } from '@/contexts/apis'; +import { useBalances } from '@/contexts/balance'; +import { useNetwork } from '@/contexts/network'; +import { useToast } from '@/contexts/toast'; + +const Renewal = () => { + const theme = useTheme(); + + const { + state: { activeAccount, activeSigner }, + } = useAccounts(); + const { balance } = useBalances(); + const { loading, parachains } = useRenewableParachains(); + const { + state: { api, symbol }, + } = useCoretimeApi(); + const { toastError, toastInfo, toastSuccess } = useToast(); + const { network } = useNetwork(); + + const [paraId, setParaId] = useState(0); + const [working, setWorking] = useState(false); + + const defaultHandler = { + ready: () => toastInfo('Transaction was initiated.'), + inBlock: () => toastInfo(`In Block`), + finalized: () => setWorking(false), + success: () => { + toastSuccess('Successfully renewed the selected parachain.'); + }, + error: () => { + toastError(`Failed to renew the selected parachain.`); + setWorking(false); + }, + }; + + const handleRenew = () => { + if (!activeAccount || !api || !activeSigner) return; + + const { core } = parachains[paraId]; + + const txRenewal = api.tx.broker.renew(core); + + sendTx(txRenewal, activeAccount.address, activeSigner, defaultHandler); + }; + + return loading ? ( + + + + ) : parachains.length === 0 ? ( + There are no renewable parachains. + ) : ( + <> + + + + Renew a parachain + + + Renew a parachain + + + + + + + + Select a parachain to renew. + + + Parachain + + + + {`Core number: ${parachains[paraId].core}`} + {`Renewal price: ${formatBalance( + parachains[paraId].price.toString(), + false + )} ${symbol}`} + + + + + + + + + + ); +}; +export default Renewal;