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
@@ -0,0 +1,78 @@
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Abi } from '@pezkuwi/api-contract';
import React from 'react';
import { IconLink, InputFile, Labelled } from '@pezkuwi/react-components';
import { useTranslation } from '../translate.js';
import Messages from './Messages.js';
interface Props {
className?: string;
contractAbi?: Abi | null;
errorText?: string | null;
isDisabled?: boolean;
isError?: boolean;
isFull?: boolean;
isRequired?: boolean;
isValid?: boolean;
isSupplied?: boolean;
label?: React.ReactNode;
onChange: (u8a: Uint8Array, name: string) => void;
onRemove?: () => void;
onRemoved?: () => void;
onSelect?: () => void;
onSelectConstructor?: (index?: number) => void;
withConstructors?: boolean;
withLabel?: boolean;
withMessages?: boolean;
withWasm?: boolean;
}
const NOOP = (): void => undefined;
function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull, isValid, label, onChange, onRemove = NOOP, onSelectConstructor, withConstructors = true, withLabel = true, withMessages = true, withWasm }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (contractAbi && isValid)
? (
<Labelled
className={className}
label={label || t('contract ABI')}
labelExtra={onRemove && (
<IconLink
icon='trash'
label={t('Remove ABI')}
onClick={onRemove}
/>
)}
withLabel={withLabel}
>
<Messages
contractAbi={contractAbi}
isLabelled={withLabel}
onSelectConstructor={onSelectConstructor}
withConstructors={withConstructors}
withMessages={withMessages}
withWasm={withWasm}
/>
</Labelled>
)
: (
<div className={className}>
<InputFile
isDisabled={isDisabled}
isError={isError}
isFull={isFull}
label={label || t('contract ABI')}
onChange={onChange}
placeholder={errorText || t('click to select or drag and drop a JSON file')}
/>
</div>
);
}
export default React.memo(ABI);
@@ -0,0 +1,90 @@
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { CodeStored } from '../types.js';
import React, { useCallback, useEffect, useState } from 'react';
import { Icon, styled } from '@pezkuwi/react-components';
import Row from '@pezkuwi/react-components/Row';
import contracts from '../store.js';
interface Props {
buttons?: React.ReactNode;
children?: React.ReactNode;
className?: string;
code: CodeStored;
isInline?: boolean;
withTags?: boolean;
}
const DEFAULT_HASH = '0x';
const DEFAULT_NAME = '<unknown>';
function CodeRow ({ buttons, children, className, code: { json }, isInline, withTags }: Props): React.ReactElement<Props> {
const [name, setName] = useState(json.name || DEFAULT_NAME);
const [tags, setTags] = useState(json.tags || []);
const [codeHash, setCodeHash] = useState(json.codeHash || DEFAULT_HASH);
useEffect((): void => {
setName(json.name || DEFAULT_NAME);
setTags(json.tags || []);
setCodeHash(json.codeHash || DEFAULT_HASH);
}, [json]);
const _onSaveName = useCallback(
(): void => {
const trimmedName = name.trim();
if (trimmedName && codeHash) {
contracts.saveCode(codeHash, { name });
}
},
[codeHash, name]
);
const _onSaveTags = useCallback(
(): void => {
codeHash && contracts.saveCode(codeHash, { tags });
},
[codeHash, tags]
);
return (
<StyledRow
buttons={buttons}
className={className}
icon={
<div className='ui--CodeRow-icon'>
<Icon icon='code' />
</div>
}
isInline={isInline}
name={name}
onChangeName={setName}
onChangeTags={setTags}
onSaveName={_onSaveName}
onSaveTags={_onSaveTags}
tags={withTags ? tags : undefined}
>
{children}
</StyledRow>
);
}
const StyledRow = styled(Row)`
.ui--CodeRow-icon {
margin-right: -0.5em;
background: #eee;
border-radius: 50%;
color: #666;
width: 26px;
height: 26px;
display: flex;
justify-content: center;
align-items: center;
}
`;
export default React.memo(CodeRow);
@@ -0,0 +1,162 @@
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { WeightV2 } from '@pezkuwi/types/interfaces';
import type { BN } from '@pezkuwi/util';
import type { UseWeight } from '../types.js';
import React, { useEffect, useMemo, useState } from 'react';
import { InputNumber, Toggle } from '@pezkuwi/react-components';
import { BN_MILLION, BN_ONE, BN_ZERO } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
interface Props {
className?: string;
estimatedWeight?: BN | null;
estimatedWeightV2?: WeightV2 | null;
isCall?: boolean;
weight: UseWeight;
}
function InputMegaGas ({ className,
estimatedWeight,
estimatedWeightV2,
isCall,
weight: { executionTime,
isValid,
isWeightV2,
megaGas,
megaRefTime,
percentage,
proofSize,
setIsEmpty,
setMegaGas,
setMegaRefTime,
setProofSize } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [withEstimate, setWithEstimate] = useState(true);
const estimatedMg = useMemo(
() => estimatedWeight
? estimatedWeight.div(BN_MILLION).iadd(BN_ONE)
: null,
[estimatedWeight]
);
const estimatedMgRefTime = useMemo(
() => estimatedWeightV2
? estimatedWeightV2.refTime.toBn().div(BN_MILLION).iadd(BN_ONE)
: null,
[estimatedWeightV2]
);
const estimatedProofSize = useMemo(
() => estimatedWeightV2
? estimatedWeightV2.proofSize.toBn()
: null,
[estimatedWeightV2]
);
useEffect((): void => {
withEstimate && estimatedMg && setMegaGas(estimatedMg);
}, [estimatedMg, setMegaGas, withEstimate]);
useEffect((): void => {
withEstimate && estimatedMgRefTime && setMegaRefTime(estimatedMgRefTime);
}, [estimatedMgRefTime, setMegaRefTime, withEstimate]);
useEffect((): void => {
withEstimate && estimatedProofSize && setProofSize(estimatedProofSize);
}, [estimatedProofSize, setProofSize, withEstimate]);
useEffect((): void => {
setIsEmpty(withEstimate && !!isCall);
}, [isCall, setIsEmpty, withEstimate]);
const isDisabled = !!estimatedMg && withEstimate;
return (
<div className={className}>
{isWeightV2
? <>
<InputNumber
defaultValue={estimatedMgRefTime && isDisabled ? estimatedMgRefTime.toString() : undefined}
isDisabled={isDisabled}
isError={!isValid}
isZeroable={isCall}
label={
estimatedMgRefTime && (isCall ? !withEstimate : true)
? t('max RefTime allowed (M, {{estimatedRefTime}} estimated)', { replace: { estimatedMgRefTime: estimatedMgRefTime.toString() } })
: t('max RefTime allowed (M)')
}
onChange={isDisabled ? undefined : setMegaRefTime}
value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : megaRefTime)}
>
{(estimatedWeightV2 || isCall) && (
<Toggle
label={
isCall
? t('max read gas')
: t('use estimated gas')
}
onChange={setWithEstimate}
value={withEstimate}
/>
)}
</InputNumber>
<InputNumber
defaultValue={estimatedProofSize && isDisabled ? estimatedProofSize.toString() : undefined}
isDisabled={isDisabled}
isError={!isValid}
isZeroable={isCall}
label={
estimatedProofSize && (isCall ? !withEstimate : true)
? t('max ProofSize allowed ({{estimatedProofSize}} estimated)', { replace: { estimatedProofSize: estimatedProofSize.toString() } })
: t('max ProofSize allowed')
}
onChange={isDisabled ? undefined : setProofSize}
value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : proofSize)}
/>
<div className='contracts--Input-meter'>
{t('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}{', '}
{t('{{percentage}}% of block weight', { replace: { percentage: percentage.toFixed(2) } })}
</div>
</>
: <>
<InputNumber
defaultValue={estimatedMg && isDisabled ? estimatedMg.toString() : undefined}
isDisabled={isDisabled}
isError={!isValid}
isZeroable={isCall}
label={
estimatedMg && (isCall ? !withEstimate : true)
? t('max gas allowed (M, {{estimatedMg}} estimated)', { replace: { estimatedMg: estimatedMg.toString() } })
: t('max gas allowed (M)')
}
onChange={isDisabled ? undefined : setMegaGas}
value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : megaGas)}
>
{(estimatedWeight || isCall) && (
<Toggle
label={
isCall
? t('max read gas')
: t('use estimated gas')
}
onChange={setWithEstimate}
value={withEstimate}
/>
)}
</InputNumber>
<div className='contracts--Input-meter'>
{t('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}{', '}
{t('{{percentage}}% of block weight', { replace: { percentage: percentage.toFixed(2) } })}
</div>
</>}
</div>
);
}
export default React.memo(InputMegaGas);
@@ -0,0 +1,41 @@
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { Input } from '@pezkuwi/react-components';
import { useTranslation } from '../translate.js';
interface Props {
className?: string;
isBusy?: boolean;
isContract?: boolean;
isError?: boolean;
isDisabled?: boolean;
onChange: (_: string) => void;
onEnter?: () => void;
value?: string;
}
function InputName ({ className, isBusy, isContract, isError, onChange, onEnter, value = '' }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<Input
className={className}
isDisabled={isBusy}
isError={isError}
label={t(
isContract
? 'contract name'
: 'code bundle name'
)}
onChange={onChange}
onEnter={onEnter}
value={value}
/>
);
}
export default React.memo(InputName);
@@ -0,0 +1,133 @@
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AbiConstructor, ContractCallOutcome } from '@pezkuwi/api-contract/types';
import React, { useCallback } from 'react';
import { Button, Output, styled } from '@pezkuwi/react-components';
import valueToText from '@pezkuwi/react-params/valueToText';
import { useTranslation } from '../translate.js';
import MessageSignature from './MessageSignature.js';
export interface Props {
className?: string;
index: number;
lastResult?: ContractCallOutcome;
message: AbiConstructor;
onSelect?: (index: number) => void;
}
function filterDocs (docs: string[]): string[] {
let skip = false;
return docs
.map((line) => line.trim())
.filter((line) => line)
.filter((line, index): boolean => {
if (skip) {
return false;
} else if (index || line.startsWith('#')) {
skip = true;
return false;
}
return true;
});
}
function Message ({ className = '', index, lastResult, message, onSelect }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const _onSelect = useCallback(
() => onSelect && onSelect(index),
[index, onSelect]
);
return (
<StyledDiv
className={`${className} ${!onSelect ? 'exempt-hover' : ''} ${message.isConstructor ? 'constructor' : ''}`}
key={`${message.identifier}-${index}`}
>
{onSelect && (
message.isConstructor
? (
<Button
className='accessory'
icon='upload'
label={t('deploy')}
onClick={_onSelect}
/>
)
: (
<Button
className='accessory'
icon='play'
isDisabled={message.isMutating ? false : (!message.args.length && lastResult?.result.isOk)}
label={message.isMutating ? t('exec') : t('read')}
onClick={_onSelect}
/>
)
)}
<div className='info'>
<MessageSignature
asConstructor={message.isConstructor}
message={message}
withTooltip
/>
<div className='docs'>
{message.docs.length
? filterDocs(message.docs).map((line, index) => ((
<div key={`${message.identifier}-docs-${index}`}>{line}</div>
)))
: <i>&nbsp;{t('No documentation provided')}&nbsp;</i>
}
</div>
</div>
{lastResult && lastResult.result.isOk && lastResult.output && (
<Output
className='result'
isFull
label={t('current value')}
>
{valueToText('Text', lastResult.output)}
</Output>
)}
</StyledDiv>
);
}
const StyledDiv = styled.div`
align-items: center;
border-radius: 0.25rem;
display: flex;
padding: 0.25rem 0.75rem 0.25rem 0;
&.disabled {
opacity: 1 !important;
background: #eee !important;
color: #555 !important;
}
.info {
flex: 1 1;
margin-left: 1.5rem;
.docs {
font-size: var(--font-size-small);
font-weight: var(--font-weight-normal);
}
}
.result {
min-width: 15rem;
}
&+& {
margin-top: 0.5rem;
}
`;
export default React.memo(Message);
@@ -0,0 +1,105 @@
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AbiMessage } from '@pezkuwi/api-contract/types';
import React from 'react';
import { Icon, styled, Tooltip } from '@pezkuwi/react-components';
import { useApi } from '@pezkuwi/react-hooks';
import { encodeTypeDef } from '@pezkuwi/types/create';
import { useTranslation } from '../translate.js';
const MAX_PARAM_LENGTH = 20;
export interface Props {
asConstructor?: boolean;
className?: string;
message: AbiMessage;
params?: unknown[];
withTooltip?: boolean;
}
function truncate (param: string): string {
return param.length > MAX_PARAM_LENGTH
? `${param.substring(0, MAX_PARAM_LENGTH / 2)}${param.substring(param.length - MAX_PARAM_LENGTH / 2)}`
: param;
}
function MessageSignature ({ className, message: { args, isConstructor, isMutating, method, returnType }, params = [], withTooltip = false }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
return (
<StyledDiv className={className}>
<span className='ui--MessageSignature-name'>{method}</span>
{' '}({args.map(({ name, type }, index): React.ReactNode => {
return (
<React.Fragment key={`${name}-args-${index}`}>
{name}:
{' '}
<span className='ui--MessageSignature-type'>
{params?.[index]
? <b>{truncate((params as string[])[index].toString())}</b>
: encodeTypeDef(api.registry, type)
}
</span>
{index < (args.length) - 1 && ', '}
</React.Fragment>
);
})})
{(!isConstructor && returnType) && (
<>
:
{' '}
<span className='ui--MessageSignature-returnType'>
{encodeTypeDef(api.registry, returnType)}
</span>
</>
)}
{isMutating && (
<>
<Icon
className='ui--MessageSignature-mutates'
icon='database'
tooltip={`mutates-${method}`}
/>
{withTooltip && (
<Tooltip
text={t('Mutates contract state')}
trigger={`mutates-${method}`}
/>
)}
</>
)}
</StyledDiv>
);
}
const StyledDiv = styled.div`
font: var(--font-mono);
font-weight: var(--font-weight-normal);
flex-grow: 1;
.ui--MessageSignature-mutates {
color: #ff8600;
margin-left: 0.5rem;
opacity: var(--opacity-light);
}
.ui--MessageSignature-name {
color: #2f8ddb;
font-weight: var(--font-weight-normal);
}
.ui--MessageSignature-type {
color: #21a2b2;
}
.ui--MessageSignature-returnType {
color: #ff8600;
}
`;
export default React.memo(MessageSignature);
@@ -0,0 +1,146 @@
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Abi, ContractPromise } from '@pezkuwi/api-contract';
import type { AbiMessage, ContractCallOutcome } from '@pezkuwi/api-contract/types';
import type { Option } from '@pezkuwi/types';
import type { ContractInfo } from '@pezkuwi/types/interfaces';
import React, { useCallback, useEffect, useState } from 'react';
import { Expander, styled } from '@pezkuwi/react-components';
import { useApi, useCall } from '@pezkuwi/react-hooks';
import { formatNumber } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
import Message from './Message.js';
export interface Props {
className?: string;
contract?: ContractPromise;
contractAbi: Abi;
isLabelled?: boolean;
isWatching?: boolean;
trigger?: number;
onSelect?: (messageIndex: number, resultCb: (messageIndex: number, result?: ContractCallOutcome) => void) => void;
onSelectConstructor?: (constructorIndex: number) => void;
withConstructors?: boolean;
withMessages?: boolean;
withWasm?: boolean;
}
const READ_ADDR = '0x'.padEnd(66, '0');
function sortMessages (messages: AbiMessage[]): [AbiMessage, number][] {
return messages
.map((m, index): [AbiMessage, number] => [m, index])
.sort((a, b) => a[0].identifier.localeCompare(b[0].identifier))
.sort((a, b) => a[0].isMutating === b[0].isMutating
? 0
: a[0].isMutating
? -1
: 1
);
}
function Messages ({ className = '', contract, contractAbi: { constructors, info: { source }, messages }, isLabelled, isWatching, onSelect, onSelectConstructor, trigger, withConstructors, withMessages, withWasm }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const optInfo = useCall<Option<ContractInfo>>(contract && api.query.contracts.contractInfoOf, [contract?.address]);
const [isUpdating, setIsUpdating] = useState(false);
const [lastResults, setLastResults] = useState<(ContractCallOutcome | undefined)[]>([]);
const _onExpander = useCallback(
(isOpen: boolean): void => {
isWatching && setIsUpdating(isOpen);
},
[isWatching]
);
const _onRefresh = useCallback(
(): void => {
optInfo && contract &&
Promise
.all(
messages.map((m) =>
m.isMutating || m.args.length !== 0
? Promise.resolve(undefined)
: contract
.query[m.method](READ_ADDR, { gasLimit: -1, value: 0 })
.catch((e: Error) => console.error(`contract.query.${m.method}:: ${e.message}`))
.then(() => undefined)
)
)
.then(setLastResults)
.catch(console.error);
},
[contract, messages, optInfo]
);
useEffect((): void => {
(isUpdating || trigger) && optInfo && contract && _onRefresh();
}, [_onRefresh, contract, isUpdating, optInfo, trigger]);
const _setMessageResult = useCallback(
(_messageIndex: number, _result?: ContractCallOutcome): void => {
// ignore
},
[]
);
const _onSelect = useCallback(
(index: number) => onSelect && onSelect(index, _setMessageResult),
[_setMessageResult, onSelect]
);
return (
<StyledDiv className={`${className} ui--Messages ${isLabelled ? 'isLabelled' : ''}`}>
{withConstructors && (
<Expander summary={t('Constructors ({{count}})', { replace: { count: constructors.length } })}>
{sortMessages(constructors).map(([message, index]) => (
<Message
index={index}
key={index}
message={message}
onSelect={onSelectConstructor}
/>
))}
</Expander>
)}
{withMessages && (
<Expander
onClick={_onExpander}
summary={t('Messages ({{count}})', { replace: { count: messages.length } })}
>
{sortMessages(messages).map(([message, index]) => (
<Message
index={index}
key={index}
lastResult={lastResults[index]}
message={message}
onSelect={_onSelect}
/>
))}
</Expander>
)}
{withWasm && source.wasm.length !== 0 && (
<div>{t('{{size}} WASM bytes', { replace: { size: formatNumber(source.wasm.length) } })}</div>
)}
</StyledDiv>
);
}
const StyledDiv = styled.div`
padding-bottom: 0.75rem !important;
&.isLabelled {
background: var(--bg-input);
box-sizing: border-box;
border: 1px solid var(--border-input);
border-radius: .28571429rem;
padding: 1rem 1rem 0.5rem;
width: 100%;
}
`;
export default React.memo(Messages);
@@ -0,0 +1,51 @@
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { RawParams } from '@pezkuwi/react-params/types';
import type { Registry, TypeDef } from '@pezkuwi/types/types';
import React, { useCallback, useEffect, useState } from 'react';
import UIParams from '@pezkuwi/react-params';
interface Props {
isDisabled?: boolean;
params?: ParamDef[] | null | '';
onChange: (values: unknown[]) => void;
onEnter?: () => void;
registry: Registry;
}
interface ParamDef {
name: string;
type: TypeDef;
}
function Params ({ isDisabled, onChange, onEnter, params: propParams, registry }: Props): React.ReactElement<Props> | null {
const [params, setParams] = useState<ParamDef[]>([]);
useEffect((): void => {
propParams && setParams(propParams);
}, [propParams]);
const _onChange = useCallback(
(values: RawParams) => onChange(values.map(({ value }) => value)),
[onChange]
);
if (!params.length) {
return null;
}
return (
<UIParams
isDisabled={isDisabled}
onChange={_onChange}
onEnter={onEnter}
params={params}
registry={registry}
/>
);
}
export default React.memo(Params);
@@ -0,0 +1,10 @@
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { default as ABI } from './ABI.js';
export { default as CodeRow } from './CodeRow.js';
export { default as InputMegaGas } from './InputMegaGas.js';
export { default as InputName } from './InputName.js';
export { default as Messages } from './Messages.js';
export { default as MessageSignature } from './MessageSignature.js';
export { default as Params } from './Params.js';