mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-06-13 18:41: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:
@@ -0,0 +1,115 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
||||
import type { ExtrinsicPayload } from '@pezkuwi/types/interfaces';
|
||||
import type { Inspect } from '@pezkuwi/types/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { Columar, Inspect as DecodedInspect, Output, styled } from '@pezkuwi/react-components';
|
||||
import { u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from './translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
extrinsic?: SubmittableExtrinsic<'promise'> | null;
|
||||
isCall: boolean;
|
||||
payload?: ExtrinsicPayload | null;
|
||||
withData?: boolean;
|
||||
withHash?: boolean;
|
||||
}
|
||||
|
||||
function extract (isCall: boolean, extrinsic?: SubmittableExtrinsic<'promise'> | null, payload?: ExtrinsicPayload | null): [HexString, HexString, Inspect | null] {
|
||||
if (!extrinsic) {
|
||||
return ['0x', '0x', null];
|
||||
}
|
||||
|
||||
const u8a = extrinsic.method.toU8a();
|
||||
let inspect = isCall
|
||||
? extrinsic.method.inspect()
|
||||
: extrinsic.inspect();
|
||||
|
||||
if (payload) {
|
||||
const prev = inspect;
|
||||
|
||||
inspect = payload.inspect();
|
||||
inspect.inner?.map((entry, index) => {
|
||||
if (index === 0) {
|
||||
// replace the method inner
|
||||
entry.inner = prev.inner;
|
||||
entry.outer = undefined;
|
||||
}
|
||||
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
|
||||
// don't use the built-in hash, we only want to convert once
|
||||
return [
|
||||
u8aToHex(u8a),
|
||||
extrinsic.registry.hash(u8a).toHex(),
|
||||
inspect
|
||||
];
|
||||
}
|
||||
|
||||
function Decoded ({ className, extrinsic, isCall, payload, withData = true, withHash = true }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [hex, hash, inspect] = useMemo(
|
||||
() => extract(isCall, extrinsic, payload),
|
||||
[extrinsic, isCall, payload]
|
||||
);
|
||||
|
||||
if (!inspect) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledColumar
|
||||
className={className}
|
||||
isPadded={false}
|
||||
>
|
||||
<Columar.Column>
|
||||
{withData && (
|
||||
<Output
|
||||
isDisabled
|
||||
isTrimmed
|
||||
label={t('encoded call data')}
|
||||
value={hex}
|
||||
withCopy
|
||||
/>
|
||||
)}
|
||||
{withHash && (
|
||||
<Output
|
||||
isDisabled
|
||||
label={t('encoded call hash')}
|
||||
value={hash}
|
||||
withCopy
|
||||
/>
|
||||
)}
|
||||
</Columar.Column>
|
||||
<Columar.Column>
|
||||
<DecodedInspect
|
||||
hex={hex}
|
||||
inspect={inspect}
|
||||
label={t('encoding details')}
|
||||
/>
|
||||
</Columar.Column>
|
||||
</StyledColumar>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledColumar = styled(Columar)`
|
||||
.ui--Column:last-child .ui--Labelled {
|
||||
padding-left: 0.5rem;
|
||||
|
||||
label {
|
||||
left: 2.05rem; /* 3.55 - 1.5 (diff from padding above) */
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Decoded);
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@pezkuwi/api/types';
|
||||
import type { Call, ExtrinsicPayload } from '@pezkuwi/types/interfaces';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { DecodedExtrinsic } from './types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Input, InputExtrinsic, MarkError, styled } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import { Call as CallDisplay } from '@pezkuwi/react-params';
|
||||
import { assert, compactToU8a, isHex, u8aConcat, u8aEq } from '@pezkuwi/util';
|
||||
|
||||
import Decoded from './Decoded.js';
|
||||
import { useTranslation } from './translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
defaultValue?: HexString | null;
|
||||
setLast: (value: DecodedExtrinsic | null) => void;
|
||||
}
|
||||
|
||||
interface ExtrinsicInfo {
|
||||
decoded: SubmittableExtrinsic<'promise'> | null;
|
||||
extrinsicCall: Call | null;
|
||||
extrinsicError: string | null;
|
||||
extrinsicFn: SubmittableExtrinsicFunction<'promise'> | null;
|
||||
extrinsicHex: string | null;
|
||||
extrinsicKey: string;
|
||||
extrinsicPayload: ExtrinsicPayload | null;
|
||||
isCall: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_INFO: ExtrinsicInfo = {
|
||||
decoded: null,
|
||||
extrinsicCall: null,
|
||||
extrinsicError: null,
|
||||
extrinsicFn: null,
|
||||
extrinsicHex: null,
|
||||
extrinsicKey: 'none',
|
||||
extrinsicPayload: null,
|
||||
isCall: true
|
||||
};
|
||||
|
||||
function Decoder ({ className, defaultValue, setLast }: Props): React.ReactElement<Props> {
|
||||
const { encoded } = useParams<{ encoded: string }>();
|
||||
const [initialValue] = useState(() => defaultValue || encoded);
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [{ decoded, extrinsicCall, extrinsicError, extrinsicFn, extrinsicKey, extrinsicPayload, isCall }, setExtrinsicInfo] = useState<ExtrinsicInfo>(DEFAULT_INFO);
|
||||
|
||||
const _setExtrinsicHex = useCallback(
|
||||
(hex: string): void => {
|
||||
try {
|
||||
assert(isHex(hex), 'Expected a hex-encoded call');
|
||||
|
||||
let extrinsicCall: Call;
|
||||
let extrinsicPayload: ExtrinsicPayload | null = null;
|
||||
let decoded: SubmittableExtrinsic<'promise'> | null = null;
|
||||
let isCall = false;
|
||||
|
||||
try {
|
||||
// cater for an extrinsic input
|
||||
const tx = api.tx(hex);
|
||||
|
||||
// ensure that the full data matches here
|
||||
assert(tx.toHex() === hex, 'Cannot decode data as extrinsic, length mismatch');
|
||||
|
||||
decoded = tx;
|
||||
extrinsicCall = api.createType('Call', decoded.method);
|
||||
} catch {
|
||||
try {
|
||||
// attempt to decode as Call
|
||||
extrinsicCall = api.createType('Call', hex);
|
||||
|
||||
const callHex = extrinsicCall.toHex();
|
||||
|
||||
if (callHex === hex) {
|
||||
// all good, we have a call
|
||||
isCall = true;
|
||||
} else if (hex.startsWith(callHex)) {
|
||||
// this could be an un-prefixed payload...
|
||||
const prefixed = u8aConcat(compactToU8a(extrinsicCall.encodedLength), hex);
|
||||
|
||||
extrinsicPayload = api.createType('ExtrinsicPayload', prefixed);
|
||||
|
||||
assert(u8aEq(extrinsicPayload.toU8a(), prefixed), 'Unable to decode data as un-prefixed ExtrinsicPayload');
|
||||
|
||||
extrinsicCall = api.createType('Call', extrinsicPayload.method.toHex());
|
||||
} else {
|
||||
throw new Error('Unable to decode data as Call, length mismatch in supplied data');
|
||||
}
|
||||
} catch {
|
||||
// final attempt, we try this as-is as a (prefixed) payload
|
||||
extrinsicPayload = api.createType('ExtrinsicPayload', hex);
|
||||
|
||||
assert(extrinsicPayload.toHex() === hex, 'Unable to decode input data as Call, Extrinsic or ExtrinsicPayload');
|
||||
|
||||
extrinsicCall = api.createType('Call', extrinsicPayload.method.toHex());
|
||||
}
|
||||
}
|
||||
|
||||
const { method, section } = api.registry.findMetaCall(extrinsicCall.callIndex);
|
||||
const extrinsicFn = api.tx[section][method];
|
||||
const extrinsicKey = extrinsicCall.callIndex.toString();
|
||||
|
||||
if (!decoded) {
|
||||
decoded = extrinsicFn(...extrinsicCall.args);
|
||||
}
|
||||
|
||||
setExtrinsicInfo({ ...DEFAULT_INFO, decoded, extrinsicCall, extrinsicFn, extrinsicHex: hex, extrinsicKey, extrinsicPayload, isCall });
|
||||
setLast({ call: extrinsicCall, fn: extrinsicFn, hex });
|
||||
} catch (e) {
|
||||
setExtrinsicInfo({ ...DEFAULT_INFO, extrinsicError: (e as Error).message });
|
||||
setLast(null);
|
||||
}
|
||||
},
|
||||
[api, setLast]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDiv className={className}>
|
||||
<Input
|
||||
defaultValue={initialValue}
|
||||
isError={!extrinsicFn}
|
||||
label={t('hex-encoded call')}
|
||||
onChange={_setExtrinsicHex}
|
||||
placeholder={t('0x...')}
|
||||
/>
|
||||
{extrinsicError && (
|
||||
<MarkError content={extrinsicError} />
|
||||
)}
|
||||
{extrinsicFn && extrinsicCall && (
|
||||
<>
|
||||
<InputExtrinsic
|
||||
defaultValue={extrinsicFn}
|
||||
isDisabled
|
||||
key={`extrinsicKey:${extrinsicKey}`}
|
||||
label={t('decoded call')}
|
||||
/>
|
||||
<CallDisplay
|
||||
className='details'
|
||||
value={extrinsicCall}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Decoded
|
||||
extrinsic={decoded}
|
||||
isCall={isCall}
|
||||
payload={extrinsicPayload}
|
||||
withData={false}
|
||||
/>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
.ui--Call--toplevel {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ui--Call > .ui--Params.withBorder {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Decoder);
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@pezkuwi/api/types';
|
||||
import type { RawParam } from '@pezkuwi/react-params/types';
|
||||
import type { DecodedExtrinsic } from './types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, InputAddress, MarkError, TxButton } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import { Extrinsic } from '@pezkuwi/react-params';
|
||||
import { BalanceFree } from '@pezkuwi/react-query';
|
||||
|
||||
import Decoded from './Decoded.js';
|
||||
import { useTranslation } from './translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
defaultValue: DecodedExtrinsic | null;
|
||||
}
|
||||
|
||||
interface DefaultExtrinsic {
|
||||
defaultArgs?: RawParam[];
|
||||
defaultFn: SubmittableExtrinsicFunction<'promise'>;
|
||||
}
|
||||
|
||||
function extractDefaults (value: DecodedExtrinsic | null, defaultFn: SubmittableExtrinsicFunction<'promise'>): DefaultExtrinsic {
|
||||
if (!value) {
|
||||
return { defaultFn };
|
||||
}
|
||||
|
||||
return {
|
||||
defaultArgs: value.call.args.map((value) => ({
|
||||
isValid: true,
|
||||
value
|
||||
})),
|
||||
defaultFn: value.fn
|
||||
};
|
||||
}
|
||||
|
||||
function Selection ({ className, defaultValue }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { apiDefaultTxSudo } = useApi();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [extrinsic, setExtrinsic] = useState<SubmittableExtrinsic<'promise'> | null>(null);
|
||||
const [{ defaultArgs, defaultFn }] = useState<DefaultExtrinsic>(() => extractDefaults(defaultValue, apiDefaultTxSudo));
|
||||
|
||||
const _onExtrinsicChange = useCallback(
|
||||
(method?: SubmittableExtrinsic<'promise'>) =>
|
||||
setExtrinsic(() => method || null),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onExtrinsicError = useCallback(
|
||||
(error?: Error | null) =>
|
||||
setError(error ? error.message : null),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<InputAddress
|
||||
label={t('using the selected account')}
|
||||
labelExtra={
|
||||
<BalanceFree
|
||||
label={<label>{t('free balance')}</label>}
|
||||
params={accountId}
|
||||
/>
|
||||
}
|
||||
onChange={setAccountId}
|
||||
type='account'
|
||||
/>
|
||||
<Extrinsic
|
||||
defaultArgs={defaultArgs}
|
||||
defaultValue={defaultFn}
|
||||
label={t('submit the following extrinsic')}
|
||||
onChange={_onExtrinsicChange}
|
||||
onError={_onExtrinsicError}
|
||||
/>
|
||||
<Decoded
|
||||
extrinsic={extrinsic}
|
||||
isCall
|
||||
/>
|
||||
{error && !extrinsic && (
|
||||
<MarkError content={error} />
|
||||
)}
|
||||
<Button.Group>
|
||||
<TxButton
|
||||
extrinsic={extrinsic}
|
||||
icon='sign-in-alt'
|
||||
isUnsigned
|
||||
label={t('Submit Unsigned')}
|
||||
withSpinner
|
||||
/>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
extrinsic={extrinsic}
|
||||
icon='sign-in-alt'
|
||||
label={t('Submit Transaction')}
|
||||
/>
|
||||
</Button.Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Selection);
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AppProps as Props, TabItem } from '@pezkuwi/react-components/types';
|
||||
import type { DecodedExtrinsic } from './types.js';
|
||||
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Route, Routes } from 'react-router';
|
||||
|
||||
import { Tabs } from '@pezkuwi/react-components';
|
||||
|
||||
import Decoder from './Decoder.js';
|
||||
import Submission from './Submission.js';
|
||||
import { useTranslation } from './translate.js';
|
||||
|
||||
function createItemsRef (t: (key: string, options?: { replace: Record<string, unknown> }) => string): TabItem[] {
|
||||
return [
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'create',
|
||||
text: t('Submission')
|
||||
},
|
||||
{
|
||||
hasParams: true,
|
||||
name: 'decode',
|
||||
text: t('Decode')
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function ExtrinsicsApp ({ basePath }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [decoded, setDecoded] = useState<DecodedExtrinsic | null>(null);
|
||||
const itemsRef = useRef(createItemsRef(t));
|
||||
|
||||
return (
|
||||
<main className='extrinsics--App'>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={itemsRef.current}
|
||||
/>
|
||||
<Routes>
|
||||
<Route path={basePath}>
|
||||
<Route
|
||||
element={
|
||||
<Decoder
|
||||
defaultValue={decoded?.hex}
|
||||
setLast={setDecoded}
|
||||
/>
|
||||
}
|
||||
path='decode/:encoded?'
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Submission defaultValue={decoded} />
|
||||
}
|
||||
index
|
||||
/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export { ExtrinsicsApp };
|
||||
|
||||
export default React.memo(ExtrinsicsApp);
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useTranslation as useTranslationBase } from 'react-i18next';
|
||||
|
||||
export function useTranslation (): { t: (key: string, options?: { replace: Record<string, unknown> }) => string } {
|
||||
return useTranslationBase('app-extrinsics');
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsicFunction } from '@pezkuwi/api/types';
|
||||
import type { Call } from '@pezkuwi/types/interfaces';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
export interface DecodedExtrinsic {
|
||||
call: Call;
|
||||
hex: HexString;
|
||||
fn: SubmittableExtrinsicFunction<'promise'>;
|
||||
}
|
||||
Reference in New Issue
Block a user