diff --git a/package.json b/package.json index 14fac057..d4b8b4ef 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "portal", "private": true, - "version": "3.1.0", + "version": "3.2.0", "type": "module", "scripts": { "build:testnet": "NETWORK_NAME=testnet bash build.sh", diff --git a/packages/core/bun.lockb b/packages/core/bun.lockb index c8746163..e6b0635c 100755 Binary files a/packages/core/bun.lockb and b/packages/core/bun.lockb differ diff --git a/packages/core/package.json b/packages/core/package.json index c3f65fe2..f29040ca 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,7 +11,6 @@ }, "author": "SKALE Labs", "devDependencies": { - "ethers": "*.*.*", "typescript": "^4.9.5" } } \ No newline at end of file diff --git a/packages/core/src/types/staking/Delegation.ts b/packages/core/src/types/staking/Delegation.ts index 1442120d..bb2fe2d4 100644 --- a/packages/core/src/types/staking/Delegation.ts +++ b/packages/core/src/types/staking/Delegation.ts @@ -69,3 +69,22 @@ export interface IDelegationInfo { delegationId: bigint delegationType: DelegationType } + +export interface IDelegationTotals { + proposed: { + count: number + amount: bigint + } + accepted: { + count: number + amount: bigint + } + delegated: { + count: number + amount: bigint + } + completed: { + count: number + amount: bigint + } +} \ No newline at end of file diff --git a/src/App.scss b/src/App.scss index cf132899..359753ee 100644 --- a/src/App.scss +++ b/src/App.scss @@ -76,6 +76,10 @@ body { width: 100%; } +.fullH { + height: 100%; +} + .mp__btnConnect { position: relative; @@ -449,12 +453,8 @@ body::-webkit-scrollbar { box-shadow: none !important; } -.btnerror { - background: #f4433621; -} - -.btnwarning { - background: rgb(244 139 54 / 13%); +.btnDisabled { + background: #262626; } .btnSmLoading { @@ -543,6 +543,12 @@ body::-webkit-scrollbar { } } +.MuiToggleButton-root { + border-radius: 25px !important; + padding: 4px 10px; + border: none !important; +} + .copyBoard { margin: 10px 0 !important; padding: 13pt 15pt !important; @@ -791,6 +797,22 @@ input[type=number] { } } +.chipNotification { + background: #e94e4e; + border-radius: 20px; + width: 20px; + height: 20px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + + p { + color: #000000de !important; + font-weight: 600 !important; + } +} + .chipTrending { background: linear-gradient(180deg, #e56d36, #D0602D) !important; @@ -825,12 +847,13 @@ input[type=number] { } .chipXs { - border-radius: 20px; - padding: 3px 6px; + border-radius: 15px; + padding: 6px 8px; + text-align: center; svg { - width: 14px; - height: 14px; + width: 12px; + height: 12px; } } @@ -844,16 +867,6 @@ input[type=number] { } } -.chipXs { - border-radius: 15px; - padding: 4px 6px; - - svg { - width: 12px; - height: 12px; - } -} - .skChip { background: linear-gradient(180deg, rgb(52 52 52), rgb(31 31 31)); } @@ -885,6 +898,11 @@ input[type=number] { color: #3cda94; } +.chip_REWARDS { + background: linear-gradient(180deg, #3d390f, #2a230a); + color: #dac83c; +} + .chip_ACCEPTED { background: linear-gradient(180deg, #233d0f, #0a1b07); color: #3cda4e; @@ -1149,7 +1167,6 @@ input[type=number] { } } - .trustedBadge { color: #0095f6; } @@ -1158,12 +1175,6 @@ input[type=number] { color: #ffb817; } -.validatorCard { - height: 100% !important; - cursor: pointer; -} - - .pOneLine { overflow: hidden; white-space: nowrap; @@ -1426,7 +1437,21 @@ input[type=number] { display: none; } +.opacity0 { + opacity: 0; +} + .MuiTooltip-tooltip { font-size: 0.8rem !important; padding: 8px 12px !important; +} + +.delegationFlowText { + border-top: 2px #4a4a4a solid; + margin: 0 10px; + padding: 2px 5px 0 5px; +} + +.delegationFlowIcon { + margin-top: -20px; } \ No newline at end of file diff --git a/src/Portal.tsx b/src/Portal.tsx index 9b109dd7..490ffb62 100644 --- a/src/Portal.tsx +++ b/src/Portal.tsx @@ -21,9 +21,19 @@ * @copyright SKALE Labs 2023-Present */ +import { useState, useEffect } from 'react' +import { types } from '@/core' + import Box from '@mui/material/Box' import CssBaseline from '@mui/material/CssBaseline' -import { useMetaportStore, useWagmiAccount, Debug, cls, cmn } from '@skalenetwork/metaport' +import { + useMetaportStore, + useWagmiAccount, + Debug, + cls, + cmn, + PROXY_ENDPOINTS +} from '@skalenetwork/metaport' import Header from './Header' import SkDrawer from './SkDrawer' @@ -31,17 +41,123 @@ import Router from './Router' import SkBottomNavigation from './SkBottomNavigation' import ProfileModal from './components/profile/ProfileModal' +import { formatSChains } from './core/chain' +import { STATS_API } from './core/constants' +import { getValidatorDelegations } from './core/delegation/staking' +import { getValidator } from './core/delegation' +import { initContracts } from './core/contracts' + export default function Portal() { const mpc = useMetaportStore((state) => state.mpc) + + const [schains, setSchains] = useState([]) + const [metrics, setMetrics] = useState(null) + const [stats, setStats] = useState(null) + const [validator, setValidator] = useState(null) + const [validatorDelegations, setValidatorDelegations] = useState< + types.staking.IDelegation[] | null + >(null) + const [customAddress, setCustomAddress] = useState(undefined) + const [sc, setSc] = useState(null) + const [loadCalled, setLoadCalled] = useState(false) + + const endpoint = PROXY_ENDPOINTS[mpc.config.skaleNetwork] + const statsApi = STATS_API[mpc.config.skaleNetwork] + const { address } = useWagmiAccount() if (!mpc) return
+ + useEffect(() => { + initSkaleContracts() + loadData() + }, []) + + useEffect(() => { + loadValidator() + }, [address, customAddress, sc]) + + async function initSkaleContracts() { + setLoadCalled(true) + if (loadCalled) return + setSc(await initContracts(mpc)) + } + + async function loadChains() { + try { + const response = await fetch(`https://${endpoint}/files/chains.json`) + const chainsJson = await response.json() + setSchains(formatSChains(chainsJson)) + } catch (e) { + console.log('Failed to load chains') + console.error(e) + } + } + + async function loadMetrics() { + try { + const response = await fetch(`https://${endpoint}/files/metrics.json`) + const metricsJson = await response.json() + setMetrics(metricsJson) + } catch (e) { + console.log('Failed to load metrics') + console.error(e) + } + } + + async function loadStats() { + if (statsApi === null) return + try { + const response = await fetch(statsApi) + const statsResp = await response.json() + setStats(statsResp.payload) + } catch (e) { + console.log('Failed to load stats') + console.error(e) + } + } + + async function loadValidator() { + const addr = customAddress ?? address + if (!sc || !addr) { + setValidator(null) + setValidatorDelegations(null) + return + } + const validatorData = await getValidator(sc.validatorService, addr) + setValidator(validatorData) + if (validatorData && validatorData.id) { + setValidatorDelegations(await getValidatorDelegations(sc, validatorData.id)) + } else { + setValidator(undefined) + setValidatorDelegations(null) + } + } + + async function loadData() { + loadChains() + loadMetrics() + loadStats() + loadValidator() + } + return (
- +
- +
diff --git a/src/Router.tsx b/src/Router.tsx index 61d0a8e8..c29cb48d 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -26,7 +26,7 @@ import { useState, useEffect } from 'react' import { WalletClient } from 'viem' import { Helmet } from 'react-helmet' -import { useLocation, Routes, Route, useSearchParams, Navigate } from 'react-router-dom' +import { useLocation, Routes, Route, Navigate, useSearchParams } from 'react-router-dom' import { TransitionGroup, CSSTransition } from 'react-transition-group' import { useTheme } from '@mui/material/styles' @@ -35,7 +35,6 @@ import { CircularProgress } from '@mui/material' import { useMetaportStore, - PROXY_ENDPOINTS, type MetaportState, useWagmiAccount, useWagmiWalletClient, @@ -64,6 +63,7 @@ import Staking from './pages/Staking' import StakeValidator from './pages/StakeValidator' import StakeAmount from './pages/StakeAmount' import Validators from './pages/Validators' +import Validator from './pages/Validator' import Onramp from './pages/Onramp' import TermsModal from './components/TermsModal' import Changelog from './pages/Changelog' @@ -72,16 +72,24 @@ import MetricsWarning from './components/MetricsWarning' import ScrollToTop from './components/ScrollToTop' import { getHistoryFromStorage, setHistoryToStorage } from './core/transferHistory' -import { BRIDGE_PAGES, MAINNET_CHAIN_NAME, STAKING_PAGES, STATS_API } from './core/constants' -import { type IValidator, type ISkaleContractsMap, type StakingInfoMap } from './core/interfaces' +import { BRIDGE_PAGES, MAINNET_CHAIN_NAME, STAKING_PAGES } from './core/constants' import { getValidators } from './core/delegation/validators' -import { initContracts } from './core/contracts' import { getStakingInfoMap } from './core/delegation/staking' -import { formatSChains } from './core/chain' import { loadMeta } from './core/metadata' -export default function Router() { +export default function Router(props: { + loadData: () => Promise + customAddress?: types.AddressType + setCustomAddress: (address: types.AddressType) => void + schains: types.ISChain[] + stats: types.IStats | null + metrics: types.IMetrics | null + validator: types.staking.IValidator | null | undefined + validatorDelegations: types.staking.IDelegation[] | null + sc: types.staking.ISkaleContractsMap | null + loadValidator: () => Promise +}) { const location = useLocation() const currentUrl = `${window.location.origin}${location.pathname}${location.search}` @@ -89,39 +97,28 @@ export default function Router() { const isXs = useMediaQuery(theme.breakpoints.down('sm')) const [chainsMeta, setChainsMeta] = useState(null) - const [schains, setSchains] = useState([]) - const [metrics, setMetrics] = useState(null) - const [stats, setStats] = useState(null) const [termsAccepted, setTermsAccepted] = useState(false) const [stakingTermsAccepted, setStakingTermsAccepted] = useState(false) - const [loadCalled, setLoadCalled] = useState(false) - const [sc, setSc] = useState(null) - const [validators, setValidators] = useState([]) - const [si, setSi] = useState({ 0: null, 1: null, 2: null }) - - const [customAddress, setCustomAddress] = useState(undefined) + const [validators, setValidators] = useState([]) + const [si, setSi] = useState({ 0: null, 1: null, 2: null }) const mpc = useMetaportStore((state: MetaportState) => state.mpc) const transfersHistory = useMetaportStore((state) => state.transfersHistory) const setTransfersHistory = useMetaportStore((state) => state.setTransfersHistory) + const [searchParams, _] = useSearchParams() const { address } = useWagmiAccount() const { data: walletClient } = useWagmiWalletClient() const { switchChainAsync } = useWagmiSwitchNetwork() - const [searchParams, _] = useSearchParams() - const endpoint = PROXY_ENDPOINTS[mpc.config.skaleNetwork] - const statsApi = STATS_API[mpc.config.skaleNetwork] - useEffect(() => { setTransfersHistory(getHistoryFromStorage(mpc.config.skaleNetwork)) - initSkaleContracts() loadMetadata() }, []) useEffect(() => { - setCustomAddress((searchParams.get('_customAddress') as types.AddressType) ?? undefined) + props.setCustomAddress((searchParams.get('_customAddress') as types.AddressType) ?? undefined) }, [location]) useEffect(() => { @@ -142,65 +139,19 @@ export default function Router() { return walletClientToSigner(walletClient!) } - async function loadData() { - loadChains() - loadMetrics() - loadStats() - } - async function loadMetadata() { setChainsMeta(await loadMeta(mpc.config.skaleNetwork)) } - async function loadChains() { - try { - const response = await fetch(`https://${endpoint}/files/chains.json`) - const chainsJson = await response.json() - setSchains(formatSChains(chainsJson)) - } catch (e) { - console.log('Failed to load chains') - console.error(e) - } - } - - async function loadMetrics() { - try { - const response = await fetch(`https://${endpoint}/files/metrics.json`) - const metricsJson = await response.json() - setMetrics(metricsJson) - } catch (e) { - console.log('Failed to load metrics') - console.error(e) - } - } - - async function loadStats() { - if (statsApi === null) return - try { - const response = await fetch(statsApi) - const statsResp = await response.json() - setStats(statsResp.payload) - } catch (e) { - console.log('Failed to load stats') - console.error(e) - } - } - - async function initSkaleContracts() { - setLoadCalled(true) - if (loadCalled) return - setSc(await initContracts(mpc)) - } - async function loadValidators() { - if (!sc) return - const validatorsData = await getValidators(sc.validatorService) + if (!props.sc) return + const validatorsData = await getValidators(props.sc.validatorService) setValidators(validatorsData) } async function loadStakingInfo() { - if (!sc) return - setSi(await getStakingInfoMap(sc, customAddress ?? address)) + if (!props.sc) return + setSi(await getStakingInfoMap(props.sc, props.customAddress ?? address)) } function isToSPage(pages: any): boolean { @@ -250,7 +201,7 @@ export default function Router() { - + @@ -261,8 +212,8 @@ export default function Router() { } /> @@ -276,9 +227,9 @@ export default function Router() { element={ @@ -289,10 +240,10 @@ export default function Router() { path=":name" element={ } /> @@ -324,8 +275,8 @@ export default function Router() { chainsMeta={chainsMeta} mpc={mpc} isXs={isXs} - metrics={metrics} - loadData={loadData} + metrics={props.metrics} + loadData={props.loadData} /> } /> @@ -350,10 +301,10 @@ export default function Router() { validators={validators} loadValidators={loadValidators} loadStakingInfo={loadStakingInfo} - sc={sc} + sc={props.sc} si={si} - address={customAddress ?? address} - customAddress={customAddress} + address={props.customAddress ?? address} + customAddress={props.customAddress} getMainnetSigner={getMainnetSigner} /> } @@ -365,7 +316,24 @@ export default function Router() { mpc={mpc} validators={validators} loadValidators={loadValidators} - sc={sc} + sc={props.sc} + validatorDelegations={props.validatorDelegations} + /> + } + /> + } /> @@ -378,7 +346,7 @@ export default function Router() { validators={validators} loadValidators={loadValidators} loadStakingInfo={loadStakingInfo} - sc={sc} + sc={props.sc} si={si} address={address} getMainnetSigner={getMainnetSigner} @@ -393,7 +361,7 @@ export default function Router() { validators={validators} loadValidators={loadValidators} loadStakingInfo={loadStakingInfo} - sc={sc} + sc={props.sc} si={si} /> } diff --git a/src/SkDrawer.tsx b/src/SkDrawer.tsx index 8303a38d..6efbb364 100644 --- a/src/SkDrawer.tsx +++ b/src/SkDrawer.tsx @@ -1,5 +1,29 @@ +/** + * @license + * SKALE portal + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file SkDrawer.tsx + * @copyright SKALE Labs 2024-Present + */ + import { cls, cmn } from '@skalenetwork/metaport' import { useLocation, Link } from 'react-router-dom' +import { types } from '@/core' import Box from '@mui/material/Box' @@ -23,10 +47,13 @@ import LinkRoundedIcon from '@mui/icons-material/LinkRounded' import ExploreOutlinedIcon from '@mui/icons-material/ExploreOutlined' import { GET_STARTED_URL } from './core/constants' +import DelegationsNotification from './components/delegation/DelegationsNotification' const drawerWidth = 220 -export default function SkDrawer() { +export default function SkDrawer(props: { + validatorDelegations: types.staking.IDelegation[] | null +}) { const location = useLocation() return ( @@ -55,12 +82,7 @@ export default function SkDrawer() { - + @@ -151,12 +173,16 @@ export default function SkDrawer() { + diff --git a/src/_variables.scss b/src/_variables.scss index 3a275190..8bd89e65 100644 --- a/src/_variables.scss +++ b/src/_variables.scss @@ -3,9 +3,10 @@ $sk-bg: #191919; $sk-bg-prim: #000000; $sk-btn-height: 47px; +$sk-prim: #93B8EC; $border-color: #353535; $border-color-light: #171616; // legacy $sk-paper-color: rgb(136 135 135 / 15%); -$sk-gray-background-color: rgba(161, 161, 161, 0.2); +$sk-gray-background-color: rgba(161, 161, 161, 0.2); \ No newline at end of file diff --git a/src/assets/validators/v10.png b/src/assets/validators/v10.png new file mode 100644 index 00000000..9ac46d90 Binary files /dev/null and b/src/assets/validators/v10.png differ diff --git a/src/assets/validators/v10.webp b/src/assets/validators/v10.webp deleted file mode 100644 index 9ea42a6e..00000000 Binary files a/src/assets/validators/v10.webp and /dev/null differ diff --git a/src/assets/validators/v43.png b/src/assets/validators/v43.png new file mode 100644 index 00000000..9ac46d90 Binary files /dev/null and b/src/assets/validators/v43.png differ diff --git a/src/assets/validators/v43.webp b/src/assets/validators/v43.webp deleted file mode 100644 index 9ea42a6e..00000000 Binary files a/src/assets/validators/v43.webp and /dev/null differ diff --git a/src/components/ErrorTile.tsx b/src/components/ErrorTile.tsx index e40cdb3d..99f49a5b 100644 --- a/src/components/ErrorTile.tsx +++ b/src/components/ErrorTile.tsx @@ -43,7 +43,7 @@ export default function ErrorTile(props: { icon={} color="error" grow - children={ + childrenRi={ props.setErrorMsg && ( + + + } + /> +
+ + } + /> + + + ) +} + +export default ChainRewards diff --git a/src/components/delegation/Delegate.tsx b/src/components/delegation/Delegate.tsx index 98b013d3..a2c6dcc9 100644 --- a/src/components/delegation/Delegate.tsx +++ b/src/components/delegation/Delegate.tsx @@ -28,7 +28,6 @@ import { cmn, cls, TokenIcon, - type interfaces, fromWei, styles, toWei, @@ -43,13 +42,13 @@ import { TextField } from '@mui/material' import AccessTimeRoundedIcon from '@mui/icons-material/AccessTimeRounded' import TransitEnterexitRoundedIcon from '@mui/icons-material/TransitEnterexitRounded' import EventRepeatRoundedIcon from '@mui/icons-material/EventRepeatRounded' +import AccountTreeRoundedIcon from '@mui/icons-material/AccountTreeRounded' import Tile from '../Tile' import SkStack from '../SkStack' import ErrorTile from '../ErrorTile' import Loader from '../Loader' -import { type DelegationType, type IValidator, type StakingInfoMap } from '../../core/interfaces' import { formatBalance } from '../../core/helper' import { DEFAULT_DELEGATION_INFO, @@ -58,17 +57,19 @@ import { DEFAULT_ERROR_MSG } from '../../core/constants' import { initActionContract } from '../../core/contracts' +import { types } from '@/core' +import DelegationFlow from './DelegationFlow' debug.enable('*') const log = debug('portal:pages:Delegate') export default function Delegate(props: { mpc: MetaportCore - validator: IValidator | undefined - si: StakingInfoMap + validator: types.staking.IValidator | undefined + si: types.staking.StakingInfoMap getMainnetSigner: () => Promise - address: interfaces.AddressType - delegationType: DelegationType + address: types.AddressType + delegationType: types.staking.DelegationType loaded: boolean delegationTypeAvailable: boolean errorMsg: string | undefined @@ -163,9 +164,17 @@ export default function Delegate(props: { value="Auto-renewed" text="Renewal" icon={} - color={true ? undefined : 'error'} /> + + } + grow + children={} + /> + Promise - cancelRequest: (delegationInfo: IDelegationInfo) => Promise - loading: IRewardInfo | IDelegationInfo | false + delegation: types.staking.IDelegation + validator: types.staking.IValidator + delegationType: types.staking.DelegationType + accept?: (delegationInfo: types.staking.IDelegationInfo) => Promise + unstake?: (delegationInfo: types.staking.IDelegationInfo) => Promise + cancelRequest?: (delegationInfo: types.staking.IDelegationInfo) => Promise + loading: types.staking.IRewardInfo | types.staking.IDelegationInfo | false isXs: boolean - customAddress: interfaces.AddressType | undefined + customAddress: types.AddressType | undefined + isValidatorPage?: boolean }) { - const validator = getValidatorById(props.validators, props.delegation.validator_id) const source = getDelegationSource(props.delegation) const delegationAmount = formatBalance(props.delegation.amount, 'SKL') const [open, setOpen] = useState(false) @@ -72,7 +70,7 @@ export default function Delegation(props: { delId === DelegationState.PROPOSED || delId === DelegationState.ACCEPTED - const delegationInfo: IDelegationInfo = { + const delegationInfo: types.staking.IDelegationInfo = { delegationId: props.delegation.id, delegationType: props.delegationType } @@ -98,38 +96,47 @@ export default function Delegation(props: { } } - if (!validator) return + const noActions = + Number(props.delegation.stateId) !== DelegationState.PROPOSED && + Number(props.delegation.stateId) !== DelegationState.DELEGATED && + !isCompleted && + !props.isValidatorPage + + if (!props.validator) return return (
{ + if (noActions) return setOpen(!open) }} >
- + {!props.isValidatorPage && ( + + )}

ID: {Number(props.delegation.id)}

{formatBigIntTimestampSeconds(props.delegation.created)}

- {props.delegationType === DelegationType.ESCROW ? ( + {props.delegationType === types.staking.DelegationType.ESCROW ? ( ) : null} - {props.delegationType === DelegationType.ESCROW2 ? ( + {props.delegationType === types.staking.DelegationType.ESCROW2 ? ( @@ -139,7 +146,7 @@ export default function Delegation(props: {
-
+

{props.delegation.state.replace(/_/g, ' ')}

@@ -150,7 +157,7 @@ export default function Delegation(props: {
-
+

{source}

@@ -170,52 +177,77 @@ export default function Delegation(props: {

{getStakingText()}

- {isCompleted ? ( -
-

Delegation completed

-

- {convertMonthIndexToText(Number(props.delegation.finished))} -

-
+ {props.isValidatorPage && ( + } + /> + )} + {isCompleted && ( + } + /> + )} + {Number(props.delegation.stateId) === DelegationState.PROPOSED && props.accept ? ( + { + props.accept && (await props.accept(delegationInfo)) + }} + disabled={props.loading !== false || props.customAddress !== undefined} + /> ) : null} - {Number(props.delegation.stateId) === DelegationState.DELEGATED ? ( + {Number(props.delegation.stateId) === DelegationState.DELEGATED && props.unstake ? ( { - await props.unstake(delegationInfo) + props.unstake && (await props.unstake(delegationInfo)) }} disabled={props.loading !== false || props.customAddress !== undefined} /> ) : null} - {Number(props.delegation.stateId) === DelegationState.PROPOSED ? ( + {Number(props.delegation.stateId) === DelegationState.PROPOSED && props.cancelRequest ? ( { - await props.cancelRequest(delegationInfo) + props.cancelRequest && (await props.cancelRequest(delegationInfo)) }} disabled={props.loading !== false || props.customAddress !== undefined} /> ) : null} - {Number(props.delegation.stateId) !== DelegationState.PROPOSED && - Number(props.delegation.stateId) !== DelegationState.DELEGATED && - !isCompleted ? ( -

- No actions available -

- ) : null}
diff --git a/src/components/delegation/DelegationFlow.tsx b/src/components/delegation/DelegationFlow.tsx new file mode 100644 index 00000000..98ec5e2c --- /dev/null +++ b/src/components/delegation/DelegationFlow.tsx @@ -0,0 +1,108 @@ +/** + * @license + * SKALE portal + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * @file DelegationFlow.tsx + * @copyright SKALE Labs 2024-Present + */ + +import { cls, cmn, styles } from '@skalenetwork/metaport' +import { types } from '@/core' + +import ArrowForwardRoundedIcon from '@mui/icons-material/ArrowForwardRounded' + +import { formatBigIntTimestampSeconds } from '../../core/timeHelper' + +interface DelegationFlowProps { + delegation?: types.staking.IDelegation + className?: string +} + +const DelegationFlow: React.FC = ({ delegation, className }) => { + function formatDate(date: Date): string { + const day = date.getDate().toString().padStart(2, '0') + const month = (date.getMonth() + 1).toString().padStart(2, '0') + const year = date.getFullYear() + return `${day}.${month}.${year}` + } + + function getFirstDayNextMonth(): string { + const date = new Date() + date.setMonth(date.getMonth() + 1) + date.setDate(1) + return formatDate(date) + } + + function getFirstDayMonthAfterNext(): string { + const date = new Date() + date.setMonth(date.getMonth() + 2) + date.setDate(1) + return formatDate(date) + } + + function getCurrentDate(): string { + return formatDate(new Date()) + } + + return ( +
+
+
+

PROPOSED

+
+

+ {delegation ? formatBigIntTimestampSeconds(delegation.created) : getCurrentDate()} +

+
+ +
+
+

ACCEPTED

+
+

+ Until {getFirstDayNextMonth()} +

+
+ +
+
+

DELEGATED

+
+

+ From {getFirstDayNextMonth()} +

+
+ +
+
+

REWARDS GENERATED

+
+

+ Monthly, starting on {getFirstDayMonthAfterNext()} +

+
+
+ ) +} + +export default DelegationFlow diff --git a/src/components/delegation/DelegationTotals.tsx b/src/components/delegation/DelegationTotals.tsx new file mode 100644 index 00000000..a18c7577 --- /dev/null +++ b/src/components/delegation/DelegationTotals.tsx @@ -0,0 +1,87 @@ +/** + * @license + * SKALE portal + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file DelegationTotals.tsx + * @copyright SKALE Labs 2024-Present + */ + +import { useMemo } from 'react' +import { cls, styles, useUIStore } from '@skalenetwork/metaport' +import { type types } from '@/core' + +import InboxRoundedIcon from '@mui/icons-material/InboxRounded' +import TaskAltRoundedIcon from '@mui/icons-material/TaskAltRounded' +import DonutLargeRoundedIcon from '@mui/icons-material/DonutLargeRounded' +import LibraryAddCheckRoundedIcon from '@mui/icons-material/LibraryAddCheckRounded' + +import { calculateDelegationTotals } from '../../core/delegation/delegations' +import { formatBalance } from '../../core/helper' + +import SkStack from '../SkStack' +import Tile from '../Tile' + +interface DelegationTotalsProps { + delegations: types.staking.IDelegation[] | null + className?: string +} + +const DelegationTotals: React.FC = ({ delegations, className }) => { + const totals = useMemo( + () => (delegations ? calculateDelegationTotals(delegations) : null), + [delegations] + ) + const theme = useUIStore((state) => state.theme) + const getTileText = (status: string, count?: number) => `${status}${count ? ` (${count})` : ''}` + + return ( + + } + /> + } + /> + } + /> + } + /> + + ) +} + +export default DelegationTotals diff --git a/src/components/delegation/DelegationTypeSelect.tsx b/src/components/delegation/DelegationTypeSelect.tsx index d2055b05..6597e3d0 100644 --- a/src/components/delegation/DelegationTypeSelect.tsx +++ b/src/components/delegation/DelegationTypeSelect.tsx @@ -22,14 +22,14 @@ import { cmn, cls } from '@skalenetwork/metaport' -import { DelegationType, type StakingInfoMap } from '../../core/interfaces' import NativeSelect from '@mui/material/NativeSelect' import { isDelegationTypeAvailable } from '../../core/delegation/staking' +import { types } from '@/core' export default function DelegationTypeSelect(props: { - delegationType: DelegationType + delegationType: types.staking.DelegationType handleChange: (event: any) => void - si: StakingInfoMap + si: types.staking.StakingInfoMap }) { return (
@@ -39,16 +39,16 @@ export default function DelegationTypeSelect(props: { value={props.delegationType} onChange={props.handleChange} > - - {isDelegationTypeAvailable(props.si, DelegationType.ESCROW) ? ( - ) : null} - {isDelegationTypeAvailable(props.si, DelegationType.ESCROW2) ? ( - ) : null} diff --git a/src/components/delegation/Delegations.tsx b/src/components/delegation/Delegations.tsx index 0916b350..0af660ef 100644 --- a/src/components/delegation/Delegations.tsx +++ b/src/components/delegation/Delegations.tsx @@ -21,46 +21,38 @@ * @copyright SKALE Labs 2024-Present */ -import { cmn, cls, styles, type interfaces } from '@skalenetwork/metaport' +import { cmn, cls, styles } from '@skalenetwork/metaport' import Skeleton from '@mui/material/Skeleton' import AllInboxRoundedIcon from '@mui/icons-material/AllInboxRounded' import PieChartRoundedIcon from '@mui/icons-material/PieChartRounded' import Headline from '../Headline' import DelegationsToValidator from './DelegationsToValidator' - -import { - DelegationType, - type IDelegationInfo, - type IDelegationsToValidator, - type IRewardInfo, - type IValidator, - type StakingInfoMap -} from '../../core/interfaces' +import { types } from '@/core' export default function Delegations(props: { - si: StakingInfoMap - validators: IValidator[] - retrieveRewards: (rewardInfo: IRewardInfo) => Promise - loading: IRewardInfo | IDelegationInfo | false + si: types.staking.StakingInfoMap + validators: types.staking.IValidator[] + retrieveRewards: (rewardInfo: types.staking.IRewardInfo) => Promise + loading: types.staking.IRewardInfo | types.staking.IDelegationInfo | false setErrorMsg: (errorMsg: string | undefined) => void errorMsg: string | undefined - unstake: (delegationInfo: IDelegationInfo) => Promise - cancelRequest: (delegationInfo: IDelegationInfo) => Promise + unstake: (delegationInfo: types.staking.IDelegationInfo) => Promise + cancelRequest: (delegationInfo: types.staking.IDelegationInfo) => Promise isXs: boolean - address: interfaces.AddressType | undefined - customAddress: interfaces.AddressType | undefined - customRewardAddress: interfaces.AddressType | undefined - setCustomRewardAddress: (customRewardAddress: interfaces.AddressType | undefined) => void + address: types.AddressType | undefined + customAddress: types.AddressType | undefined + customRewardAddress: types.AddressType | undefined + setCustomRewardAddress: (customRewardAddress: types.AddressType | undefined) => void }) { - const loaded = props.si[DelegationType.REGULAR] !== null + const loaded = props.si[types.staking.DelegationType.REGULAR] !== null const noDelegations = - (!props.si[DelegationType.REGULAR] || - props.si[DelegationType.REGULAR]?.delegations.length === 0) && - (!props.si[DelegationType.ESCROW] || - props.si[DelegationType.ESCROW]?.delegations.length === 0) && - (!props.si[DelegationType.ESCROW2] || - props.si[DelegationType.ESCROW2]?.delegations.length === 0) + (!props.si[types.staking.DelegationType.REGULAR] || + props.si[types.staking.DelegationType.REGULAR]?.delegations.length === 0) && + (!props.si[types.staking.DelegationType.ESCROW] || + props.si[types.staking.DelegationType.ESCROW]?.delegations.length === 0) && + (!props.si[types.staking.DelegationType.ESCROW2] || + props.si[types.staking.DelegationType.ESCROW2]?.delegations.length === 0) return (
) : (
- {props.si[DelegationType.REGULAR]?.delegations.map( - (delegationsToValidator: IDelegationsToValidator, index: number) => ( + {props.si[types.staking.DelegationType.REGULAR]?.delegations.map( + (delegationsToValidator: types.staking.IDelegationsToValidator, index: number) => ( ) )} - {props.si[DelegationType.ESCROW]?.delegations.map( - (delegationsToValidator: IDelegationsToValidator, index: number) => ( + {props.si[types.staking.DelegationType.ESCROW]?.delegations.map( + (delegationsToValidator: types.staking.IDelegationsToValidator, index: number) => ( ) )} - {props.si[DelegationType.ESCROW2]?.delegations.map( - (delegationsToValidator: IDelegationsToValidator, index: number) => ( + {props.si[types.staking.DelegationType.ESCROW2]?.delegations.map( + (delegationsToValidator: types.staking.IDelegationsToValidator, index: number) => ( . + */ + +/** + * @file DelegationsNotification.tsx + * @copyright SKALE Labs 2024-Present + */ + +import { cls, cmn } from '@skalenetwork/metaport' +import { types } from '@/core' + +import { getProposedDelegationsCount } from '../../core/delegation' +import { Tooltip } from '@mui/material' + +export default function DelegationsNotification(props: { + validatorDelegations: types.staking.IDelegation[] | null + className?: string +}) { + const proposedDelegations = getProposedDelegationsCount(props.validatorDelegations) + + if (proposedDelegations && proposedDelegations > 0) { + return ( + 1 && 's' + }`} + > +
+

{proposedDelegations}

+
+
+ ) + } +} diff --git a/src/components/delegation/DelegationsToValidator.tsx b/src/components/delegation/DelegationsToValidator.tsx index c3630a57..5b5a6008 100644 --- a/src/components/delegation/DelegationsToValidator.tsx +++ b/src/components/delegation/DelegationsToValidator.tsx @@ -22,35 +22,33 @@ */ import { useState } from 'react' -import { cls, type interfaces } from '@skalenetwork/metaport' +import { cls } from '@skalenetwork/metaport' import { Collapse } from '@mui/material' -import { - type DelegationType, - type IDelegation, - type IDelegationInfo, - type IDelegationsToValidator, - type IRewardInfo, - type IValidator -} from '../../core/interfaces' + +import { types } from '@/core' + +import { getValidatorById } from '../../core/delegation' import Delegation from './Delegation' import Reward from './Reward' export default function DelegationsToValidator(props: { - delegationsToValidator: IDelegationsToValidator - validators: IValidator[] - delegationType: DelegationType - retrieveRewards: (rewardInfo: IRewardInfo) => Promise - loading: IRewardInfo | IDelegationInfo | false - unstake: (delegationInfo: IDelegationInfo) => Promise - cancelRequest: (delegationInfo: IDelegationInfo) => Promise + delegationsToValidator: types.staking.IDelegationsToValidator + validators: types.staking.IValidator[] + delegationType: types.staking.DelegationType + retrieveRewards: (rewardInfo: types.staking.IRewardInfo) => Promise + loading: types.staking.IRewardInfo | types.staking.IDelegationInfo | false + unstake: (delegationInfo: types.staking.IDelegationInfo) => Promise + cancelRequest: (delegationInfo: types.staking.IDelegationInfo) => Promise isXs: boolean - address: interfaces.AddressType | undefined - customAddress: interfaces.AddressType | undefined - customRewardAddress: interfaces.AddressType | undefined - setCustomRewardAddress: (customRewardAddress: interfaces.AddressType | undefined) => void + address: types.AddressType | undefined + customAddress: types.AddressType | undefined + customRewardAddress: types.AddressType | undefined + setCustomRewardAddress: (customRewardAddress: types.AddressType | undefined) => void }) { const [open, setOpen] = useState(true) + const validator = getValidatorById(props.validators, props.delegationsToValidator.validatorId) + if (!validator) return return (
{props.delegationsToValidator.delegations.map( - (delegation: IDelegation, index: number) => ( + (delegation: types.staking.IDelegation, index: number) => ( void + address: types.AddressType | undefined + customRewardAddress: types.AddressType | undefined + setCustomRewardAddress: (customRewardAddress: types.AddressType | undefined) => void retrieveRewards: () => void loading: boolean disabled: boolean @@ -65,7 +64,7 @@ export default function RetrieveRewardModal(props: { const saveAddress = () => { if (isAddress(inputAddress)) { - props.setCustomRewardAddress(inputAddress as interfaces.AddressType) + props.setCustomRewardAddress(inputAddress as types.AddressType) setEdit(false) setErrorMsg(undefined) } else { diff --git a/src/components/delegation/Reward.tsx b/src/components/delegation/Reward.tsx index 6b44f478..cfb0843f 100644 --- a/src/components/delegation/Reward.tsx +++ b/src/components/delegation/Reward.tsx @@ -20,7 +20,7 @@ * @copyright SKALE Labs 2024-Present */ -import { cmn, cls, styles, type interfaces } from '@skalenetwork/metaport' +import { cmn, cls, styles } from '@skalenetwork/metaport' import { Grid } from '@mui/material' import LoadingButton from '@mui/lab/LoadingButton' @@ -30,30 +30,24 @@ import RemoveCircleRoundedIcon from '@mui/icons-material/RemoveCircleRounded' import ValidatorLogo from './ValidatorLogo' -import { - type DelegationType, - type IDelegationInfo, - type IDelegationsToValidator, - type IRewardInfo, - type IValidator -} from '../../core/interfaces' import { getValidatorById } from '../../core/delegation' import { formatBalance } from '../../core/helper' import RetrieveRewardModal from './RetrieveRewardModal' +import { types } from '@/core' export default function Reward(props: { - validators: IValidator[] - delegationsToValidator: IDelegationsToValidator + validators: types.staking.IValidator[] + delegationsToValidator: types.staking.IDelegationsToValidator setOpen: (open: boolean) => void open: boolean - retrieveRewards: (rewardInfo: IRewardInfo) => Promise - loading: IRewardInfo | IDelegationInfo | false - delegationType: DelegationType + retrieveRewards: (rewardInfo: types.staking.IRewardInfo) => Promise + loading: types.staking.IRewardInfo | types.staking.IDelegationInfo | false + delegationType: types.staking.DelegationType isXs: boolean - address: interfaces.AddressType | undefined - customAddress: interfaces.AddressType | undefined - customRewardAddress: interfaces.AddressType | undefined - setCustomRewardAddress: (customRewardAddress: interfaces.AddressType | undefined) => void + address: types.AddressType | undefined + customAddress: types.AddressType | undefined + customRewardAddress: types.AddressType | undefined + setCustomRewardAddress: (customRewardAddress: types.AddressType | undefined) => void }) { const validator = getValidatorById(props.validators, props.delegationsToValidator.validatorId) const rewardsAmount = formatBalance(props.delegationsToValidator.rewards, 'SKL') diff --git a/src/components/delegation/ShowMoreButton.tsx b/src/components/delegation/ShowMoreButton.tsx new file mode 100644 index 00000000..308497cd --- /dev/null +++ b/src/components/delegation/ShowMoreButton.tsx @@ -0,0 +1,53 @@ +/** + * @license + * SKALE portal + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * @file ShowMoreButton.tsx + * @copyright SKALE Labs 2024-Present + */ + +import { Button } from '@mui/material' +import { cls, cmn } from '@skalenetwork/metaport' +import ExpandCircleDownRoundedIcon from '@mui/icons-material/ExpandCircleDownRounded' + +interface ShowMoreButtonProps { + onClick: () => void + remainingItems: number + loading?: boolean + className?: string +} + +const ShowMoreButton: React.FC = ({ + onClick, + remainingItems, + loading, + className +}) => { + return ( + + ) +} + +export default ShowMoreButton diff --git a/src/components/delegation/SortToggle.tsx b/src/components/delegation/SortToggle.tsx new file mode 100644 index 00000000..7a6d183b --- /dev/null +++ b/src/components/delegation/SortToggle.tsx @@ -0,0 +1,69 @@ +/** + * @license + * SKALE portal + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * @file SortToggle.tsx + * @copyright SKALE Labs 2024-Present + */ + +import { useState } from 'react' +import { cls, cmn, styles } from '@skalenetwork/metaport' +import { ToggleButtonGroup, ToggleButton } from '@mui/material' +import FilterListRoundedIcon from '@mui/icons-material/FilterListRounded' +import ContrastRoundedIcon from '@mui/icons-material/ContrastRounded' + +interface SortToggleProps { + onChange: (sort: 'id' | 'status') => void + className?: string +} + +const SortToggle: React.FC = ({ onChange, className }) => { + const [sortBy, setSortBy] = useState<'id' | 'status'>('id') + + const handleChange = (_: React.MouseEvent, newSort: 'id' | 'status') => { + if (newSort !== null) { + setSortBy(newSort) + onChange(newSort) + } + } + + return ( + + + + Sort by ID + + + + Sort by Status + + + ) +} + +export default SortToggle diff --git a/src/components/delegation/Summary.tsx b/src/components/delegation/Summary.tsx index 6d9745a6..d105ed3e 100644 --- a/src/components/delegation/Summary.tsx +++ b/src/components/delegation/Summary.tsx @@ -21,7 +21,7 @@ * @copyright SKALE Labs 2024-Present */ -import { cmn, cls, styles, TokenIcon, type interfaces } from '@skalenetwork/metaport' +import { cmn, cls, styles, TokenIcon } from '@skalenetwork/metaport' import ArrowOutwardRoundedIcon from '@mui/icons-material/ArrowOutwardRounded' import AccountBalanceRoundedIcon from '@mui/icons-material/AccountBalanceRounded' @@ -36,16 +36,11 @@ import SkStack from '../SkStack' import Tile from '../Tile' import AccordionSection from '../AccordionSection' -import { - DelegationType, - type IDelegationInfo, - type IDelegatorInfo, - type IRewardInfo -} from '../../core/interfaces' import { formatBalance, shortAddress } from '../../core/helper' import SkBtn from '../SkBtn' +import { types } from '@/core' -const icons: { [key in DelegationType]: any } = { +const icons: { [key in types.staking.DelegationType]: any } = { 0: , 1: , 2: @@ -54,20 +49,20 @@ const icons: { [key in DelegationType]: any } = { const SUMMARY_VALIDATOR_ID = -1 export default function Summary(props: { - type: DelegationType - accountInfo: IDelegatorInfo | undefined - retrieveUnlocked: (rewardInfo: IRewardInfo) => Promise - loading: IRewardInfo | IDelegationInfo | false - customAddress: interfaces.AddressType | undefined + type: types.staking.DelegationType + accountInfo: types.staking.IDelegatorInfo | undefined + retrieveUnlocked: (rewardInfo: types.staking.IRewardInfo) => Promise + loading: types.staking.IRewardInfo | types.staking.IDelegationInfo | false + customAddress: types.AddressType | undefined isXs: boolean }) { function getTitle() { - if (props.type === DelegationType.ESCROW) return 'Escrow' - if (props.type === DelegationType.ESCROW2) return 'Grant Escrow' + if (props.type === types.staking.DelegationType.ESCROW) return 'Escrow' + if (props.type === types.staking.DelegationType.ESCROW2) return 'Grant Escrow' return 'Account' } - const rewardInfo: IRewardInfo = { + const rewardInfo: types.staking.IRewardInfo = { validatorId: SUMMARY_VALIDATOR_ID, delegationType: props.type } @@ -95,7 +90,7 @@ export default function Summary(props: { icon={} childrenRi={ - {props.type !== DelegationType.REGULAR ? ( + {props.type !== types.staking.DelegationType.REGULAR ? (
@@ -42,7 +42,7 @@ export function ValidatorBadge(props: { validator: IValidator; className?: strin return null } -export function TrustBadge(props: { validator: IValidator }) { +export function TrustBadge(props: { validator: types.staking.IValidator }) { if (props.validator.trusted) { return ( diff --git a/src/components/delegation/ValidatorCard.tsx b/src/components/delegation/ValidatorCard.tsx index 414d3f86..1ca35a2e 100644 --- a/src/components/delegation/ValidatorCard.tsx +++ b/src/components/delegation/ValidatorCard.tsx @@ -30,14 +30,14 @@ import { cmn, cls, styles, fromWei, SkPaper } from '@skalenetwork/metaport' import ValidatorLogo from './ValidatorLogo' import { TrustBadge, ValidatorBadge } from './ValidatorBadges' -import { type DelegationType, type IValidator } from '../../core/interfaces' import { DEFAULT_ERC20_DECIMALS } from '../../core/constants' +import { types } from '@/core' export default function ValidatorCard(props: { - validator: IValidator + validator: types.staking.IValidator validatorId: number | undefined setValidatorId: any - delegationType: DelegationType + delegationType: types.staking.DelegationType size?: 'md' | 'lg' }) { if (!props.validator.trusted) return diff --git a/src/components/delegation/ValidatorInfo.tsx b/src/components/delegation/ValidatorInfo.tsx index 9548a561..917139bd 100644 --- a/src/components/delegation/ValidatorInfo.tsx +++ b/src/components/delegation/ValidatorInfo.tsx @@ -22,6 +22,7 @@ */ import { cmn, cls, fromWei, TokenIcon } from '@skalenetwork/metaport' +import { types } from '@/core' import PercentRoundedIcon from '@mui/icons-material/PercentRounded' import PersonRoundedIcon from '@mui/icons-material/PersonRounded' @@ -31,45 +32,56 @@ import { ValidatorBadge, TrustBadge } from './ValidatorBadges' import Tile from '../Tile' import SkStack from '../SkStack' -import { type IValidator } from '../../core/interfaces' import { DEFAULT_ERC20_DECIMALS } from '../../core/constants' +import { Skeleton } from '@mui/material' -export default function ValidatorInfo(props: { validator: IValidator; className?: string }) { - const description = props.validator.description ? props.validator.description : 'No description' - const minDelegation = fromWei(props.validator.minimumDelegationAmount, DEFAULT_ERC20_DECIMALS) +export default function ValidatorInfo(props: { + validator: types.staking.IValidator | null + className?: string +}) { + const description = props.validator?.description ? props.validator.description : 'No description' + const minDelegation = + props.validator && fromWei(props.validator.minimumDelegationAmount, DEFAULT_ERC20_DECIMALS) return (
- -
-
-

{props.validator.name}

- - + + {props.validator ? ( +
+
+

{props.validator.name}

+ + +
+

+ {description} +

-

- {description} -

-
+ ) : ( +
+ + +
+ )}
} /> } /> - {validators.map((validator: IValidator, index) => ( + {validators.map((validator: types.staking.IValidator, index) => ( { +export async function initContracts(mpc: MetaportCore): Promise { log('Initializing contracts') const provider = mpc.provider('mainnet') const network = await skaleContracts.getNetworkByProvider(provider) @@ -57,15 +56,15 @@ export async function initContracts(mpc: MetaportCore): Promise { log('initActionContract:', skaleNetwork, beneficiary, contractType, delegationType) const network = await skaleContracts.getNetworkByProvider(signer.provider!) let contract: Contract - if (delegationType === DelegationType.REGULAR) { + if (delegationType === types.staking.DelegationType.REGULAR) { contract = await getManagerContract( network, skaleNetwork, @@ -92,14 +91,14 @@ function connectedContract(contract: Contract, signer: Signer): Contract { async function getEscrowContract( network: any, skaleNetwork: types.SkaleNetwork, - delegationType: DelegationType, - beneficiary: interfaces.AddressType + delegationType: types.staking.DelegationType, + beneficiary: types.AddressType ): Promise { const project = await network.getProject('skale-allocator') const instance = await getInstance( project, skaleNetwork, - delegationType === DelegationType.ESCROW ? 'allocator' : 'grants' + delegationType === types.staking.DelegationType.ESCROW ? 'allocator' : 'grants' ) return (await instance.getContract('Escrow', [beneficiary])) as Contract } diff --git a/src/core/delegation/delegations.ts b/src/core/delegation/delegations.ts index 94885498..72642f94 100644 --- a/src/core/delegation/delegations.ts +++ b/src/core/delegation/delegations.ts @@ -22,16 +22,10 @@ */ import { Contract, type Provider, getUint } from 'ethers' -import { ERC_ABIS, type interfaces } from '@skalenetwork/metaport' -import { - type IDelegationArray, - type IDelegation, - type IDelegationsToValidator, - type ISkaleContractsMap, - DelegationType, - type IDelegatorInfo -} from '../interfaces' +import { ERC_ABIS } from '@skalenetwork/metaport' +import { types } from '@/core' import { maxBigInt } from '../helper' +import { BATCH_SIZE } from '../constants' export enum DelegationState { PROPOSED = 0, @@ -55,46 +49,66 @@ export enum DelegationSource { export async function getDelegationIdsByHolder( delegationController: Contract, - address: interfaces.AddressType + address: types.AddressType ): Promise { - const delegationIdsLen = await delegationController.getDelegationsByHolderLength(address) + const idsLen = await delegationController.getDelegationsByHolderLength(address) return await Promise.all( Array.from( - { length: Number(delegationIdsLen) }, + { length: Number(idsLen) }, async (_, id) => await delegationController.delegationsByHolder(address, id) ) ) } -async function getDelegationsRaw( +async function loadDelegationBatch( delegationController: Contract, - delegationIds: bigint[] -): Promise> { + valId: number, + start: number, + size: number +): Promise { return await Promise.all( - delegationIds - .map((delegationId) => [ - delegationController.getDelegation(delegationId), - delegationController.getState(delegationId) - ]) - .flat() + Array.from( + { length: size }, + async (_, index) => await delegationController.delegationsByValidator(valId, start + index) + ) ) } -export async function getDelegations( +export async function getDelegationIdsByValidator( + delegationController: Contract, + valId: number +): Promise { + const totalDelegations = Number(await delegationController.getDelegationsByValidatorLength(valId)) + const batchCount = Math.ceil(totalDelegations / BATCH_SIZE) + let allDelegations: bigint[] = [] + + for (let i = 0; i < batchCount; i++) { + const start = i * BATCH_SIZE + const batchSize = Math.min(BATCH_SIZE, totalDelegations - start) + const batch = await loadDelegationBatch(delegationController, valId, start, batchSize) + allDelegations = [...allDelegations, ...batch] + } + + return allDelegations +} + +async function loadDelegationDetailsBatch( delegationController: Contract, delegationIds: bigint[] -): Promise { - const rawDelegations: Array = await getDelegationsRaw( - delegationController, - delegationIds +): Promise { + const rawData = await Promise.all( + delegationIds.flatMap((id) => [ + delegationController.getDelegation(id), + delegationController.getState(id) + ]) ) - const delegations: IDelegation[] = [] - for (let i = 0; i < rawDelegations.length; i += 2) { - const delegationArray: IDelegationArray = rawDelegations[i] as IDelegationArray - const stateId: bigint = rawDelegations[i + 1] as bigint - delegations.push({ - id: delegationIds[i / 2], + return delegationIds.map((id, index) => { + const delegationArray = rawData[index * 2] + const stateId = rawData[index * 2 + 1] + + return { + id, address: delegationArray[0], validator_id: delegationArray[1], amount: delegationArray[2], @@ -105,12 +119,28 @@ export async function getDelegations( info: delegationArray[7], stateId, state: DelegationState[Number(stateId)] - }) + } + }) +} + +export async function getDelegations( + delegationController: Contract, + delegationIds: bigint[] +): Promise { + const batchCount = Math.ceil(delegationIds.length / BATCH_SIZE) + let allDelegations: types.staking.IDelegation[] = [] + + for (let i = 0; i < batchCount; i++) { + const start = i * BATCH_SIZE + const batchIds = delegationIds.slice(start, start + BATCH_SIZE) + const batchDelegations = await loadDelegationDetailsBatch(delegationController, batchIds) + allDelegations = [...allDelegations, ...batchDelegations] } - return delegations + + return allDelegations } -export function getDelegationSource(delegation: IDelegation): DelegationSource { +export function getDelegationSource(delegation: types.staking.IDelegation): DelegationSource { if (delegation.info.includes('Delegation UI')) return DelegationSource.DELEGATION_UI if (delegation.info.includes('MEW Wallet')) return DelegationSource.MEW_WALLET if (delegation.info.includes('Activate')) return DelegationSource.ACTIVATE @@ -125,11 +155,11 @@ export function getKeyByValue(enumType: any, enumValue: string): string | undefi } export async function groupDelegationsByValidator( - delegations: IDelegation[], + delegations: types.staking.IDelegation[], distributor: Contract, - address: interfaces.AddressType -): Promise { - const groupedDelegations = new Map() + address: types.AddressType +): Promise { + const groupedDelegations = new Map() delegations.forEach((delegation) => { const { validator_id } = delegation const existingDelegations = groupedDelegations.get(validator_id) || [] @@ -147,7 +177,7 @@ export async function groupDelegationsByValidator( const res = await Promise.all( delegationsArray.map( - async (delegationsToValidator: IDelegationsToValidator) => + async (delegationsToValidator: types.staking.IDelegationsToValidator) => await distributor.getAndUpdateEarnedBountyAmountOf.staticCallResult( address, delegationsToValidator.validatorId @@ -171,7 +201,7 @@ export async function groupDelegationsByValidator( return delegationsArray } -export const sumRewards = (delegations: IDelegationsToValidator[]): bigint => +export const sumRewards = (delegations: types.staking.IDelegationsToValidator[]): bigint => delegations.reduce((total, del) => total + del.rewards, BigInt(0)) export async function initSkaleToken(provider: Provider, instance: any): Promise { @@ -180,13 +210,13 @@ export async function initSkaleToken(provider: Provider, instance: any): Promise } export async function getDelegatorInfo( - sc: ISkaleContractsMap, + sc: types.staking.ISkaleContractsMap, rewards: bigint, - address: interfaces.AddressType, - beneficiary?: interfaces.AddressType, - type?: DelegationType -): Promise { - const info: IDelegatorInfo = { + address: types.AddressType, + beneficiary?: types.AddressType, + type?: types.staking.DelegationType +): Promise { + const info: types.staking.IDelegatorInfo = { balance: await sc.skaleToken.balanceOf(address), staked: ( await sc.delegationController.getAndUpdateDelegatedAmount.staticCallResult(address) @@ -201,11 +231,11 @@ export async function getDelegatorInfo( info.allowedToDelegate = maxBigInt(info.balance - info.forbiddenToDelegate, 0n) if (beneficiary) { - if (type === DelegationType.ESCROW) { + if (type === types.staking.DelegationType.ESCROW) { info.vested = await getVestedAmount(sc.allocator, address, beneficiary) info.fullAmount = await sc.allocator.getFullAmount(beneficiary) } - if (type === DelegationType.ESCROW2) { + if (type === types.staking.DelegationType.ESCROW2) { info.vested = await getVestedAmount(sc.grantsAllocator, address, beneficiary) info.fullAmount = await sc.grantsAllocator.getFullAmount(beneficiary) } @@ -218,8 +248,8 @@ export async function getDelegatorInfo( export async function getVestedAmount( allocator: Contract, - escrowAddress: interfaces.AddressType, - address: interfaces.AddressType + escrowAddress: types.AddressType, + address: types.AddressType ): Promise { let vestedAmount: bigint if (await allocator.isVestingActive(address)) { @@ -236,8 +266,54 @@ export async function getVestedAmount( return vestedAmount } -export function getDelegationTypeAlias(type: DelegationType): string { - if (type === DelegationType.ESCROW) return 'Escrow' - if (type === DelegationType.ESCROW2) return 'Grant' +export function getDelegationTypeAlias(type: types.staking.DelegationType): string { + if (type === types.staking.DelegationType.ESCROW) return 'Escrow' + if (type === types.staking.DelegationType.ESCROW2) return 'Grant' return 'Regular' } + +export function calculateDelegationTotals( + delegations: types.staking.IDelegation[] +): types.staking.IDelegationTotals { + const initialTotals: types.staking.IDelegationTotals = { + proposed: { count: 0, amount: 0n }, + accepted: { count: 0, amount: 0n }, + delegated: { count: 0, amount: 0n }, + completed: { count: 0, amount: 0n } + } + + return delegations.reduce((totals, delegation) => { + const amount = delegation.amount + + switch (Number(delegation.stateId)) { + case DelegationState.PROPOSED: + totals.proposed.count++ + totals.proposed.amount += amount + break + case DelegationState.ACCEPTED: + totals.accepted.count++ + totals.accepted.amount += amount + break + case DelegationState.DELEGATED: + totals.delegated.count++ + totals.delegated.amount += amount + break + case DelegationState.COMPLETED: + totals.completed.count++ + totals.completed.amount += amount + break + } + + return totals + }, initialTotals) +} + +export function getProposedDelegationsCount( + validatorDelegations: types.staking.IDelegation[] | null +): number | null { + if (!validatorDelegations) return null + + return validatorDelegations.filter( + (delegation) => Number(delegation.stateId) === DelegationState.PROPOSED + ).length +} diff --git a/src/core/delegation/staking.ts b/src/core/delegation/staking.ts index 40836042..b0362434 100644 --- a/src/core/delegation/staking.ts +++ b/src/core/delegation/staking.ts @@ -21,26 +21,21 @@ * @copyright SKALE Labs 2024-Present */ -import { - DelegationType, - type ISkaleContractsMap, - type StakingInfo, - type StakingInfoMap -} from '../interfaces' -import { type interfaces } from '@skalenetwork/metaport' +import { types } from '@/core' import { isZeroAddr } from '../helper' import { getDelegationIdsByHolder, getDelegations, groupDelegationsByValidator, sumRewards, - getDelegatorInfo + getDelegatorInfo, + getDelegationIdsByValidator } from '.' export async function getStakingInfoMap( - sc: ISkaleContractsMap, - address: interfaces.AddressType | undefined -): Promise { + sc: types.staking.ISkaleContractsMap, + address: types.AddressType | undefined +): Promise { if (!address) return { 0: null, 1: null, 2: null } const escrowAddress = await sc.allocator.getEscrowAddress(address) const escrowGrantsAddress = await sc.grantsAllocator.getEscrowAddress(address) @@ -48,19 +43,19 @@ export async function getStakingInfoMap( 0: await getStakingInfo(sc, address), 1: isZeroAddr(escrowAddress) ? null - : await getStakingInfo(sc, escrowAddress, address, DelegationType.ESCROW), + : await getStakingInfo(sc, escrowAddress, address, types.staking.DelegationType.ESCROW), 2: isZeroAddr(escrowGrantsAddress) ? null - : await getStakingInfo(sc, escrowGrantsAddress, address, DelegationType.ESCROW2) + : await getStakingInfo(sc, escrowGrantsAddress, address, types.staking.DelegationType.ESCROW2) } } export async function getStakingInfo( - sc: ISkaleContractsMap, - address: interfaces.AddressType, - beneficiary?: interfaces.AddressType, - type?: DelegationType -): Promise { + sc: types.staking.ISkaleContractsMap, + address: types.AddressType, + beneficiary?: types.AddressType, + type?: types.staking.DelegationType +): Promise { const delegationIds = await getDelegationIdsByHolder(sc.delegationController, address) const delegationsArray = await getDelegations(sc.delegationController, delegationIds) const groupedDelegations = await groupDelegationsByValidator( @@ -75,10 +70,24 @@ export async function getStakingInfo( } } -export function isDelegationTypeAvailable(si: StakingInfoMap, type: DelegationType): boolean { - return si[DelegationType.REGULAR] !== null && si[type] !== undefined && si[type] !== null +export async function getValidatorDelegations( + sc: types.staking.ISkaleContractsMap, + valId: number +): Promise { + const delegationIds = await getDelegationIdsByValidator(sc.delegationController, valId) + const delegationsArray = await getDelegations(sc.delegationController, delegationIds) + return delegationsArray +} + +export function isDelegationTypeAvailable( + si: types.staking.StakingInfoMap, + type: types.staking.DelegationType +): boolean { + return ( + si[types.staking.DelegationType.REGULAR] !== null && si[type] !== undefined && si[type] !== null + ) } -export function isLoaded(si: StakingInfoMap): boolean { - return si[DelegationType.REGULAR] !== null +export function isLoaded(si: types.staking.StakingInfoMap): boolean { + return si[types.staking.DelegationType.REGULAR] !== null } diff --git a/src/core/delegation/stakingActions.ts b/src/core/delegation/stakingActions.ts new file mode 100644 index 00000000..faf6a526 --- /dev/null +++ b/src/core/delegation/stakingActions.ts @@ -0,0 +1,175 @@ +/** + * @license + * SKALE portal + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file stakingActions.tsx + * @copyright SKALE Labs 2024-Present + */ + +import { type Signer } from 'ethers' +import { sendTransaction } from '@skalenetwork/metaport' +import { type types } from '@/core' + +import { initActionContract } from '../contracts' + +export type LoadingState = types.staking.IRewardInfo | types.staking.IDelegationInfo | false +export type SetLoadingFn = (state: LoadingState) => void +export type SetErrorFn = (msg: string | undefined) => void +export type PostActionFn = () => Promise + +export interface StakingActionProps { + sc: types.staking.ISkaleContractsMap | null + address: types.AddressType | undefined + skaleNetwork: types.SkaleNetwork + getMainnetSigner: () => Promise + setLoading: SetLoadingFn + setErrorMsg: SetErrorFn + postAction: PostActionFn +} + +async function processTx({ + delegationType, + txName, + txArgs, + contractType, + props +}: { + delegationType: types.staking.DelegationType + txName: string + txArgs: any[] + contractType: types.staking.ContractType + props: StakingActionProps +}) { + if (!props.sc || !props.address) return + + try { + const signer = await props.getMainnetSigner() + const contract = await initActionContract( + signer, + delegationType, + props.address, + props.skaleNetwork, + contractType + ) + + const res = await sendTransaction(contract[txName], txArgs) + if (!res.status) { + props.setErrorMsg(res.err?.name) + } else { + props.setErrorMsg(undefined) + await props.postAction() + } + } catch (err: any) { + console.error(err) + props.setErrorMsg(err.message || 'Transaction failed') + } finally { + props.setLoading(false) + } +} + +export async function retrieveRewards({ + rewardInfo, + rewardAddress, + props +}: { + rewardInfo: types.staking.IRewardInfo + rewardAddress: types.AddressType + props: StakingActionProps +}) { + props.setLoading(rewardInfo) + + await processTx({ + delegationType: rewardInfo.delegationType, + txName: 'withdrawBounty', + txArgs: [rewardInfo.validatorId, rewardAddress], + contractType: 'distributor', + props + }) +} + +export async function unstakeDelegation({ + delegationInfo, + props +}: { + delegationInfo: types.staking.IDelegationInfo + props: StakingActionProps +}) { + props.setLoading(delegationInfo) + + await processTx({ + delegationType: delegationInfo.delegationType, + txName: 'requestUndelegation', + txArgs: [delegationInfo.delegationId], + contractType: 'delegation', + props + }) +} + +export async function cancelDelegationRequest({ + delegationInfo, + props +}: { + delegationInfo: types.staking.IDelegationInfo + props: StakingActionProps +}) { + props.setLoading(delegationInfo) + + await processTx({ + delegationType: delegationInfo.delegationType, + txName: 'cancelPendingDelegation', + txArgs: [delegationInfo.delegationId], + contractType: 'delegation', + props + }) +} + +export async function retrieveUnlockedTokens({ + rewardInfo, + props +}: { + rewardInfo: types.staking.IRewardInfo + props: StakingActionProps +}) { + props.setLoading(rewardInfo) + + await processTx({ + delegationType: rewardInfo.delegationType, + txName: 'retrieve', + txArgs: [], + contractType: 'distributor', + props + }) +} + +export async function acceptDelegation({ + delegationInfo, + props +}: { + delegationInfo: types.staking.IDelegationInfo + props: StakingActionProps +}) { + props.setLoading(delegationInfo) + + await processTx({ + delegationType: delegationInfo.delegationType, + txName: 'acceptPendingDelegation', + txArgs: [delegationInfo.delegationId], + contractType: 'delegation', + props + }) +} diff --git a/src/core/delegation/validators.ts b/src/core/delegation/validators.ts index ec6d2027..e567b3d6 100644 --- a/src/core/delegation/validators.ts +++ b/src/core/delegation/validators.ts @@ -23,8 +23,8 @@ import debug from 'debug' import { type Contract } from 'ethers' - -import { type IValidatorArray, type IValidator } from '../interfaces' +import { types } from '@/core' +import { DelegationState } from './delegations' debug.enable('*') const log = debug('portal:core:validators') @@ -33,10 +33,42 @@ export const ESCROW_VALIDATORS = [ 43, 46, 54, 37, 48, 49, 42, 41, 47, 40, 52, 35, 36, 39, 50, 45, 51, 68, 30 ] +const STATUS_ORDER = { + [DelegationState.PROPOSED]: 1, + [DelegationState.ACCEPTED]: 2, + [DelegationState.DELEGATED]: 3, + [DelegationState.COMPLETED]: 4, + [DelegationState.CANCELED]: 5, + [DelegationState.REJECTED]: 6, + [DelegationState.UNDELEGATION_REQUESTED]: 7 +} + +export type SortType = 'id' | 'status' + +export function sortDelegations( + delegations: types.staking.IDelegation[], + sortBy: SortType +): types.staking.IDelegation[] { + return [...delegations].sort((a, b) => { + if (sortBy === 'id') { + return Number(b.id) - Number(a.id) + } else { + const aStatus = Number(a.stateId) as DelegationState + const bStatus = Number(b.stateId) as DelegationState + const statusComparison = STATUS_ORDER[aStatus] - STATUS_ORDER[bStatus] + if (statusComparison === 0) { + return Number(b.id) - Number(a.id) + } + + return statusComparison + } + }) +} + async function getValidatorsRaw( validatorService: Contract, numberOfValidators: bigint[] -): Promise> { +): Promise> { const validatorIds = Array.from(Array(Number(numberOfValidators)).keys()) return await Promise.all( validatorIds @@ -49,39 +81,82 @@ async function getValidatorsRaw( ) } +export async function getValidatorRaw( + validatorService: Contract, + validatorId: number +): Promise<[types.staking.IValidatorArray, boolean, string[]]> { + const [validatorData, isAuthorized, nodeAddresses] = await Promise.all([ + validatorService.validators(validatorId), + validatorService.isAuthorizedValidator(validatorId), + validatorService.getNodeAddresses(validatorId) + ]) + return [validatorData, isAuthorized, nodeAddresses] +} + +function formatValidator( + validatorData: types.staking.IValidatorArray, + isAuthorized: boolean, + nodeAddresses: string[], + validatorId: number +): types.staking.IValidator { + return { + name: validatorData[0], + validatorAddress: validatorData[1], + requestedAddress: validatorData[2], + description: validatorData[3], + feeRate: validatorData[4], + registrationTime: validatorData[5], + minimumDelegationAmount: validatorData[6], + acceptNewRequests: validatorData[7], + trusted: isAuthorized, + id: validatorId, + linkedNodes: nodeAddresses.length + } +} + export async function getValidators( validatorService: Contract, sorted: boolean = true -): Promise { +): Promise { const numberOfValidators = await validatorService.numberOfValidators() log('getValidators: ', numberOfValidators) - const rawValidators: Array = await getValidatorsRaw( - validatorService, - numberOfValidators - ) - const validatorsData: IValidator[] = [] + const rawValidators: Array = + await getValidatorsRaw(validatorService, numberOfValidators) + const validatorsData: types.staking.IValidator[] = [] for (let i = 0; i < rawValidators.length; i += 3) { - const IValidatorArray: IValidatorArray = rawValidators[i] as IValidatorArray + const validatorArray: types.staking.IValidatorArray = rawValidators[ + i + ] as types.staking.IValidatorArray const isTrusted: boolean = rawValidators[i + 1] as boolean const linkedNodeAddresses = rawValidators[i + 2] as string[] - validatorsData.push({ - name: IValidatorArray[0], - validatorAddress: IValidatorArray[1], - requestedAddress: IValidatorArray[2], - description: IValidatorArray[3], - feeRate: IValidatorArray[4], - registrationTime: IValidatorArray[5], - minimumDelegationAmount: IValidatorArray[6], - acceptNewRequests: IValidatorArray[7], - trusted: isTrusted, - id: i / 3 + 1, - linkedNodes: linkedNodeAddresses.length - }) + validatorsData.push(formatValidator(validatorArray, isTrusted, linkedNodeAddresses, i / 3 + 1)) } return sorted ? sortValidators(validatorsData) : validatorsData } -function sortValidators(validatorsData: IValidator[]): IValidator[] { +export async function getValidator( + validatorService: Contract, + address: types.AddressType +): Promise { + try { + const validatorId = await validatorService.getValidatorId(address) + const [validatorData, isAuthorized, nodeAddresses] = await getValidatorRaw( + validatorService, + validatorId + ) + return formatValidator(validatorData, isAuthorized, nodeAddresses, Number(validatorId)) + } catch (error: any) { + if ( + error?.message?.includes('Validator address does not exist') || + error?.message?.includes('ValidatorAddressDoesNotExist') + ) { + return undefined + } + throw error + } +} + +function sortValidators(validatorsData: types.staking.IValidator[]): types.staking.IValidator[] { validatorsData.sort((a, b) => { if (a.trusted !== b.trusted) { return a.trusted ? -1 : 1 @@ -100,15 +175,18 @@ function sortValidators(validatorsData: IValidator[]): IValidator[] { } export function filterValidators( - validators: IValidator[], + validators: types.staking.IValidator[], ids: number[], internal: boolean -): IValidator[] { +): types.staking.IValidator[] { return validators.filter( (val) => (ids.includes(val.id) && internal) || (!ids.includes(val.id) && !internal) ) } -export function getValidatorById(validators: IValidator[], id: bigint): IValidator | undefined { +export function getValidatorById( + validators: types.staking.IValidator[], + id: bigint +): types.staking.IValidator | undefined { return validators.find((val) => Number(val.id) === Number(id)) } diff --git a/src/core/explorer.ts b/src/core/explorer.ts index 57f2489a..f3c3ce13 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -20,7 +20,7 @@ * @copyright SKALE Labs 2024-Present */ -import { BASE_EXPLORER_URLS, interfaces } from '@skalenetwork/metaport' +import { BASE_EXPLORER_URLS } from '@skalenetwork/metaport' import { HTTPS_PREFIX } from './chain' import { type types } from '@/core' @@ -33,6 +33,14 @@ export function getExplorerUrl(network: types.SkaleNetwork, chainName: string): return HTTPS_PREFIX + chainName + '.' + explorerBaseUrl } +export function getExplorerUrlForAddress( + network: types.SkaleNetwork, + chainName: string, + address: string +): string { + return addressUrl(getExplorerUrl(network, chainName), address) +} + export function getTotalAppCounters( countersArray: types.IAppCounters | null ): types.IAddressCounters | null { @@ -48,7 +56,7 @@ export function getTotalAppCounters( } for (const address in countersArray) { if (countersArray.hasOwnProperty(address)) { - const addressCounters = countersArray[address as interfaces.AddressType] + const addressCounters = countersArray[address as types.AddressType] if (addressCounters.gas_usage_count === undefined) continue totalCounters.gas_usage_count = ( parseInt(totalCounters.gas_usage_count) + parseInt(addressCounters.gas_usage_count) diff --git a/src/core/helper.ts b/src/core/helper.ts index e843f105..fd261588 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -20,10 +20,11 @@ * @copyright SKALE Labs 2024-Present */ -import { fromWei, type interfaces } from '@skalenetwork/metaport' +import { fromWei } from '@skalenetwork/metaport' +import { types } from '@/core' import { DEFAULT_ERC20_DECIMALS, ZERO_ADDRESS, DEFAULT_FRACTION_DIGITS } from './constants' -export function isZeroAddr(address: interfaces.AddressType): boolean { +export function isZeroAddr(address: types.AddressType): boolean { return address === ZERO_ADDRESS } @@ -85,7 +86,7 @@ export function minBigInt(a: bigint, b: bigint): bigint { return a < b ? a : b } -export function shortAddress(address: interfaces.AddressType | undefined): string { +export function shortAddress(address: types.AddressType | undefined): string { if (!address) return '' return `${address.slice(0, 4)}...${address.slice(-2)}` } diff --git a/src/core/interfaces/Beneficiary.ts b/src/core/interfaces/Beneficiary.ts deleted file mode 100644 index 66d99690..00000000 --- a/src/core/interfaces/Beneficiary.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * SKALE portal - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -/** - * @file Beneficiary.ts - * @copyright SKALE Labs 2023-Present - */ - -import { type interfaces } from '@skalenetwork/metaport' - -export type IBeneficiaryArray = [bigint, bigint, bigint, bigint, bigint, interfaces.AddressType] - -export interface IBeneficiary { - status: bigint - planId: bigint - startMonth: bigint - fullAmount: bigint - amountAfterLockup: bigint - requestedAddress: interfaces.AddressType -} diff --git a/src/core/interfaces/Delegation.ts b/src/core/interfaces/Delegation.ts deleted file mode 100644 index 85b180b7..00000000 --- a/src/core/interfaces/Delegation.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @license - * SKALE portal - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * @file Delegation.ts - * @copyright SKALE Labs 2023-Present - */ - -import { type interfaces } from '@skalenetwork/metaport' - -export type IDelegationArray = [ - interfaces.AddressType, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - string -] - -export interface IDelegation { - id: bigint - address: interfaces.AddressType - validator_id: bigint - amount: bigint - delegation_period: bigint - created: bigint - started: bigint - finished: bigint - info: string - stateId: bigint - state: string -} - -export interface IDelegationsToValidator { - validatorId: bigint - delegations: IDelegation[] - rewards: bigint - staked: bigint -} - -export enum DelegationType { - REGULAR = 0, - ESCROW = 1, - ESCROW2 = 2 -} - -export interface IRewardInfo { - validatorId: number - delegationType: DelegationType -} - -export interface IDelegationInfo { - delegationId: bigint - delegationType: DelegationType -} diff --git a/src/core/interfaces/Delegator.ts b/src/core/interfaces/Delegator.ts deleted file mode 100644 index 3c511e7b..00000000 --- a/src/core/interfaces/Delegator.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @license - * SKALE portal - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * @file Delegator.ts - * @copyright SKALE Labs 2024-Present - */ - -import { type interfaces } from '@skalenetwork/metaport' - -export interface IDelegatorInfo { - balance: bigint - staked: bigint - rewards: bigint - forbiddenToDelegate: bigint - allowedToDelegate?: bigint - vested?: bigint - fullAmount?: bigint - unlocked?: bigint - address: interfaces.AddressType -} diff --git a/src/core/interfaces/SkaleContract.ts b/src/core/interfaces/SkaleContract.ts deleted file mode 100644 index 2d4267b1..00000000 --- a/src/core/interfaces/SkaleContract.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * SKALE portal - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * @file SkaleContract.ts - * @copyright SKALE Labs 2024-Present - */ - -import { type Contract } from 'ethers' - -export type SkaleContractName = - | 'delegationController' - | 'skaleToken' - | 'allocator' - | 'distributor' - | 'validatorService' - | 'grantsAllocator' - | 'tokenState' - -export type ISkaleContractsMap = { - [key in SkaleContractName]: Contract -} - -export type ContractType = 'delegation' | 'distributor' diff --git a/src/core/interfaces/Staking.ts b/src/core/interfaces/Staking.ts deleted file mode 100644 index 5521306e..00000000 --- a/src/core/interfaces/Staking.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @license - * SKALE portal - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * @file Staking.ts - * @copyright SKALE Labs 2023-Present - */ - -import { type DelegationType, type IDelegationsToValidator, type IDelegatorInfo } from '.' - -export type StakingInfoMap = { [key in DelegationType]: StakingInfo | null } - -export interface StakingInfo { - delegations: IDelegationsToValidator[] - info: IDelegatorInfo -} diff --git a/src/core/interfaces/Validator.ts b/src/core/interfaces/Validator.ts deleted file mode 100644 index d0c2e0b1..00000000 --- a/src/core/interfaces/Validator.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @license - * SKALE portal - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * @file Validator.ts - * @copyright SKALE Labs 2024-Present - */ - -import { type interfaces } from '@skalenetwork/metaport' - -export type IValidatorArray = [ - string, - interfaces.AddressType, - interfaces.AddressType, - string, - bigint, - bigint, - bigint, - boolean -] - -export interface IValidator { - name: string - validatorAddress: interfaces.AddressType - requestedAddress: interfaces.AddressType - description: string - feeRate: bigint - registrationTime: bigint - minimumDelegationAmount: bigint - acceptNewRequests: boolean - trusted: boolean - id: number - linkedNodes: number -} diff --git a/src/core/interfaces/index.ts b/src/core/interfaces/index.ts deleted file mode 100644 index 93cd7a30..00000000 --- a/src/core/interfaces/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * SKALE portal - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * @file validator.ts - * @copyright SKALE Labs 2024-Present - */ - -export * from './Validator' -export * from './Delegation' -export * from './Delegator' -export * from './SkaleContract' -export * from './Staking' diff --git a/src/core/meta.ts b/src/core/meta.ts index c89d8b25..8d225754 100644 --- a/src/core/meta.ts +++ b/src/core/meta.ts @@ -71,6 +71,11 @@ export const META_TAGS = { description: 'List of validators on SKALE Network', help: 'Explore validators that secure the SKALE Network and choose your preferred ones to stake SKL tokens with to earn rewards.' }, + validator: { + title: 'SKALE Portal - Validator', + description: 'Delegations and chain rewards management', + help: 'Manage your validator and delegations, review your delegations and chain rewards.' + }, onramp: { title: 'SKALE Portal - Onramp', description: 'Purchase crypto directly on SKALE Europa Hub using the Transak onramp.', diff --git a/src/core/paymaster.ts b/src/core/paymaster.ts index b629c199..63ee1db5 100644 --- a/src/core/paymaster.ts +++ b/src/core/paymaster.ts @@ -24,6 +24,7 @@ import { Contract, id, type InterfaceAbi } from 'ethers' import { type MetaportCore } from '@skalenetwork/metaport' import { type types } from '@/core' import PAYMASTER_INFO from '../data/paymaster' +import PAYMASTER_ABI from '../data/paymasterAbi.json' export interface PaymasterInfo { maxReplenishmentPeriod: bigint @@ -67,7 +68,7 @@ export function getPaymasterLaunchTs(skaleNetwork: types.SkaleNetwork): bigint { } export function getPaymasterAbi(): InterfaceAbi { - return PAYMASTER_INFO.abi + return PAYMASTER_ABI.abi } export function initPaymaster(mpc: MetaportCore): Contract { diff --git a/src/data/paymaster.ts b/src/data/paymaster.ts index a5f86b1a..62c686de 100644 --- a/src/data/paymaster.ts +++ b/src/data/paymaster.ts @@ -11,8 +11,8 @@ export default { launchTs: '0' }, legacy: { - chain: '', - address: '0x', + chain: 'adorable-quaint-bellatrix', + address: '0xb76A448071Ed77d22cAa1669567B5D28f5448d99', launchTs: '0' }, regression: { @@ -25,906 +25,5 @@ export default { address: '0x', launchTs: '0' } - }, - abi: [ - { - inputs: [ - { - internalType: 'address', - name: 'authority', - type: 'address' - } - ], - name: 'AccessManagedInvalidAuthority', - type: 'error' - }, - { - inputs: [ - { - internalType: 'address', - name: 'caller', - type: 'address' - }, - { - internalType: 'uint32', - name: 'delay', - type: 'uint32' - } - ], - name: 'AccessManagedRequiredDelay', - type: 'error' - }, - { - inputs: [ - { - internalType: 'address', - name: 'caller', - type: 'address' - } - ], - name: 'AccessManagedUnauthorized', - type: 'error' - }, - { - inputs: [], - name: 'AccessToEmptyHeap', - type: 'error' - }, - { - inputs: [], - name: 'AccessToEmptyPriorityQueue', - type: 'error' - }, - { - inputs: [], - name: 'CannotAddToThePast', - type: 'error' - }, - { - inputs: [], - name: 'CannotSetValueInThePast', - type: 'error' - }, - { - inputs: [], - name: 'ClearUnprocessed', - type: 'error' - }, - { - inputs: [], - name: 'ImportantDataRemoving', - type: 'error' - }, - { - inputs: [], - name: 'IncorrectTimeInterval', - type: 'error' - }, - { - inputs: [], - name: 'InvalidInitialization', - type: 'error' - }, - { - inputs: [], - name: 'NotInitializing', - type: 'error' - }, - { - inputs: [], - name: 'QueueEmpty', - type: 'error' - }, - { - inputs: [], - name: 'QueueFull', - type: 'error' - }, - { - inputs: [], - name: 'QueueOutOfBounds', - type: 'error' - }, - { - inputs: [], - name: 'ReplenishmentPeriodIsTooBig', - type: 'error' - }, - { - inputs: [], - name: 'RootDoesNotHaveParent', - type: 'error' - }, - { - inputs: [ - { - internalType: 'SchainHash', - name: 'hash', - type: 'bytes32' - } - ], - name: 'SchainAddingError', - type: 'error' - }, - { - inputs: [ - { - internalType: 'SchainHash', - name: 'hash', - type: 'bytes32' - } - ], - name: 'SchainDeletionError', - type: 'error' - }, - { - inputs: [ - { - internalType: 'SchainHash', - name: 'hash', - type: 'bytes32' - } - ], - name: 'SchainNotFound', - type: 'error' - }, - { - inputs: [], - name: 'SchainPriceIsNotSet', - type: 'error' - }, - { - inputs: [], - name: 'SkaleTokenIsNotSet', - type: 'error' - }, - { - inputs: [], - name: 'SklPriceIsNotSet', - type: 'error' - }, - { - inputs: [], - name: 'SklPriceIsOutdated', - type: 'error' - }, - { - inputs: [], - name: 'TimeIntervalIsAlreadyProcessed', - type: 'error' - }, - { - inputs: [], - name: 'TimeIntervalIsNotProcessed', - type: 'error' - }, - { - inputs: [], - name: 'TimestampIsOutOfValues', - type: 'error' - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address' - }, - { - internalType: 'uint256', - name: 'required', - type: 'uint256' - }, - { - internalType: 'uint256', - name: 'allowed', - type: 'uint256' - } - ], - name: 'TooSmallAllowance', - type: 'error' - }, - { - inputs: [], - name: 'TransferFailure', - type: 'error' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'id', - type: 'uint256' - } - ], - name: 'ValidatorAddingError', - type: 'error' - }, - { - inputs: [ - { - internalType: 'address', - name: 'validatorAddress', - type: 'address' - } - ], - name: 'ValidatorAddressAlreadyExists', - type: 'error' - }, - { - inputs: [ - { - internalType: 'address', - name: 'validatorAddress', - type: 'address' - } - ], - name: 'ValidatorAddressNotFound', - type: 'error' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'id', - type: 'uint256' - } - ], - name: 'ValidatorDeletionError', - type: 'error' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'id', - type: 'uint256' - }, - { - internalType: 'Timestamp', - name: 'when', - type: 'uint256' - } - ], - name: 'ValidatorHasBeenRemoved', - type: 'error' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'id', - type: 'uint256' - } - ], - name: 'ValidatorNotFound', - type: 'error' - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'address', - name: 'authority', - type: 'address' - } - ], - name: 'AuthorityUpdated', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'uint64', - name: 'version', - type: 'uint64' - } - ], - name: 'Initialized', - type: 'event' - }, - { - inputs: [ - { - internalType: 'string', - name: 'name', - type: 'string' - } - ], - name: 'addSchain', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'id', - type: 'uint256' - }, - { - internalType: 'address', - name: 'validatorAddress', - type: 'address' - } - ], - name: 'addValidator', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [], - name: 'allowedSklPriceLag', - outputs: [ - { - internalType: 'Seconds', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'authority', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'address', - name: 'to', - type: 'address' - } - ], - name: 'claim', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'validatorId', - type: 'uint256' - }, - { - internalType: 'address', - name: 'to', - type: 'address' - } - ], - name: 'claimFor', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'Timestamp', - name: 'before', - type: 'uint256' - } - ], - name: 'clearHistory', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'Paymaster.DebtId', - name: '', - type: 'uint256' - } - ], - name: 'debts', - outputs: [ - { - internalType: 'Timestamp', - name: 'from', - type: 'uint256' - }, - { - internalType: 'Timestamp', - name: 'to', - type: 'uint256' - }, - { - internalType: 'SKL', - name: 'amount', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'debtsBegin', - outputs: [ - { - internalType: 'Paymaster.DebtId', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'debtsEnd', - outputs: [ - { - internalType: 'Paymaster.DebtId', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'validatorId', - type: 'uint256' - } - ], - name: 'getActiveNodesNumber', - outputs: [ - { - internalType: 'uint256', - name: 'number', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'validatorId', - type: 'uint256' - } - ], - name: 'getNodesNumber', - outputs: [ - { - internalType: 'uint256', - name: 'number', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'getRewardAmount', - outputs: [ - { - internalType: 'SKL', - name: 'reward', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'validatorId', - type: 'uint256' - } - ], - name: 'getRewardAmountFor', - outputs: [ - { - internalType: 'SKL', - name: 'reward', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'SchainHash', - name: 'schainHash', - type: 'bytes32' - } - ], - name: 'getSchainExpirationTimestamp', - outputs: [ - { - internalType: 'Timestamp', - name: 'expiration', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'getSchainsNames', - outputs: [ - { - internalType: 'string[]', - name: 'names', - type: 'string[]' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'getSchainsNumber', - outputs: [ - { - internalType: 'uint256', - name: 'number', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'getValidatorsNumber', - outputs: [ - { - internalType: 'uint256', - name: 'number', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'address', - name: 'initialAuthority', - type: 'address' - } - ], - name: 'initialize', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [], - name: 'isConsumingScheduledOp', - outputs: [ - { - internalType: 'bytes4', - name: '', - type: 'bytes4' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'maxReplenishmentPeriod', - outputs: [ - { - internalType: 'Months', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'oneSklPrice', - outputs: [ - { - internalType: 'USD', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'effectiveTimestamp', - outputs: [ - { - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'SchainHash', - name: 'schainHash', - type: 'bytes32' - }, - { - internalType: 'Months', - name: 'duration', - type: 'uint256' - } - ], - name: 'pay', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'SchainHash', - name: 'schainHash', - type: 'bytes32' - } - ], - name: 'removeSchain', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'id', - type: 'uint256' - } - ], - name: 'removeValidator', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [], - name: 'schainPricePerMonth', - outputs: [ - { - internalType: 'USD', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'SchainHash', - name: '', - type: 'bytes32' - } - ], - name: 'schains', - outputs: [ - { - internalType: 'SchainHash', - name: 'hash', - type: 'bytes32' - }, - { - internalType: 'string', - name: 'name', - type: 'string' - }, - { - internalType: 'Timestamp', - name: 'paidUntil', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'validatorId', - type: 'uint256' - }, - { - internalType: 'uint256', - name: 'amount', - type: 'uint256' - } - ], - name: 'setActiveNodes', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'Seconds', - name: 'lagSeconds', - type: 'uint256' - } - ], - name: 'setAllowedSklPriceLag', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'address', - name: 'newAuthority', - type: 'address' - } - ], - name: 'setAuthority', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'Months', - name: 'months', - type: 'uint256' - } - ], - name: 'setMaxReplenishmentPeriod', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'ValidatorId', - name: 'validatorId', - type: 'uint256' - }, - { - internalType: 'uint256', - name: 'amount', - type: 'uint256' - } - ], - name: 'setNodesAmount', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'USD', - name: 'price', - type: 'uint256' - } - ], - name: 'setSchainPrice', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'contract IERC20', - name: 'token', - type: 'address' - } - ], - name: 'setSkaleToken', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'USD', - name: 'price', - type: 'uint256' - } - ], - name: 'setSklPrice', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'string', - name: 'newVersion', - type: 'string' - } - ], - name: 'setVersion', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [], - name: 'skaleToken', - outputs: [ - { - internalType: 'contract IERC20', - name: '', - type: 'address' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'sklPriceTimestamp', - outputs: [ - { - internalType: 'Timestamp', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'version', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string' - } - ], - stateMutability: 'view', - type: 'function' - } - ] + } } diff --git a/src/data/paymasterAbi.json b/src/data/paymasterAbi.json new file mode 100644 index 00000000..c177cd36 --- /dev/null +++ b/src/data/paymasterAbi.json @@ -0,0 +1 @@ +{"abi":[{"type":"error","name":"AccessManagedInvalidAuthority","inputs":[{"type":"address","name":"authority"}]},{"type":"error","name":"AccessManagedRequiredDelay","inputs":[{"type":"address","name":"caller"},{"type":"uint32","name":"delay"}]},{"type":"error","name":"AccessManagedUnauthorized","inputs":[{"type":"address","name":"caller"}]},{"type":"error","name":"AccessToEmptyHeap","inputs":[]},{"type":"error","name":"AccessToEmptyPriorityQueue","inputs":[]},{"type":"error","name":"CannotAddToThePast","inputs":[]},{"type":"error","name":"CannotSetValueInThePast","inputs":[]},{"type":"error","name":"ClearUnprocessed","inputs":[]},{"type":"error","name":"ImportantDataRemoving","inputs":[]},{"type":"error","name":"IncorrectActiveNodesAmount","inputs":[{"type":"uint256","name":"amount"},{"type":"uint256","name":"totalAmount"}]},{"type":"error","name":"IncorrectTimeInterval","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"QueueEmpty","inputs":[]},{"type":"error","name":"QueueFull","inputs":[]},{"type":"error","name":"QueueOutOfBounds","inputs":[]},{"type":"error","name":"ReplenishmentPeriodIsTooBig","inputs":[]},{"type":"error","name":"ReplenishmentPeriodIsTooSmall","inputs":[]},{"type":"error","name":"RootDoesNotHaveParent","inputs":[]},{"type":"error","name":"SchainAddingError","inputs":[{"type":"bytes32","name":"hash"}]},{"type":"error","name":"SchainDeletionError","inputs":[{"type":"bytes32","name":"hash"}]},{"type":"error","name":"SchainNotFound","inputs":[{"type":"bytes32","name":"hash"}]},{"type":"error","name":"SchainPriceIsNotSet","inputs":[]},{"type":"error","name":"SkaleTokenIsNotSet","inputs":[]},{"type":"error","name":"SklPriceIsNotSet","inputs":[]},{"type":"error","name":"SklPriceIsOutdated","inputs":[]},{"type":"error","name":"TimeIntervalIsAlreadyProcessed","inputs":[]},{"type":"error","name":"TimeIntervalIsNotProcessed","inputs":[]},{"type":"error","name":"TimestampIsOutOfValues","inputs":[]},{"type":"error","name":"TooSmallAllowance","inputs":[{"type":"address","name":"spender"},{"type":"uint256","name":"required"},{"type":"uint256","name":"allowed"}]},{"type":"error","name":"TransferFailure","inputs":[]},{"type":"error","name":"ValidatorAddingError","inputs":[{"type":"uint256","name":"id"}]},{"type":"error","name":"ValidatorAddressAlreadyExists","inputs":[{"type":"address","name":"validatorAddress"}]},{"type":"error","name":"ValidatorAddressNotFound","inputs":[{"type":"address","name":"validatorAddress"}]},{"type":"error","name":"ValidatorDeletionError","inputs":[{"type":"uint256","name":"id"}]},{"type":"error","name":"ValidatorHasBeenRemoved","inputs":[{"type":"uint256","name":"id"},{"type":"uint256","name":"when"}]},{"type":"error","name":"ValidatorNotFound","inputs":[{"type":"uint256","name":"id"}]},{"type":"event","anonymous":false,"name":"ActiveNodesNumberChanged","inputs":[{"type":"uint256","name":"validator","indexed":false},{"type":"uint256","name":"oldNumber","indexed":false},{"type":"uint256","name":"newNumber","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"AddedToTimeline","inputs":[{"type":"uint256","name":"from","indexed":false},{"type":"uint256","name":"to","indexed":false},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","anonymous":false,"name":"AuthorityUpdated","inputs":[{"type":"address","name":"authority","indexed":false}]},{"type":"event","anonymous":false,"name":"Cleared","inputs":[]},{"type":"event","anonymous":false,"name":"Cleared","inputs":[{"type":"uint256","name":"until","indexed":false}]},{"type":"event","anonymous":false,"name":"ClearedUntil","inputs":[{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"HeapValueAdded","inputs":[{"type":"uint256","name":"value","indexed":true}]},{"type":"event","anonymous":false,"name":"HeapValueRemoved","inputs":[{"type":"uint256","name":"value","indexed":true}]},{"type":"event","anonymous":false,"name":"HistoryCleaned","inputs":[{"type":"uint256","name":"until","indexed":false}]},{"type":"event","anonymous":false,"name":"Initialized","inputs":[{"type":"uint64","name":"version","indexed":false}]},{"type":"event","anonymous":false,"name":"MaxReplenishmentPeriodChanged","inputs":[{"type":"uint256","name":"valueInMonths","indexed":false}]},{"type":"event","anonymous":false,"name":"PriorityQueueValueAdded","inputs":[{"type":"uint256","name":"priority","indexed":false},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","anonymous":false,"name":"PriorityQueueValueRemoved","inputs":[{"type":"uint256","name":"priority","indexed":false},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","anonymous":false,"name":"ProcessedUntil","inputs":[{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"RewardClaimed","inputs":[{"type":"uint256","name":"validator","indexed":false},{"type":"address","name":"receiver","indexed":false},{"type":"uint256","name":"amount","indexed":false},{"type":"uint256","name":"until","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SchainAdded","inputs":[{"type":"string","name":"name","indexed":false},{"type":"bytes32","name":"hash","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SchainPaid","inputs":[{"type":"bytes32","name":"hash","indexed":false},{"type":"uint256","name":"period","indexed":false},{"type":"uint256","name":"amount","indexed":false},{"type":"uint256","name":"newLifetime","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SchainPriceSet","inputs":[{"type":"uint256","name":"priceInUsd","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SchainRemoved","inputs":[{"type":"string","name":"name","indexed":false},{"type":"bytes32","name":"hash","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SequenceValueAdded","inputs":[{"type":"uint256","name":"timestamp","indexed":false},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","anonymous":false,"name":"SkaleTokenSet","inputs":[{"type":"address","name":"tokenAddress","indexed":false}]},{"type":"event","anonymous":false,"name":"SklPriceLagSet","inputs":[{"type":"uint256","name":"lagInSeconds","indexed":false}]},{"type":"event","anonymous":false,"name":"SklPriceSet","inputs":[{"type":"uint256","name":"priceInUsd","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"ValidatorAdded","inputs":[{"type":"uint256","name":"id","indexed":false},{"type":"address","name":"validatorAddress","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"ValidatorMarkedAsRemoved","inputs":[{"type":"uint256","name":"id","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"ValidatorRemoved","inputs":[{"type":"uint256","name":"id","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"VersionSet","inputs":[{"type":"string","name":"newVersion","indexed":false}]},{"type":"function","name":"addSchain","constant":false,"payable":false,"inputs":[{"type":"string","name":"name"}],"outputs":[]},{"type":"function","name":"addValidator","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"id"},{"type":"address","name":"validatorAddress"}],"outputs":[]},{"type":"function","name":"allowedSklPriceLag","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"authority","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"address","name":""}]},{"type":"function","name":"claim","constant":false,"payable":false,"inputs":[{"type":"address","name":"to"}],"outputs":[]},{"type":"function","name":"claimFor","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"validatorId"},{"type":"address","name":"to"}],"outputs":[]},{"type":"function","name":"clearHistory","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"before"}],"outputs":[]},{"type":"function","name":"debts","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"debtId"}],"outputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"},{"type":"uint256","name":"amount"}]},{"type":"function","name":"debtsBegin","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"debtsEnd","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"getActiveNodesNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"validatorId"}],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getHistoricalActiveNodesNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"validatorId"},{"type":"uint256","name":"when"}],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getHistoricalTotalActiveNodesNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"when"}],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getNodesNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"validatorId"}],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getRewardAmount","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"validatorId"}],"outputs":[{"type":"uint256","name":"reward"}]},{"type":"function","name":"getSchainExpirationTimestamp","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"bytes32","name":"schainHash"}],"outputs":[{"type":"uint256","name":"expiration"}]},{"type":"function","name":"getSchainsNames","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"string[]","name":"names"}]},{"type":"function","name":"getSchainsNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getTotalReward","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"}],"outputs":[{"type":"uint256","name":"reward"}]},{"type":"function","name":"getValidatorsNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"initialize","constant":false,"payable":false,"inputs":[{"type":"address","name":"initialAuthority"}],"outputs":[]},{"type":"function","name":"isConsumingScheduledOp","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"bytes4","name":""}]},{"type":"function","name":"maxReplenishmentPeriod","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"oneSklPrice","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"pay","constant":false,"payable":false,"inputs":[{"type":"bytes32","name":"schainHash"},{"type":"uint256","name":"duration"}],"outputs":[]},{"type":"function","name":"removeSchain","constant":false,"payable":false,"inputs":[{"type":"bytes32","name":"schainHash"}],"outputs":[]},{"type":"function","name":"removeValidator","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"id"}],"outputs":[]},{"type":"function","name":"schainPricePerMonth","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"schains","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"bytes32","name":"schainHash"}],"outputs":[{"type":"bytes32","name":"hash"},{"type":"string","name":"name"},{"type":"uint256","name":"paidUntil"}]},{"type":"function","name":"setActiveNodes","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"validatorId"},{"type":"uint256","name":"amount"}],"outputs":[]},{"type":"function","name":"setAllowedSklPriceLag","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"lagSeconds"}],"outputs":[]},{"type":"function","name":"setAuthority","constant":false,"payable":false,"inputs":[{"type":"address","name":"newAuthority"}],"outputs":[]},{"type":"function","name":"setMaxReplenishmentPeriod","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"months"}],"outputs":[]},{"type":"function","name":"setNodesAmount","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"validatorId"},{"type":"uint256","name":"amount"}],"outputs":[]},{"type":"function","name":"setSchainPrice","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"price"}],"outputs":[]},{"type":"function","name":"setSkaleToken","constant":false,"payable":false,"inputs":[{"type":"address","name":"token"}],"outputs":[]},{"type":"function","name":"setSklPrice","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"price"}],"outputs":[]},{"type":"function","name":"setValidatorAddress","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"id"},{"type":"address","name":"validatorAddress"}],"outputs":[]},{"type":"function","name":"setVersion","constant":false,"payable":false,"inputs":[{"type":"string","name":"newVersion"}],"outputs":[]},{"type":"function","name":"skaleToken","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"address","name":""}]},{"type":"function","name":"sklPriceTimestamp","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"version","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"string","name":""}]}],"PaymasterAccessManager":[{"type":"error","name":"AccessManagerAlreadyScheduled","inputs":[{"type":"bytes32","name":"operationId"}]},{"type":"error","name":"AccessManagerBadConfirmation","inputs":[]},{"type":"error","name":"AccessManagerExpired","inputs":[{"type":"bytes32","name":"operationId"}]},{"type":"error","name":"AccessManagerInvalidInitialAdmin","inputs":[{"type":"address","name":"initialAdmin"}]},{"type":"error","name":"AccessManagerLockedAccount","inputs":[{"type":"address","name":"account"}]},{"type":"error","name":"AccessManagerLockedRole","inputs":[{"type":"uint64","name":"roleId"}]},{"type":"error","name":"AccessManagerNotReady","inputs":[{"type":"bytes32","name":"operationId"}]},{"type":"error","name":"AccessManagerNotScheduled","inputs":[{"type":"bytes32","name":"operationId"}]},{"type":"error","name":"AccessManagerUnauthorizedAccount","inputs":[{"type":"address","name":"msgsender"},{"type":"uint64","name":"roleId"}]},{"type":"error","name":"AccessManagerUnauthorizedCall","inputs":[{"type":"address","name":"caller"},{"type":"address","name":"target"},{"type":"bytes4","name":"selector"}]},{"type":"error","name":"AccessManagerUnauthorizedCancel","inputs":[{"type":"address","name":"msgsender"},{"type":"address","name":"caller"},{"type":"address","name":"target"},{"type":"bytes4","name":"selector"}]},{"type":"error","name":"AccessManagerUnauthorizedConsume","inputs":[{"type":"address","name":"target"}]},{"type":"error","name":"AddressEmptyCode","inputs":[{"type":"address","name":"target"}]},{"type":"error","name":"AddressInsufficientBalance","inputs":[{"type":"address","name":"account"}]},{"type":"error","name":"FailedInnerCall","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"SafeCastOverflowedUintDowncast","inputs":[{"type":"uint8","name":"bits"},{"type":"uint256","name":"value"}]},{"type":"event","anonymous":false,"name":"Initialized","inputs":[{"type":"uint64","name":"version","indexed":false}]},{"type":"event","anonymous":false,"name":"OperationCanceled","inputs":[{"type":"bytes32","name":"operationId","indexed":true},{"type":"uint32","name":"nonce","indexed":true}]},{"type":"event","anonymous":false,"name":"OperationExecuted","inputs":[{"type":"bytes32","name":"operationId","indexed":true},{"type":"uint32","name":"nonce","indexed":true}]},{"type":"event","anonymous":false,"name":"OperationScheduled","inputs":[{"type":"bytes32","name":"operationId","indexed":true},{"type":"uint32","name":"nonce","indexed":true},{"type":"uint48","name":"schedule","indexed":false},{"type":"address","name":"caller","indexed":false},{"type":"address","name":"target","indexed":false},{"type":"bytes","name":"data","indexed":false}]},{"type":"event","anonymous":false,"name":"RoleAdminChanged","inputs":[{"type":"uint64","name":"roleId","indexed":true},{"type":"uint64","name":"admin","indexed":true}]},{"type":"event","anonymous":false,"name":"RoleGrantDelayChanged","inputs":[{"type":"uint64","name":"roleId","indexed":true},{"type":"uint32","name":"delay","indexed":false},{"type":"uint48","name":"since","indexed":false}]},{"type":"event","anonymous":false,"name":"RoleGranted","inputs":[{"type":"uint64","name":"roleId","indexed":true},{"type":"address","name":"account","indexed":true},{"type":"uint32","name":"delay","indexed":false},{"type":"uint48","name":"since","indexed":false},{"type":"bool","name":"newMember","indexed":false}]},{"type":"event","anonymous":false,"name":"RoleGuardianChanged","inputs":[{"type":"uint64","name":"roleId","indexed":true},{"type":"uint64","name":"guardian","indexed":true}]},{"type":"event","anonymous":false,"name":"RoleLabel","inputs":[{"type":"uint64","name":"roleId","indexed":true},{"type":"string","name":"label","indexed":false}]},{"type":"event","anonymous":false,"name":"RoleRevoked","inputs":[{"type":"uint64","name":"roleId","indexed":true},{"type":"address","name":"account","indexed":true}]},{"type":"event","anonymous":false,"name":"TargetAdminDelayUpdated","inputs":[{"type":"address","name":"target","indexed":true},{"type":"uint32","name":"delay","indexed":false},{"type":"uint48","name":"since","indexed":false}]},{"type":"event","anonymous":false,"name":"TargetClosed","inputs":[{"type":"address","name":"target","indexed":true},{"type":"bool","name":"closed","indexed":false}]},{"type":"event","anonymous":false,"name":"TargetFunctionRoleUpdated","inputs":[{"type":"address","name":"target","indexed":true},{"type":"bytes4","name":"selector","indexed":false},{"type":"uint64","name":"roleId","indexed":true}]},{"type":"function","name":"ADMIN_ROLE","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint64","name":""}]},{"type":"function","name":"PRICE_SETTER_ROLE","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint64","name":""}]},{"type":"function","name":"PUBLIC_ROLE","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint64","name":""}]},{"type":"function","name":"canCall","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"caller"},{"type":"address","name":"target"},{"type":"bytes4","name":"selector"}],"outputs":[{"type":"bool","name":"immediate"},{"type":"uint32","name":"delay"}]},{"type":"function","name":"cancel","constant":false,"payable":false,"inputs":[{"type":"address","name":"caller"},{"type":"address","name":"target"},{"type":"bytes","name":"data"}],"outputs":[{"type":"uint32","name":""}]},{"type":"function","name":"consumeScheduledOp","constant":false,"payable":false,"inputs":[{"type":"address","name":"caller"},{"type":"bytes","name":"data"}],"outputs":[]},{"type":"function","name":"execute","constant":false,"stateMutability":"payable","payable":true,"inputs":[{"type":"address","name":"target"},{"type":"bytes","name":"data"}],"outputs":[{"type":"uint32","name":""}]},{"type":"function","name":"expiration","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint32","name":""}]},{"type":"function","name":"getAccess","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"address","name":"account"}],"outputs":[{"type":"uint48","name":"since"},{"type":"uint32","name":"currentDelay"},{"type":"uint32","name":"pendingDelay"},{"type":"uint48","name":"effect"}]},{"type":"function","name":"getNonce","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"bytes32","name":"id"}],"outputs":[{"type":"uint32","name":""}]},{"type":"function","name":"getRoleAdmin","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint64","name":"roleId"}],"outputs":[{"type":"uint64","name":""}]},{"type":"function","name":"getRoleGrantDelay","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint64","name":"roleId"}],"outputs":[{"type":"uint32","name":""}]},{"type":"function","name":"getRoleGuardian","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint64","name":"roleId"}],"outputs":[{"type":"uint64","name":""}]},{"type":"function","name":"getSchedule","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"bytes32","name":"id"}],"outputs":[{"type":"uint48","name":""}]},{"type":"function","name":"getTargetAdminDelay","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"target"}],"outputs":[{"type":"uint32","name":""}]},{"type":"function","name":"getTargetFunctionRole","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"target"},{"type":"bytes4","name":"selector"}],"outputs":[{"type":"uint64","name":""}]},{"type":"function","name":"grantRole","constant":false,"payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"address","name":"account"},{"type":"uint32","name":"executionDelay"}],"outputs":[]},{"type":"function","name":"hasRole","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"address","name":"account"}],"outputs":[{"type":"bool","name":"isMember"},{"type":"uint32","name":"executionDelay"}]},{"type":"function","name":"hashOperation","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"caller"},{"type":"address","name":"target"},{"type":"bytes","name":"data"}],"outputs":[{"type":"bytes32","name":""}]},{"type":"function","name":"initialize","constant":false,"payable":false,"inputs":[{"type":"address","name":"initialAdmin"}],"outputs":[]},{"type":"function","name":"isTargetClosed","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"target"}],"outputs":[{"type":"bool","name":""}]},{"type":"function","name":"labelRole","constant":false,"payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"string","name":"label"}],"outputs":[]},{"type":"function","name":"minSetback","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint32","name":""}]},{"type":"function","name":"multicall","constant":false,"payable":false,"inputs":[{"type":"bytes[]","name":"data"}],"outputs":[{"type":"bytes[]","name":"results"}]},{"type":"function","name":"renounceRole","constant":false,"payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"address","name":"callerConfirmation"}],"outputs":[]},{"type":"function","name":"revokeRole","constant":false,"payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"address","name":"account"}],"outputs":[]},{"type":"function","name":"schedule","constant":false,"payable":false,"inputs":[{"type":"address","name":"target"},{"type":"bytes","name":"data"},{"type":"uint48","name":"when"}],"outputs":[{"type":"bytes32","name":"operationId"},{"type":"uint32","name":"nonce"}]},{"type":"function","name":"setGrantDelay","constant":false,"payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"uint32","name":"newDelay"}],"outputs":[]},{"type":"function","name":"setRoleAdmin","constant":false,"payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"uint64","name":"admin"}],"outputs":[]},{"type":"function","name":"setRoleGuardian","constant":false,"payable":false,"inputs":[{"type":"uint64","name":"roleId"},{"type":"uint64","name":"guardian"}],"outputs":[]},{"type":"function","name":"setTargetAdminDelay","constant":false,"payable":false,"inputs":[{"type":"address","name":"target"},{"type":"uint32","name":"newDelay"}],"outputs":[]},{"type":"function","name":"setTargetClosed","constant":false,"payable":false,"inputs":[{"type":"address","name":"target"},{"type":"bool","name":"closed"}],"outputs":[]},{"type":"function","name":"setTargetFunctionRole","constant":false,"payable":false,"inputs":[{"type":"address","name":"target"},{"type":"bytes4[]","name":"selectors"},{"type":"uint64","name":"roleId"}],"outputs":[]},{"type":"function","name":"updateAuthority","constant":false,"payable":false,"inputs":[{"type":"address","name":"target"},{"type":"address","name":"newAuthority"}],"outputs":[]}],"FastForwardPaymaster":[{"type":"error","name":"AccessManagedInvalidAuthority","inputs":[{"type":"address","name":"authority"}]},{"type":"error","name":"AccessManagedRequiredDelay","inputs":[{"type":"address","name":"caller"},{"type":"uint32","name":"delay"}]},{"type":"error","name":"AccessManagedUnauthorized","inputs":[{"type":"address","name":"caller"}]},{"type":"error","name":"AccessToEmptyHeap","inputs":[]},{"type":"error","name":"AccessToEmptyPriorityQueue","inputs":[]},{"type":"error","name":"CannotAddToThePast","inputs":[]},{"type":"error","name":"CannotSetValueInThePast","inputs":[]},{"type":"error","name":"ClearUnprocessed","inputs":[]},{"type":"error","name":"ImportantDataRemoving","inputs":[]},{"type":"error","name":"IncorrectActiveNodesAmount","inputs":[{"type":"uint256","name":"amount"},{"type":"uint256","name":"totalAmount"}]},{"type":"error","name":"IncorrectTimeInterval","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"QueueEmpty","inputs":[]},{"type":"error","name":"QueueFull","inputs":[]},{"type":"error","name":"QueueOutOfBounds","inputs":[]},{"type":"error","name":"ReplenishmentPeriodIsTooBig","inputs":[]},{"type":"error","name":"ReplenishmentPeriodIsTooSmall","inputs":[]},{"type":"error","name":"RootDoesNotHaveParent","inputs":[]},{"type":"error","name":"SchainAddingError","inputs":[{"type":"bytes32","name":"hash"}]},{"type":"error","name":"SchainDeletionError","inputs":[{"type":"bytes32","name":"hash"}]},{"type":"error","name":"SchainNotFound","inputs":[{"type":"bytes32","name":"hash"}]},{"type":"error","name":"SchainPriceIsNotSet","inputs":[]},{"type":"error","name":"SkaleTokenIsNotSet","inputs":[]},{"type":"error","name":"SklPriceIsNotSet","inputs":[]},{"type":"error","name":"SklPriceIsOutdated","inputs":[]},{"type":"error","name":"TimeIntervalIsAlreadyProcessed","inputs":[]},{"type":"error","name":"TimeIntervalIsNotProcessed","inputs":[]},{"type":"error","name":"TimestampIsOutOfValues","inputs":[]},{"type":"error","name":"TooSmallAllowance","inputs":[{"type":"address","name":"spender"},{"type":"uint256","name":"required"},{"type":"uint256","name":"allowed"}]},{"type":"error","name":"TransferFailure","inputs":[]},{"type":"error","name":"ValidatorAddingError","inputs":[{"type":"uint256","name":"id"}]},{"type":"error","name":"ValidatorAddressAlreadyExists","inputs":[{"type":"address","name":"validatorAddress"}]},{"type":"error","name":"ValidatorAddressNotFound","inputs":[{"type":"address","name":"validatorAddress"}]},{"type":"error","name":"ValidatorDeletionError","inputs":[{"type":"uint256","name":"id"}]},{"type":"error","name":"ValidatorHasBeenRemoved","inputs":[{"type":"uint256","name":"id"},{"type":"uint256","name":"when"}]},{"type":"error","name":"ValidatorNotFound","inputs":[{"type":"uint256","name":"id"}]},{"type":"event","anonymous":false,"name":"ActiveNodesNumberChanged","inputs":[{"type":"uint256","name":"validator","indexed":false},{"type":"uint256","name":"oldNumber","indexed":false},{"type":"uint256","name":"newNumber","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"AddedToTimeline","inputs":[{"type":"uint256","name":"from","indexed":false},{"type":"uint256","name":"to","indexed":false},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","anonymous":false,"name":"AuthorityUpdated","inputs":[{"type":"address","name":"authority","indexed":false}]},{"type":"event","anonymous":false,"name":"Cleared","inputs":[]},{"type":"event","anonymous":false,"name":"Cleared","inputs":[{"type":"uint256","name":"until","indexed":false}]},{"type":"event","anonymous":false,"name":"ClearedUntil","inputs":[{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"HeapValueAdded","inputs":[{"type":"uint256","name":"value","indexed":true}]},{"type":"event","anonymous":false,"name":"HeapValueRemoved","inputs":[{"type":"uint256","name":"value","indexed":true}]},{"type":"event","anonymous":false,"name":"HistoryCleaned","inputs":[{"type":"uint256","name":"until","indexed":false}]},{"type":"event","anonymous":false,"name":"Initialized","inputs":[{"type":"uint64","name":"version","indexed":false}]},{"type":"event","anonymous":false,"name":"MaxReplenishmentPeriodChanged","inputs":[{"type":"uint256","name":"valueInMonths","indexed":false}]},{"type":"event","anonymous":false,"name":"PriorityQueueValueAdded","inputs":[{"type":"uint256","name":"priority","indexed":false},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","anonymous":false,"name":"PriorityQueueValueRemoved","inputs":[{"type":"uint256","name":"priority","indexed":false},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","anonymous":false,"name":"ProcessedUntil","inputs":[{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"RewardClaimed","inputs":[{"type":"uint256","name":"validator","indexed":false},{"type":"address","name":"receiver","indexed":false},{"type":"uint256","name":"amount","indexed":false},{"type":"uint256","name":"until","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SchainAdded","inputs":[{"type":"string","name":"name","indexed":false},{"type":"bytes32","name":"hash","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SchainPaid","inputs":[{"type":"bytes32","name":"hash","indexed":false},{"type":"uint256","name":"period","indexed":false},{"type":"uint256","name":"amount","indexed":false},{"type":"uint256","name":"newLifetime","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SchainPriceSet","inputs":[{"type":"uint256","name":"priceInUsd","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SchainRemoved","inputs":[{"type":"string","name":"name","indexed":false},{"type":"bytes32","name":"hash","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"SequenceValueAdded","inputs":[{"type":"uint256","name":"timestamp","indexed":false},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","anonymous":false,"name":"SkaleTokenSet","inputs":[{"type":"address","name":"tokenAddress","indexed":false}]},{"type":"event","anonymous":false,"name":"SklPriceLagSet","inputs":[{"type":"uint256","name":"lagInSeconds","indexed":false}]},{"type":"event","anonymous":false,"name":"SklPriceSet","inputs":[{"type":"uint256","name":"priceInUsd","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"ValidatorAdded","inputs":[{"type":"uint256","name":"id","indexed":false},{"type":"address","name":"validatorAddress","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"ValidatorMarkedAsRemoved","inputs":[{"type":"uint256","name":"id","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"ValidatorRemoved","inputs":[{"type":"uint256","name":"id","indexed":false},{"type":"uint256","name":"timestamp","indexed":false}]},{"type":"event","anonymous":false,"name":"VersionSet","inputs":[{"type":"string","name":"newVersion","indexed":false}]},{"type":"function","name":"addSchain","constant":false,"payable":false,"inputs":[{"type":"string","name":"name"}],"outputs":[]},{"type":"function","name":"addValidator","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"id"},{"type":"address","name":"validatorAddress"}],"outputs":[]},{"type":"function","name":"allowedSklPriceLag","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"authority","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"address","name":""}]},{"type":"function","name":"checkPoint","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":"realTime"},{"type":"uint256","name":"effectiveTime"}]},{"type":"function","name":"claim","constant":false,"payable":false,"inputs":[{"type":"address","name":"to"}],"outputs":[]},{"type":"function","name":"claimFor","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"validatorId"},{"type":"address","name":"to"}],"outputs":[]},{"type":"function","name":"clearHistory","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"before"}],"outputs":[]},{"type":"function","name":"debts","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"debtId"}],"outputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"},{"type":"uint256","name":"amount"}]},{"type":"function","name":"debtsBegin","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"debtsEnd","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"effectiveTimestamp","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":"timestamp"}]},{"type":"function","name":"getActiveNodesNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"validatorId"}],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getHistoricalActiveNodesNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"validatorId"},{"type":"uint256","name":"when"}],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getHistoricalTotalActiveNodesNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"when"}],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getNodesNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"validatorId"}],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getRewardAmount","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"validatorId"}],"outputs":[{"type":"uint256","name":"reward"}]},{"type":"function","name":"getSchainExpirationTimestamp","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"bytes32","name":"schainHash"}],"outputs":[{"type":"uint256","name":"expiration"}]},{"type":"function","name":"getSchainsNames","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"string[]","name":"names"}]},{"type":"function","name":"getSchainsNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"getTotalReward","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"}],"outputs":[{"type":"uint256","name":"reward"}]},{"type":"function","name":"getValidatorsNumber","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":"number"}]},{"type":"function","name":"initialize","constant":false,"payable":false,"inputs":[{"type":"address","name":"initialAuthority"}],"outputs":[]},{"type":"function","name":"isConsumingScheduledOp","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"bytes4","name":""}]},{"type":"function","name":"maxReplenishmentPeriod","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"oneSklPrice","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"pay","constant":false,"payable":false,"inputs":[{"type":"bytes32","name":"schainHash"},{"type":"uint256","name":"duration"}],"outputs":[]},{"type":"function","name":"removeSchain","constant":false,"payable":false,"inputs":[{"type":"bytes32","name":"schainHash"}],"outputs":[]},{"type":"function","name":"removeValidator","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"id"}],"outputs":[]},{"type":"function","name":"schainPricePerMonth","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"schains","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"bytes32","name":"schainHash"}],"outputs":[{"type":"bytes32","name":"hash"},{"type":"string","name":"name"},{"type":"uint256","name":"paidUntil"}]},{"type":"function","name":"setActiveNodes","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"validatorId"},{"type":"uint256","name":"amount"}],"outputs":[]},{"type":"function","name":"setAllowedSklPriceLag","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"lagSeconds"}],"outputs":[]},{"type":"function","name":"setAuthority","constant":false,"payable":false,"inputs":[{"type":"address","name":"newAuthority"}],"outputs":[]},{"type":"function","name":"setMaxReplenishmentPeriod","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"months"}],"outputs":[]},{"type":"function","name":"setNodesAmount","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"validatorId"},{"type":"uint256","name":"amount"}],"outputs":[]},{"type":"function","name":"setSchainPrice","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"price"}],"outputs":[]},{"type":"function","name":"setSkaleToken","constant":false,"payable":false,"inputs":[{"type":"address","name":"token"}],"outputs":[]},{"type":"function","name":"setSklPrice","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"price"}],"outputs":[]},{"type":"function","name":"setTimeMultiplier","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"multiplier"}],"outputs":[]},{"type":"function","name":"setValidatorAddress","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"id"},{"type":"address","name":"validatorAddress"}],"outputs":[]},{"type":"function","name":"setVersion","constant":false,"payable":false,"inputs":[{"type":"string","name":"newVersion"}],"outputs":[]},{"type":"function","name":"skaleToken","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"address","name":""}]},{"type":"function","name":"skipTime","constant":false,"payable":false,"inputs":[{"type":"uint256","name":"sec"}],"outputs":[]},{"type":"function","name":"sklPriceTimestamp","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"timeMultiplier","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"version","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"string","name":""}]}]} \ No newline at end of file diff --git a/src/pages/StakeAmount.tsx b/src/pages/StakeAmount.tsx index 5d1f9312..6396ba0a 100644 --- a/src/pages/StakeAmount.tsx +++ b/src/pages/StakeAmount.tsx @@ -24,14 +24,8 @@ import { useState, useEffect } from 'react' import { type Signer } from 'ethers' import { useParams } from 'react-router-dom' -import { - cmn, - cls, - type MetaportCore, - SkPaper, - type interfaces, - styles -} from '@skalenetwork/metaport' +import { cmn, cls, type MetaportCore, SkPaper, styles } from '@skalenetwork/metaport' +import { types } from '@/core' import Container from '@mui/material/Container' import ArrowBackIosNewRoundedIcon from '@mui/icons-material/ArrowBackIosNewRounded' @@ -46,13 +40,6 @@ import Delegate from '../components/delegation/Delegate' import Breadcrumbs from '../components/Breadcrumbs' import ConnectWallet from '../components/ConnectWallet' -import { - DelegationType, - type ISkaleContractsMap, - type IValidator, - type StakingInfoMap -} from '../core/interfaces' - import ErrorTile from '../components/ErrorTile' import Headline from '../components/Headline' import { isDelegationTypeAvailable, isLoaded } from '../core/delegation/staking' @@ -60,19 +47,21 @@ import { getDelegationTypeAlias } from '../core/delegation' export default function StakeAmount(props: { mpc: MetaportCore - validators: IValidator[] + validators: types.staking.IValidator[] loadValidators: () => void loadStakingInfo: () => void - sc: ISkaleContractsMap | null - si: StakingInfoMap - address: interfaces.AddressType | undefined + sc: types.staking.ISkaleContractsMap | null + si: types.staking.StakingInfoMap + address: types.AddressType | undefined getMainnetSigner: () => Promise }) { const { id, delType } = useParams() const validatorId = Number(id) ?? -1 - const delegationType = Number(delType) ?? DelegationType.REGULAR + const delegationType = Number(delType) ?? types.staking.DelegationType.REGULAR - const [currentValidator, setCurrentValidator] = useState(undefined) + const [currentValidator, setCurrentValidator] = useState( + undefined + ) const [errorMsg, setErrorMsg] = useState() const loaded = isLoaded(props.si) diff --git a/src/pages/StakeValidator.tsx b/src/pages/StakeValidator.tsx index 82ee09ef..1bb7383f 100644 --- a/src/pages/StakeValidator.tsx +++ b/src/pages/StakeValidator.tsx @@ -22,20 +22,13 @@ */ import { useState, useEffect } from 'react' +import { cmn, cls, type MetaportCore, SkPaper } from '@skalenetwork/metaport' +import { types } from '@/core' import Container from '@mui/material/Container' import ArrowBackIosNewRoundedIcon from '@mui/icons-material/ArrowBackIosNewRounded' import PersonSearchRoundedIcon from '@mui/icons-material/PersonSearchRounded' -import { cmn, cls, type MetaportCore, SkPaper } from '@skalenetwork/metaport' - -import { - DelegationType, - type ISkaleContractsMap, - type IValidator, - type StakingInfoMap -} from '../core/interfaces' - import Validators from '../components/delegation/Validators' import DelegationTypeSelect from '../components/delegation/DelegationTypeSelect' import Breadcrumbs from '../components/Breadcrumbs' @@ -44,13 +37,15 @@ import SkStack from '../components/SkStack' export default function StakeValidator(props: { mpc: MetaportCore - validators: IValidator[] + validators: types.staking.IValidator[] loadValidators: () => void loadStakingInfo: () => void - sc: ISkaleContractsMap | null - si: StakingInfoMap + sc: types.staking.ISkaleContractsMap | null + si: types.staking.StakingInfoMap }) { - const [delegationType, setDelegationType] = useState(DelegationType.REGULAR) + const [delegationType, setDelegationType] = useState( + types.staking.DelegationType.REGULAR + ) const [validatorId, setValidatorId] = useState() const handleChange = (event: any) => { @@ -100,7 +95,7 @@ export default function StakeValidator(props: { validators={props.validators} validatorId={validatorId} setValidatorId={setValidatorId} - internal={!compareEnum(delegationType, DelegationType.REGULAR)} + internal={!compareEnum(delegationType, types.staking.DelegationType.REGULAR)} delegationType={delegationType} /> diff --git a/src/pages/Staking.tsx b/src/pages/Staking.tsx index 04471343..875e3271 100644 --- a/src/pages/Staking.tsx +++ b/src/pages/Staking.tsx @@ -25,17 +25,9 @@ import { Helmet } from 'react-helmet' import { Link } from 'react-router-dom' import { type Signer, isAddress } from 'ethers' -import debug from 'debug' -import { useEffect, useState } from 'react' -import { - cmn, - cls, - styles, - SkPaper, - type MetaportCore, - type interfaces, - sendTransaction -} from '@skalenetwork/metaport' +import { useCallback, useEffect, useState } from 'react' +import { cmn, cls, styles, SkPaper, type MetaportCore } from '@skalenetwork/metaport' +import { types } from '@/core' import Container from '@mui/material/Container' import Stack from '@mui/material/Stack' @@ -50,20 +42,19 @@ import WarningRoundedIcon from '@mui/icons-material/WarningRounded' import Delegations from '../components/delegation/Delegations' -import { - type ISkaleContractsMap, - type IValidator, - DelegationType, - type StakingInfoMap, - type IRewardInfo, - type IDelegationInfo, - type ContractType -} from '../core/interfaces' - import Summary from '../components/delegation/Summary' import { Collapse } from '@mui/material' -import { initActionContract } from '../core/contracts' -import { BALANCE_UPDATE_INTERVAL_MS, DEFAULT_ERROR_MSG } from '../core/constants' + +import { + retrieveRewards, + unstakeDelegation, + cancelDelegationRequest, + retrieveUnlockedTokens, + type LoadingState, + StakingActionProps +} from '../core/delegation/stakingActions' + +import { BALANCE_UPDATE_INTERVAL_MS } from '../core/constants' import ErrorTile from '../components/ErrorTile' import ConnectWallet from '../components/ConnectWallet' import Headline from '../components/Headline' @@ -71,115 +62,86 @@ import Message from '../components/Message' import { META_TAGS } from '../core/meta' import SkPageInfoIcon from '../components/SkPageInfoIcon' -debug.enable('*') -const log = debug('portal:pages:Staking') - export default function Staking(props: { mpc: MetaportCore - validators: IValidator[] + validators: types.staking.IValidator[] loadValidators: () => Promise loadStakingInfo: () => Promise - sc: ISkaleContractsMap | null - si: StakingInfoMap - address: interfaces.AddressType | undefined - customAddress: interfaces.AddressType | undefined + sc: types.staking.ISkaleContractsMap | null + si: types.staking.StakingInfoMap + address: types.AddressType | undefined + customAddress: types.AddressType | undefined getMainnetSigner: () => Promise isXs: boolean }) { - const [loading, setLoading] = useState(false) + const [loading, setLoading] = useState(false) const [errorMsg, setErrorMsg] = useState() - - const [customRewardAddress, setCustomRewardAddress] = useState< - interfaces.AddressType | undefined - >(props.address) + const [customRewardAddress, setCustomRewardAddress] = useState( + props.address + ) useEffect(() => { props.loadValidators() props.loadStakingInfo() - const intervalId = setInterval(() => { - props.loadStakingInfo() - }, BALANCE_UPDATE_INTERVAL_MS) - log(`Updating staking info interval: ${Number(intervalId)}`) - return () => { - log(`Clearing interval: ${Number(intervalId)}`) - clearInterval(intervalId) // Clear interval on component unmount - } + const intervalId = setInterval(props.loadStakingInfo, BALANCE_UPDATE_INTERVAL_MS) + return () => clearInterval(intervalId) }, [props.address, props.sc]) useEffect(() => { setCustomRewardAddress(props.address) }, [props.address]) - async function processTx( - delegationType: DelegationType, - txName: string, - txArgs: any[], - contractType: ContractType - ) { - if (props.sc === null || props.address === undefined) return - log('processTx:', txName, txArgs, contractType, delegationType) - try { - const signer = await props.getMainnetSigner() - const contract = await initActionContract( - signer, - delegationType, - props.address, - props.mpc.config.skaleNetwork, - contractType - ) - const res = await sendTransaction(contract[txName], txArgs) - if (!res.status) { - setErrorMsg(res.err?.name) - } else { - setErrorMsg(undefined) - await props.loadStakingInfo() - } - setLoading(false) - } catch (err: any) { - console.error(err) - setErrorMsg(err.message ? err.message : DEFAULT_ERROR_MSG) - setLoading(false) - } - } + const getStakingActionProps = useCallback( + (): StakingActionProps => ({ + sc: props.sc, + address: props.address, + skaleNetwork: props.mpc.config.skaleNetwork, + getMainnetSigner: props.getMainnetSigner, + setLoading, + setErrorMsg, + postAction: props.loadStakingInfo + }), + [ + props.sc, + props.address, + props.mpc.config.skaleNetwork, + props.getMainnetSigner, + props.loadStakingInfo + ] + ) - async function retrieveRewards(rewardInfo: IRewardInfo) { - setLoading(rewardInfo) + async function handleRetrieveRewards(rewardInfo: types.staking.IRewardInfo) { if (!isAddress(customRewardAddress)) { setErrorMsg('Invalid address') setLoading(false) return } - processTx( - rewardInfo.delegationType, - 'withdrawBounty', - [rewardInfo.validatorId, customRewardAddress], - 'distributor' - ) + await retrieveRewards({ + rewardInfo, + rewardAddress: customRewardAddress, + props: getStakingActionProps() + }) } - async function unstake(delegationInfo: IDelegationInfo) { - setLoading(delegationInfo) - processTx( - delegationInfo.delegationType, - 'requestUndelegation', - [delegationInfo.delegationId], - 'delegation' - ) + async function handleUnstake(delegationInfo: types.staking.IDelegationInfo) { + await unstakeDelegation({ + delegationInfo, + props: getStakingActionProps() + }) } - async function cancelRequest(delegationInfo: IDelegationInfo) { - setLoading(delegationInfo) - processTx( - delegationInfo.delegationType, - 'cancelPendingDelegation', - [delegationInfo.delegationId], - 'delegation' - ) + async function handleCancelRequest(delegationInfo: types.staking.IDelegationInfo) { + await cancelDelegationRequest({ + delegationInfo, + props: getStakingActionProps() + }) } - async function retrieveUnlocked(rewardInfo: IRewardInfo): Promise { - setLoading(rewardInfo) - processTx(rewardInfo.delegationType, 'retrieve', [], 'distributor') + async function handleRetrieveUnlocked(rewardInfo: types.staking.IRewardInfo) { + await retrieveUnlockedTokens({ + rewardInfo, + props: getStakingActionProps() + }) } return ( @@ -251,10 +213,10 @@ export default function Staking(props: { @@ -268,27 +230,25 @@ export default function Staking(props: { - - @@ -302,12 +262,12 @@ export default function Staking(props: { . + */ + +/** + * @file Validator.tsx + * @copyright SKALE Labs 2024-Present + */ + +import { useCallback, useEffect, useMemo, useState } from 'react' +import { type Signer } from 'ethers' +import { types } from '@/core' + +import Container from '@mui/material/Container' +import { cmn, cls, type MetaportCore, styles, SkPaper } from '@skalenetwork/metaport' + +import { Collapse, Skeleton } from '@mui/material' + +import CorporateFareRoundedIcon from '@mui/icons-material/CorporateFareRounded' +import PeopleRoundedIcon from '@mui/icons-material/PeopleRounded' +import AllInboxRoundedIcon from '@mui/icons-material/AllInboxRounded' + +import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded' + +import { + StakingActionProps, + unstakeDelegation, + acceptDelegation, + LoadingState +} from '../core/delegation/stakingActions' +import { sortDelegations, SortType } from '../core/delegation' +import { META_TAGS } from '../core/meta' +import { ITEMS_PER_PAGE } from '../core/constants' + +import SkPageInfoIcon from '../components/SkPageInfoIcon' +import ValidatorInfo from '../components/delegation/ValidatorInfo' +import Headline from '../components/Headline' +import ConnectWallet from '../components/ConnectWallet' +import Delegation from '../components/delegation/Delegation' +import SortToggle from '../components/delegation/SortToggle' +import ShowMoreButton from '../components/delegation/ShowMoreButton' +import DelegationTotals from '../components/delegation/DelegationTotals' +import Message from '../components/Message' +import ErrorTile from '../components/ErrorTile' +import ChainRewards from '../components/delegation/ChainRewards' + +export default function Validator(props: { + mpc: MetaportCore + sc: types.staking.ISkaleContractsMap | null + address: types.AddressType | undefined + customAddress: types.AddressType | undefined + loadValidator: () => Promise + validator: types.staking.IValidator | null | undefined + isXs: boolean + delegations: types.staking.IDelegation[] | null + getMainnetSigner: () => Promise +}) { + const [sortBy, setSortBy] = useState('id') + const [visibleItems, setVisibleItems] = useState(ITEMS_PER_PAGE) + + const [loading, setLoading] = useState(false) + const [errorMsg, setErrorMsg] = useState() + + const getStakingActionProps = useCallback( + (): StakingActionProps => ({ + sc: props.sc, + address: props.address, + skaleNetwork: props.mpc.config.skaleNetwork, + getMainnetSigner: props.getMainnetSigner, + setLoading, + setErrorMsg, + postAction: props.loadValidator + }), + [ + props.sc, + props.address, + props.mpc.config.skaleNetwork, + props.getMainnetSigner, + props.loadValidator + ] + ) + + const sortedDelegations = useMemo( + () => sortDelegations(props.delegations || [], sortBy), + [props.delegations, sortBy] + ) + + const visibleDelegations = useMemo( + () => sortedDelegations.slice(0, visibleItems), + [sortedDelegations, visibleItems] + ) + + const remainingItems = Math.max(0, sortedDelegations.length - visibleItems) + + useEffect(() => { + setVisibleItems(ITEMS_PER_PAGE) + }, [sortBy]) + + const handleShowMore = () => { + setVisibleItems((prevVisible) => prevVisible + ITEMS_PER_PAGE) + } + + async function handleUnstake(delegationInfo: types.staking.IDelegationInfo) { + await unstakeDelegation({ + delegationInfo, + props: getStakingActionProps() + }) + } + + async function handleAccept(delegationInfo: types.staking.IDelegationInfo) { + await acceptDelegation({ + delegationInfo, + props: getStakingActionProps() + }) + } + + const renderDelegationsContent = () => { + if (props.delegations === null) { + return ( +
+ + + +
+ ) + } + return ( + <> + {visibleDelegations.map((delegation: types.staking.IDelegation) => ( + + ))} + {remainingItems > 0 && ( +
+
+ +
+
+ )} + + ) + } + + return ( + +
+
+

Manage Validator

+

{META_TAGS.validator.description}

+
+ +
+ {props.customAddress !== undefined ? ( + } + link="/validator" + linkText="click to exit" + type="warning" + /> + ) : null} + + } + size="small" + className={cls(cmn.mbott20)} + /> + + + + {props.address || props.customAddress ? ( + props.validator !== undefined ? ( +
+ + +
+ ) : ( +
+ +

+ Validator doesn't exist +

+
+ ) + ) : ( +
+ )} +
+ {props.validator && ( + + )} + + {props.validator && ( + +
+
+ } + className={cls(cmn.flexg)} + /> + +
+ {renderDelegationsContent()} +
+
+ )} +
+ ) +} diff --git a/src/pages/Validators.tsx b/src/pages/Validators.tsx index f3b71e0b..0f957d01 100644 --- a/src/pages/Validators.tsx +++ b/src/pages/Validators.tsx @@ -22,21 +22,25 @@ */ import { useEffect } from 'react' +import { Link } from 'react-router-dom' +import { cmn, cls, type MetaportCore } from '@skalenetwork/metaport' +import { types } from '@/core' +import { Button } from '@mui/material' import Container from '@mui/material/Container' -import { cmn, cls, type MetaportCore } from '@skalenetwork/metaport' +import ManageAccountsRoundedIcon from '@mui/icons-material/ManageAccountsRounded' import Validators from '../components/delegation/Validators' - -import { DelegationType, type ISkaleContractsMap, type IValidator } from '../core/interfaces' import SkPageInfoIcon from '../components/SkPageInfoIcon' import { META_TAGS } from '../core/meta' +import DelegationsNotification from '../components/delegation/DelegationsNotification' export default function ValidatorsPage(props: { mpc: MetaportCore - validators: IValidator[] - sc: ISkaleContractsMap | null + validators: types.staking.IValidator[] + sc: types.staking.ISkaleContractsMap | null loadValidators: () => void + validatorDelegations: types.staking.IDelegation[] | null }) { useEffect(() => { if (props.sc !== null) { @@ -53,6 +57,17 @@ export default function ValidatorsPage(props: { List of validators on SKALE Network

+ + +
@@ -61,7 +76,7 @@ export default function ValidatorsPage(props: { validators={props.validators} validatorId={0} setValidatorId={(): void => {}} - delegationType={DelegationType.REGULAR} + delegationType={types.staking.DelegationType.REGULAR} size="lg" />