mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-11-15 02:47:07 +00:00
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:
commit
d21bfb1320
5867 files changed
+329019
No files matched your search
@@ -0,0 +1,85 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, Input, Modal } from '@pezkuwi/react-components';
|
||||
import { isNull } from '@pezkuwi/util';
|
||||
|
||||
import { ABI, InputName } from '../shared/index.js';
|
||||
import store from '../store.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import useAbi from '../useAbi.js';
|
||||
import ValidateCode from './ValidateCode.js';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function Add ({ onClose }: Props): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const [codeHash, setCodeHash] = useState('');
|
||||
const [isCodeHashValid, setIsCodeHashValid] = useState(false);
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
const { abi, contractAbi, errorText, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi();
|
||||
|
||||
const _onSave = useCallback(
|
||||
(): void => {
|
||||
if (!codeHash || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
store.saveCode(codeHash, { abi, name, tags: [] });
|
||||
onClose();
|
||||
},
|
||||
[abi, codeHash, name, onClose]
|
||||
);
|
||||
|
||||
const isNameValid = !isNull(name) && name.length > 0;
|
||||
const isValid = isCodeHashValid && isNameValid && isAbiSupplied && isAbiValid;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header={t('Add an existing code hash')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
autoFocus
|
||||
isError={codeHash.length > 0 && !isCodeHashValid}
|
||||
label={t('code hash')}
|
||||
onChange={setCodeHash}
|
||||
value={codeHash}
|
||||
/>
|
||||
<ValidateCode
|
||||
codeHash={codeHash}
|
||||
onChange={setIsCodeHashValid}
|
||||
/>
|
||||
<InputName
|
||||
isError={!isNameValid}
|
||||
onChange={setName}
|
||||
value={name || undefined}
|
||||
/>
|
||||
<ABI
|
||||
contractAbi={contractAbi}
|
||||
errorText={errorText}
|
||||
isError={isAbiError || !isAbiError}
|
||||
isSupplied={isAbiSupplied}
|
||||
isValid={isAbiValid}
|
||||
onChange={onChangeAbi}
|
||||
onRemove={onRemoveAbi}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button
|
||||
icon='save'
|
||||
isDisabled={!isValid}
|
||||
label={t('Save')}
|
||||
onClick={_onSave}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Add);
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option } from '@pezkuwi/types';
|
||||
import type { Codec } from '@pezkuwi/types/types';
|
||||
import type { CodeStored } from '../types.js';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Button, Card, CopyButton, Forget, styled } from '@pezkuwi/react-components';
|
||||
import { useApi, useCall, useToggle } from '@pezkuwi/react-hooks';
|
||||
|
||||
import { CodeRow, Messages } from '../shared/index.js';
|
||||
import store from '../store.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import useAbi from '../useAbi.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
code: CodeStored;
|
||||
onShowDeploy: (codeHash: string, constructorIndex: number) => void;
|
||||
}
|
||||
|
||||
function Code ({ className, code, onShowDeploy }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const optCode = useCall<Option<Codec>>(api.query.contracts.pristineCode || api.query.contracts.codeStorage, [code.json.codeHash]);
|
||||
const [isForgetOpen, toggleIsForgetOpen] = useToggle();
|
||||
const { contractAbi } = useAbi([code.json.abi, code.contractAbi], code.json.codeHash, true);
|
||||
|
||||
const _onShowDeploy = useCallback(
|
||||
() => onShowDeploy(code.json.codeHash, 0),
|
||||
[code, onShowDeploy]
|
||||
);
|
||||
|
||||
const _onDeployConstructor = useCallback(
|
||||
(constructorIndex?: number): void => {
|
||||
onShowDeploy && onShowDeploy(code.json.codeHash, constructorIndex || 0);
|
||||
},
|
||||
[code, onShowDeploy]
|
||||
);
|
||||
|
||||
const _onForget = useCallback(
|
||||
(): void => {
|
||||
try {
|
||||
store.forgetCode(code.json.codeHash);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
toggleIsForgetOpen();
|
||||
}
|
||||
},
|
||||
[code, toggleIsForgetOpen]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTr className={className}>
|
||||
<td className='address top'>
|
||||
<Card>
|
||||
<CodeRow
|
||||
code={code}
|
||||
withTags={false}
|
||||
/>
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
key='modal-forget-account'
|
||||
mode='code'
|
||||
onClose={toggleIsForgetOpen}
|
||||
onForget={_onForget}
|
||||
>
|
||||
<CodeRow
|
||||
code={code || ''}
|
||||
isInline
|
||||
>
|
||||
<p>{t('You are about to remove this code from your list of available code hashes. Once completed, should you need to access it again, you will have to manually add the code hash again.')}</p>
|
||||
<p>{t('This operation does not remove the uploaded code WASM and ABI from the chain, nor any deployed contracts. The forget operation only limits your access to the code on this browser.')}</p>
|
||||
</CodeRow>
|
||||
</Forget>
|
||||
)}
|
||||
</Card>
|
||||
</td>
|
||||
<td className='all top'>
|
||||
{contractAbi && (
|
||||
<Messages
|
||||
contractAbi={contractAbi}
|
||||
onSelectConstructor={_onDeployConstructor}
|
||||
withConstructors
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className='together codeHash'>
|
||||
<div>{`${code.json.codeHash.slice(0, 8)}…${code.json.codeHash.slice(-6)}`}</div>
|
||||
<CopyButton value={code.json.codeHash} />
|
||||
</td>
|
||||
<td className='start together'>
|
||||
{optCode && (
|
||||
optCode.isSome ? t('Available') : t('Not on-chain')
|
||||
)}
|
||||
</td>
|
||||
<td className='button'>
|
||||
<Button
|
||||
icon='trash'
|
||||
onClick={toggleIsForgetOpen}
|
||||
/>
|
||||
{!contractAbi && (
|
||||
<Button
|
||||
icon='upload'
|
||||
label={t('deploy')}
|
||||
onClick={_onShowDeploy}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</StyledTr>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledTr = styled.tr`
|
||||
.codeHash {
|
||||
div {
|
||||
display: inline;
|
||||
|
||||
&:first-child {
|
||||
font-family: monospace;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Code);
|
||||
@@ -0,0 +1,269 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
||||
import type { CodeSubmittableResult } from '@pezkuwi/api-contract/promise/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { CodePromise } from '@pezkuwi/api-contract';
|
||||
import { Button, Dropdown, InputAddress, InputBalance, InputFile, MarkError, Modal, TxButton } from '@pezkuwi/react-components';
|
||||
import { useAccountId, useApi, useFormField, useNonEmptyString, useStepper } from '@pezkuwi/react-hooks';
|
||||
import { Available } from '@pezkuwi/react-query';
|
||||
import { keyring } from '@pezkuwi/ui-keyring';
|
||||
import { BN_ZERO, isNull, isWasm, stringify } from '@pezkuwi/util';
|
||||
|
||||
import { ABI, InputMegaGas, InputName, MessageSignature, Params } from '../shared/index.js';
|
||||
import store from '../store.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import useAbi from '../useAbi.js';
|
||||
import useWeight from '../useWeight.js';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function Upload ({ onClose }: Props): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [accountId, setAccountId] = useAccountId();
|
||||
const [step, nextStep, prevStep] = useStepper();
|
||||
const [[uploadTx, error], setUploadTx] = useState<[SubmittableExtrinsic<'promise'> | null, string | null]>([null, null]);
|
||||
const [constructorIndex, setConstructorIndex] = useState<number>(0);
|
||||
const [value, isValueValid, setValue] = useFormField<BN>(BN_ZERO);
|
||||
const [params, setParams] = useState<unknown[]>([]);
|
||||
const [[wasm, isWasmValid], setWasm] = useState<[Uint8Array | null, boolean]>([null, false]);
|
||||
const [name, isNameValid, setName] = useNonEmptyString();
|
||||
const { abiName, contractAbi, errorText, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi();
|
||||
const weight = useWeight();
|
||||
|
||||
const code = useMemo(
|
||||
() => isAbiValid && isWasmValid && wasm && contractAbi
|
||||
? new CodePromise(api, contractAbi, wasm)
|
||||
: null,
|
||||
[api, contractAbi, isAbiValid, isWasmValid, wasm]
|
||||
);
|
||||
|
||||
const constructOptions = useMemo(
|
||||
() => contractAbi
|
||||
? contractAbi.constructors.map((c, index) => ({
|
||||
info: c.identifier,
|
||||
key: c.identifier,
|
||||
text: (
|
||||
<MessageSignature
|
||||
asConstructor
|
||||
message={c}
|
||||
/>
|
||||
),
|
||||
value: index
|
||||
}))
|
||||
: [],
|
||||
[contractAbi]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
setConstructorIndex(0);
|
||||
}, [constructOptions]);
|
||||
|
||||
useEffect((): void => {
|
||||
setParams([]);
|
||||
}, [contractAbi, constructorIndex]);
|
||||
|
||||
useEffect((): void => {
|
||||
setWasm(
|
||||
contractAbi && isWasm(contractAbi.info.source.wasm)
|
||||
? [contractAbi.info.source.wasm, true]
|
||||
: [null, false]
|
||||
);
|
||||
}, [contractAbi]);
|
||||
|
||||
useEffect((): void => {
|
||||
abiName && setName(abiName);
|
||||
}, [abiName, setName]);
|
||||
|
||||
useEffect((): void => {
|
||||
async function dryRun () {
|
||||
let contract: SubmittableExtrinsic<'promise'> | null = null;
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
if (code && contractAbi?.constructors[constructorIndex]?.method && value && accountId) {
|
||||
const dryRunParams: Parameters<typeof api.call.contractsApi.instantiate> =
|
||||
[
|
||||
accountId,
|
||||
contractAbi?.constructors[constructorIndex].isPayable
|
||||
? api.registry.createType('Balance', value)
|
||||
: api.registry.createType('Balance', BN_ZERO),
|
||||
weight.weightV2,
|
||||
null,
|
||||
{ Upload: api.registry.createType('Raw', wasm) },
|
||||
contractAbi?.constructors[constructorIndex]?.toU8a(params),
|
||||
''
|
||||
];
|
||||
|
||||
const dryRunResult = await api.call.contractsApi.instantiate(...dryRunParams);
|
||||
|
||||
contract = code.tx[contractAbi.constructors[constructorIndex].method]({
|
||||
gasLimit: dryRunResult.gasRequired,
|
||||
storageDepositLimit: dryRunResult.storageDeposit.isCharge ? dryRunResult.storageDeposit.asCharge : null,
|
||||
value: contractAbi?.constructors[constructorIndex].isPayable ? value : undefined
|
||||
}, ...params);
|
||||
}
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
}
|
||||
|
||||
setUploadTx(() => [contract, error]);
|
||||
}
|
||||
|
||||
dryRun().catch((e) => console.error(e));
|
||||
}, [accountId, wasm, api, code, contractAbi, constructorIndex, value, params, weight]);
|
||||
|
||||
const _onAddWasm = useCallback(
|
||||
(wasm: Uint8Array, name: string): void => {
|
||||
setWasm([wasm, isWasm(wasm)]);
|
||||
setName(name.replace('.wasm', '').replace('_', ' '));
|
||||
},
|
||||
[setName]
|
||||
);
|
||||
|
||||
const _onSuccess = useCallback(
|
||||
(result: CodeSubmittableResult): void => {
|
||||
result.blueprint && store.saveCode(result.blueprint.codeHash, {
|
||||
abi: stringify(result.blueprint.abi.json),
|
||||
name: name || '<>',
|
||||
tags: []
|
||||
});
|
||||
result.contract && keyring.saveContract(result.contract.address.toString(), {
|
||||
contract: {
|
||||
abi: stringify(result.contract.abi.json),
|
||||
genesisHash: api.genesisHash.toHex()
|
||||
},
|
||||
name: name || '<>',
|
||||
tags: []
|
||||
});
|
||||
},
|
||||
[api, name]
|
||||
);
|
||||
|
||||
const isSubmittable = !!accountId && (!isNull(name) && isNameValid) && isWasmValid && isAbiSupplied && isAbiValid && !!uploadTx && step === 2;
|
||||
const invalidAbi = isAbiError || !isAbiSupplied;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header={t('Upload & deploy code {{info}}', { replace: { info: `${step}/2` } })}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Modal.Content>
|
||||
{step === 1 && (
|
||||
<>
|
||||
<InputAddress
|
||||
isInput={false}
|
||||
label={t('deployment account')}
|
||||
labelExtra={
|
||||
<Available
|
||||
label={t('transferable')}
|
||||
params={accountId}
|
||||
/>
|
||||
}
|
||||
onChange={setAccountId}
|
||||
type='account'
|
||||
value={accountId}
|
||||
/>
|
||||
<ABI
|
||||
contractAbi={contractAbi}
|
||||
errorText={errorText}
|
||||
isError={invalidAbi}
|
||||
isSupplied={isAbiSupplied}
|
||||
isValid={isAbiValid}
|
||||
label={t('json for either ABI or .contract bundle')}
|
||||
onChange={onChangeAbi}
|
||||
onRemove={onRemoveAbi}
|
||||
withWasm
|
||||
/>
|
||||
{!invalidAbi && contractAbi && (
|
||||
<>
|
||||
{!contractAbi.info.source.wasm.length && (
|
||||
<InputFile
|
||||
isError={!isWasmValid}
|
||||
label={t('compiled contract WASM')}
|
||||
onChange={_onAddWasm}
|
||||
placeholder={wasm && !isWasmValid && t('The code is not recognized as being in valid WASM format')}
|
||||
/>
|
||||
)}
|
||||
<InputName
|
||||
isError={!isNameValid}
|
||||
onChange={setName}
|
||||
value={name || undefined}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{step === 2 && contractAbi && (
|
||||
<>
|
||||
<Dropdown
|
||||
isDisabled={contractAbi.constructors.length <= 1}
|
||||
label={t('deployment constructor')}
|
||||
onChange={setConstructorIndex}
|
||||
options={constructOptions}
|
||||
value={constructorIndex}
|
||||
/>
|
||||
<Params
|
||||
onChange={setParams}
|
||||
params={contractAbi.constructors[constructorIndex].args}
|
||||
registry={contractAbi.registry}
|
||||
/>
|
||||
{contractAbi.constructors[constructorIndex].isPayable && (
|
||||
<InputBalance
|
||||
isError={!isValueValid}
|
||||
isZeroable
|
||||
label={t('value')}
|
||||
onChange={setValue}
|
||||
value={value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<InputMegaGas
|
||||
weight={weight}
|
||||
/>
|
||||
{error && (
|
||||
<MarkError content={error} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
{step === 1
|
||||
? (
|
||||
<Button
|
||||
icon='step-forward'
|
||||
isDisabled={!code || !contractAbi}
|
||||
label={t('Next')}
|
||||
onClick={nextStep}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Button
|
||||
icon='step-backward'
|
||||
label={t('Prev')}
|
||||
onClick={prevStep}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
extrinsic={uploadTx}
|
||||
icon='upload'
|
||||
isDisabled={!isSubmittable}
|
||||
label={t('Deploy')}
|
||||
onClick={onClose}
|
||||
onSuccess={_onSuccess}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Upload);
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import type { Option } from '@pezkuwi/types';
|
||||
import type { PrefabWasmModule } from '@pezkuwi/types/interfaces';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { InfoForInput } from '@pezkuwi/react-components';
|
||||
import { useApi, useCall } from '@pezkuwi/react-hooks';
|
||||
import { isHex } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
codeHash?: string | null;
|
||||
onChange: React.Dispatch<boolean>;
|
||||
}
|
||||
|
||||
function ValidateCode ({ codeHash, onChange }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const { t } = useTranslation();
|
||||
const optCode = useCall<Option<PrefabWasmModule>>((api.query.contracts || api.query.contract).pristineCode || (api.query.contracts || api.query.contract).codeStorage, [codeHash]);
|
||||
const [isValidHex, isValid] = useMemo(
|
||||
(): [boolean, boolean] => {
|
||||
const isValidHex = !!codeHash && isHex(codeHash) && codeHash.length === 66;
|
||||
const isStored = !!optCode && optCode.isSome;
|
||||
const isValid = isValidHex && isStored;
|
||||
|
||||
onChange(isValid);
|
||||
|
||||
return [
|
||||
isValidHex,
|
||||
isValid
|
||||
];
|
||||
},
|
||||
[codeHash, optCode, onChange]
|
||||
);
|
||||
|
||||
if (isValid || !isValidHex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<InfoForInput type='error'>
|
||||
{
|
||||
isValidHex
|
||||
? t('Unable to find on-chain WASM code for the supplied codeHash')
|
||||
: t('The codeHash is not a valid hex hash')
|
||||
}
|
||||
</InfoForInput>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ValidateCode);
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Table } from '@pezkuwi/react-components';
|
||||
|
||||
import contracts from '../store.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Code from './Code.js';
|
||||
|
||||
interface Props {
|
||||
onShowDeploy: (codeHash: string, constructorIndex: number) => void;
|
||||
updated: number;
|
||||
}
|
||||
|
||||
function Codes ({ onShowDeploy }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const headerRef = useRef<[string?, string?, number?][]>([
|
||||
[t('code hashes'), 'start'],
|
||||
[],
|
||||
[],
|
||||
[t('status'), 'start'],
|
||||
[]
|
||||
]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
empty={t('No code hashes available')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{contracts.getAllCode().map((code): React.ReactNode => (
|
||||
<Code
|
||||
code={code}
|
||||
key={code.json.codeHash}
|
||||
onShowDeploy={onShowDeploy}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Codes);
|
||||
Reference in new issue
Block a user