feat: initial Pezkuwi Apps rebrand from polkadot-apps

Rebranded terminology:
- Polkadot → Pezkuwi
- Kusama → Dicle
- Westend → Zagros
- Rococo → PezkuwiChain
- Substrate → Bizinikiwi
- parachain → teyrchain

Custom logos with Kurdistan brand colors (#e6007a → #86e62a):
- bizinikiwi-hexagon.svg
- sora-bizinikiwi.svg
- hezscanner.svg
- heztreasury.svg
- pezkuwiscan.svg
- pezkuwistats.svg
- pezkuwiassembly.svg
- pezkuwiholic.svg
This commit is contained in:
2026-01-07 13:05:27 +03:00
commit d21bfb1320
5867 changed files with 329019 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
// Copyright 2017-2025 @pezkuwi/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
import type { SlashEra } from './types.js';
import React, { useCallback, useRef, useState } from 'react';
import { Button, Table, TxButton } from '@pezkuwi/react-components';
import { useApi, useCollectiveInstance } from '@pezkuwi/react-hooks';
import { BN_ONE, isFunction } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
import Row from './Row.js';
import Summary from './Summary.js';
interface Props {
buttons: React.ReactNode;
councilId: string | null;
councilThreshold: number;
slash: SlashEra;
}
interface Proposal {
length: number;
proposal: SubmittableExtrinsic<'promise'>
}
interface Selected {
selected: number[];
txAll: Proposal | null;
txSome: Proposal | null;
}
function Slashes ({ buttons, councilId, councilThreshold, slash }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const councilMod = useCollectiveInstance('council');
const [{ selected, txAll, txSome }, setSelected] = useState<Selected>((): Selected => {
const proposal = api.tx.staking.cancelDeferredSlash(slash.era, slash.slashes.map((_, index) => index));
return {
selected: [],
txAll: councilMod
? { length: proposal.encodedLength, proposal }
: null,
txSome: null
};
});
const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
[t('era {{era}}/unapplied', {
replace: {
era: api.query.staking.earliestUnappliedSlash || !api.consts.staking.slashDeferDuration
? slash.era.toString()
: slash.era.sub(api.consts.staking.slashDeferDuration).sub(BN_ONE).toString()
}
}), 'start', 3],
[t('reporters'), 'address'],
[t('own')],
[t('other')],
[t('total')],
[t('payout')],
!api.query.staking.earliestUnappliedSlash && !!api.consts.staking.slashDeferDuration &&
[t('apply')],
[]
]);
const _onSelect = useCallback(
(index: number) => setSelected((state): Selected => {
const selected = state.selected.includes(index)
? state.selected.filter((i) => i !== index)
: state.selected.concat(index).sort((a, b) => a - b);
const proposal = selected.length
? api.tx.staking.cancelDeferredSlash(slash.era, selected)
: null;
return {
selected,
txAll: state.txAll,
txSome: proposal && councilMod && isFunction(api.tx[councilMod].propose)
? { length: proposal.encodedLength, proposal }
: null
};
}),
[api, councilMod, slash]
);
return (
<>
<Summary slash={slash} />
<Button.Group>
{buttons}
{councilMod && (
<>
<TxButton
accountId={councilId}
isDisabled={!txSome}
isToplevel
label={t('Cancel selected')}
params={txSome && (
api.tx[councilMod].propose.meta.args.length === 3
? [councilThreshold, txSome.proposal, txSome.length]
: [councilThreshold, txSome.proposal]
)}
tx={api.tx[councilMod].propose}
/>
<TxButton
accountId={councilId}
isDisabled={!txAll}
isToplevel
label={t('Cancel all')}
params={txAll && (
api.tx[councilMod].propose.meta.args.length === 3
? [councilThreshold, txAll.proposal, txAll.length]
: [councilThreshold, txAll.proposal]
)}
tx={api.tx[councilMod].propose}
/>
</>
)}
</Button.Group>
<Table header={headerRef.current}>
{slash.slashes.map((slash, index): React.ReactNode => (
<Row
index={index}
isSelected={selected.includes(index)}
key={index}
onSelect={
councilId
? _onSelect
: undefined
}
slash={slash}
/>
))}
</Table>
</>
);
}
export default React.memo(Slashes);
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2017-2025 @pezkuwi/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Slash } from './types.js';
import React, { useCallback } from 'react';
import { AddressMini, AddressSmall, Badge, Checkbox, ExpanderScroll } from '@pezkuwi/react-components';
import { useApi } from '@pezkuwi/react-hooks';
import { FormatBalance } from '@pezkuwi/react-query';
import { formatNumber } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
interface Props {
index: number;
isSelected: boolean;
onSelect?: (index: number) => void;
slash: Slash;
}
function Row ({ index, isSelected, onSelect, slash: { era, isMine, slash: { others, own, payout, reporters, validator }, total, totalOther } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const _onSelect = useCallback(
() => onSelect && onSelect(index),
[index, onSelect]
);
const renderOthers = useCallback(
() => others.map(([accountId, balance], index): React.ReactNode => (
<AddressMini
balance={balance}
key={index}
value={accountId}
withBalance
/>
)),
[others]
);
return (
<tr>
<td className='badge'>
{isMine && (
<Badge
color='red'
icon='skull-crossbones'
/>
)}
</td>
<td className='address'>
<AddressSmall value={validator} />
</td>
<td className='expand all'>
{!!others.length && (
<ExpanderScroll
renderChildren={renderOthers}
summary={t('Nominators ({{count}})', { replace: { count: formatNumber(others.length) } })}
/>
)}
</td>
<td className='address'>
{reporters.map((reporter, index): React.ReactNode => (
<AddressMini
key={index}
value={reporter}
/>
))}
</td>
<td className='number together'>
<FormatBalance value={own} />
</td>
<td className='number together'>
<FormatBalance value={totalOther} />
</td>
<td className='number together'>
<FormatBalance value={total} />
</td>
<td className='number together'>
<FormatBalance value={payout} />
</td>
{!api.query.staking.earliestUnappliedSlash && !!api.consts.staking.slashDeferDuration && (
<td className='number together'>
{formatNumber(era)}
</td>
)}
<td>
<Checkbox
isDisabled={!onSelect}
onChange={_onSelect}
value={isSelected}
/>
</td>
</tr>
);
}
export default React.memo(Row);
@@ -0,0 +1,65 @@
// Copyright 2017-2025 @pezkuwi/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveSessionProgress } from '@pezkuwi/api-derive/types';
import type { SlashEra } from './types.js';
import React, { useMemo } from 'react';
import { CardSummary, SummaryBox } from '@pezkuwi/react-components';
import { useApi, useCall } from '@pezkuwi/react-hooks';
import { FormatBalance } from '@pezkuwi/react-query';
import { BN, BN_ONE, formatNumber } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
interface Props {
slash: SlashEra;
}
function Summary ({ slash: { era, nominators, reporters, total, validators } }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const sessionInfo = useCall<DeriveSessionProgress>(api.derive.session?.progress);
const [blockProgress, blockEnd] = useMemo(
() => sessionInfo
? [
sessionInfo.activeEra.sub(era).isub(BN_ONE).imul(sessionInfo.eraLength).iadd(sessionInfo.eraProgress),
api.consts.staking.slashDeferDuration.mul(sessionInfo.eraLength)
]
: [new BN(0), new BN(0)],
[api, era, sessionInfo]
);
return (
<SummaryBox>
<section>
<CardSummary label={t('validators')}>
{formatNumber(validators.length)}
</CardSummary>
<CardSummary label={t('nominators')}>
{formatNumber(nominators.length)}
</CardSummary>
<CardSummary label={t('reporters')}>
{formatNumber(reporters.length)}
</CardSummary>
</section>
{blockProgress.gtn(0) && (
<CardSummary
label={t('defer')}
progress={{
total: blockEnd,
value: blockProgress,
withTime: true
}}
/>
)}
<CardSummary label={t('total')}>
<FormatBalance value={total} />
</CardSummary>
</SummaryBox>
);
}
export default React.memo(Summary);
+151
View File
@@ -0,0 +1,151 @@
// Copyright 2017-2025 @pezkuwi/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { StakerState } from '@pezkuwi/react-hooks/types';
import type { UnappliedSlash } from '@pezkuwi/types/interfaces';
import type { Slash, SlashEra } from './types.js';
import React, { useMemo, useRef, useState } from 'react';
import { getSlashProposalThreshold } from '@pezkuwi/apps-config';
import { Table, ToggleGroup } from '@pezkuwi/react-components';
import { useAccounts, useApi, useCollectiveMembers } from '@pezkuwi/react-hooks';
import { BN, BN_ONE, formatNumber } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
import Era from './Era.js';
interface Props {
ownStashes?: StakerState[];
slashes: [BN, UnappliedSlash[]][];
}
function calcSlashEras (slashes: [BN, UnappliedSlash[]][], ownStashes: StakerState[]): SlashEra[] {
const slashEras: SlashEra[] = [];
slashes
.reduce((rows: Slash[], [era, slashes]): Slash[] => {
return slashes.reduce((rows: Slash[], slash): Slash[] => {
const totalOther = slash.others.reduce((total: BN, [, value]): BN => {
return total.add(value);
}, new BN(0));
const isMine = ownStashes.some(({ stashId }): boolean => {
return slash.validator.eq(stashId) || slash.others.some(([nominatorId]) => nominatorId.eq(stashId));
});
rows.push({ era, isMine, slash, total: slash.own.add(totalOther), totalOther });
return rows;
}, rows);
}, [])
.forEach((slash): void => {
let slashEra = slashEras.find(({ era }) => era.eq(slash.era));
if (!slashEra) {
slashEra = {
era: slash.era,
nominators: [],
payout: new BN(0),
reporters: [],
slashes: [],
total: new BN(0),
validators: []
};
slashEras.push(slashEra);
}
slashEra.payout.iadd(slash.slash.payout);
slashEra.total.iadd(slash.total);
slashEra.slashes.push(slash);
const validatorId = slash.slash.validator.toString();
if (!slashEra.validators.includes(validatorId)) {
slashEra.validators.push(validatorId);
}
slash.slash.others.forEach(([accountId]): void => {
const nominatorId = accountId.toString();
if (slashEra && !slashEra.nominators.includes(nominatorId)) {
slashEra.nominators.push(nominatorId);
}
});
slash.slash.reporters.forEach((accountId): void => {
const reporterId = accountId.toString();
if (slashEra && !slashEra.reporters.includes(reporterId)) {
slashEra.reporters.push(reporterId);
}
});
});
return slashEras.sort((a, b) => b.era.cmp(a.era));
}
function Slashes ({ ownStashes = [], slashes }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
const { members } = useCollectiveMembers('council');
const [selectedIndex, setSelectedIndex] = useState(0);
const rows = useMemo(
() => calcSlashEras(slashes, ownStashes),
[ownStashes, slashes]
);
const eraOpts = useMemo(
() => rows
.map(({ era }) =>
api.query.staking.earliestUnappliedSlash || !api.consts.staking.slashDeferDuration
? era
: era.sub(api.consts.staking.slashDeferDuration).sub(BN_ONE)
)
.map((era) => ({
text: t('era {{era}}', { replace: { era: formatNumber(era) } }),
value: era.toString()
})),
[api, rows, t]
);
const councilId = useMemo(
() => allAccounts.find((accountId) => members.includes(accountId)) || null,
[allAccounts, members]
);
const emptyHeader = useRef<[React.ReactNode?, string?, number?][]>([
[t('unapplied'), 'start']
]);
if (!rows.length) {
return (
<Table
empty={t('There are no unapplied/pending slashes')}
header={emptyHeader.current}
/>
);
}
const councilThreshold = Math.ceil((members.length || 0) * getSlashProposalThreshold(api));
return (
<Era
buttons={
<ToggleGroup
onChange={setSelectedIndex}
options={eraOpts}
value={selectedIndex}
/>
}
councilId={councilId}
councilThreshold={councilThreshold}
key={rows[selectedIndex].era.toString()}
slash={rows[selectedIndex]}
/>
);
}
export default React.memo(Slashes);
@@ -0,0 +1,23 @@
// Copyright 2017-2025 @pezkuwi/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { UnappliedSlash } from '@pezkuwi/types/interfaces';
import type { BN } from '@pezkuwi/util';
export interface Slash {
era: BN;
isMine: boolean;
slash: UnappliedSlash;
total: BN;
totalOther: BN;
}
export interface SlashEra {
era: BN;
nominators: string[];
payout: BN;
reporters: string[];
slashes: Slash[];
validators: string[];
total: BN;
}