Call decoder (#5435)

* Call decoder

* Allow for extrinsics to be decoded
This commit is contained in:
Jaco 2021-06-03 17:47:16 +03:00 committed by GitHub
parent 6e5d2efbdd
commit 42c5e4ae6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 147 additions and 49 deletions

View File

@ -1,31 +0,0 @@
// Copyright 2017-2021 @polkadot/react-query authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveBalancesAll } from '@polkadot/api-derive/types';
import React from 'react';
import { InputBalance } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
interface Props {
className?: string;
label?: React.ReactNode;
params?: any;
}
function BalanceDisplay ({ className = '', label, params }: Props): React.ReactElement<Props> {
const { api } = useApi();
const allBalances = useCall<DeriveBalancesAll>(api.derive.balances?.all, [params]);
return (
<InputBalance
className={className}
defaultValue={allBalances?.freeBalance}
isDisabled
label={label}
/>
);
}
export default React.memo(BalanceDisplay);

View File

@ -0,0 +1,102 @@
// Copyright 2017-2021 @polkadot/app-extrinsics authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsicFunction } from '@polkadot/api/types';
import type { Call } from '@polkadot/types/interfaces';
import React, { useCallback, useState } from 'react';
import { Call as CallDisplay, Input, InputExtrinsic, MarkError, Output } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { assert, isHex } from '@polkadot/util';
import { useTranslation } from './translate';
interface Props {
className?: string;
}
interface ExtrinsicInfo {
extrinsicCall: Call | null;
extrinsicError: string | null;
extrinsicFn: SubmittableExtrinsicFunction<'promise'> | null;
extrinsicHash: string | null;
extrinsicHex: string | null;
}
const DEFAULT_INFO: ExtrinsicInfo = {
extrinsicCall: null,
extrinsicError: null,
extrinsicFn: null,
extrinsicHash: null,
extrinsicHex: null
};
function Decoder ({ className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [{ extrinsicCall, extrinsicError, extrinsicFn, extrinsicHash }, setExtrinsicInfo] = useState<ExtrinsicInfo>(DEFAULT_INFO);
const _setExtrinsicHex = useCallback(
(extrinsicHex: string): void => {
try {
assert(isHex(extrinsicHex), 'Expected a hex-encoded call');
let extrinsicCall: Call;
try {
// cater for an extrinsic input...
extrinsicCall = api.createType('Call', api.tx(extrinsicHex).method);
} catch (e) {
extrinsicCall = api.createType('Call', extrinsicHex);
}
const extrinsicHash = extrinsicCall.hash.toHex();
const { method, section } = api.registry.findMetaCall(extrinsicCall.callIndex);
const extrinsicFn = api.tx[section][method];
setExtrinsicInfo({ ...DEFAULT_INFO, extrinsicCall, extrinsicFn, extrinsicHash, extrinsicHex });
} catch (e) {
setExtrinsicInfo({ ...DEFAULT_INFO, extrinsicError: (e as Error).message });
}
},
[api]
);
return (
<div className={className}>
<Input
isError={!extrinsicFn}
label={t<string>('hex-encoded call')}
onChange={_setExtrinsicHex}
placeholder={t<string>('0x...')}
/>
{extrinsicError && (
<MarkError content={extrinsicError} />
)}
{extrinsicFn && extrinsicCall && (
<>
<InputExtrinsic
defaultValue={extrinsicFn}
isDisabled
label={t<string>('decoded call')}
/>
<CallDisplay
className='details'
value={extrinsicCall}
/>
</>
)}
{extrinsicHash && (
<Output
isDisabled
label='encoded call hash'
value={extrinsicHash}
withCopy
/>
)}
</div>
);
}
export default React.memo(Decoder);

View File

@ -12,7 +12,11 @@ import { u8aToHex } from '@polkadot/util';
import { useTranslation } from './translate';
function Selection (): React.ReactElement {
interface Props {
className?: string;
}
function Selection ({ className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { apiDefaultTxSudo } = useApi();
const [accountId, setAccountId] = useState<string | null>(null);
@ -44,7 +48,7 @@ function Selection (): React.ReactElement {
);
return (
<div className='extrinsics--Selection'>
<div className={className}>
<InputAddress
label={t<string>('using the selected account')}
labelExtra={

View File

@ -4,20 +4,28 @@
import type { AppProps as Props } from '@polkadot/react-components/types';
import React, { useRef } from 'react';
import { Route, Switch } from 'react-router';
import { Tabs } from '@polkadot/react-components';
import Selection from './Selection';
import Decoder from './Decoder';
import Submission from './Submission';
import { useTranslation } from './translate';
function ExtrinsicsApp ({ basePath }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const itemsRef = useRef([{
isRoot: true,
name: 'create',
text: t<string>('Submission')
}]);
const itemsRef = useRef([
{
isRoot: true,
name: 'create',
text: t<string>('Submission')
},
{
name: 'decode',
text: t<string>('Decode')
}
]);
return (
<main className='extrinsics--App'>
@ -25,7 +33,12 @@ function ExtrinsicsApp ({ basePath }: Props): React.ReactElement<Props> {
basePath={basePath}
items={itemsRef.current}
/>
<Selection />
<Switch>
<Route path={`${basePath}/decode`}><Decoder /></Route>
<Route>
<Submission />
</Route>
</Switch>
</main>
);
}

View File

@ -13,13 +13,15 @@ import Dropdown from '../Dropdown';
interface Props {
api: ApiPromise;
className?: string;
defaultValue?: string;
isDisabled?: boolean;
isError?: boolean;
onChange: (value: SubmittableExtrinsicFunction<'promise'>) => void;
onChange?: (value: SubmittableExtrinsicFunction<'promise'>) => void;
options: DropdownOptions;
value: SubmittableExtrinsicFunction<'promise'>;
}
function SelectMethod ({ api, className = '', isError, onChange, options, value }: Props): React.ReactElement<Props> | null {
function SelectMethod ({ api, className = '', defaultValue, isDisabled, isError, onChange, options, value }: Props): React.ReactElement<Props> | null {
const transform = useCallback(
(method: string): SubmittableExtrinsicFunction<'promise'> =>
api.tx[value.section][method],
@ -33,6 +35,8 @@ function SelectMethod ({ api, className = '', isError, onChange, options, value
return (
<Dropdown
className={`ui--DropdownLinked-Items ${className}`}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
onChange={onChange}
options={options}

View File

@ -11,17 +11,19 @@ import Dropdown from '../Dropdown';
interface Props {
className?: string;
defaultValue?: string;
isDisabled?: boolean;
isError?: boolean;
onChange: (value: string) => void;
onChange?: (value: string) => void;
options: DropdownOptions;
value: SubmittableExtrinsicFunction<'promise'>;
}
function SelectSection ({ className = '', defaultValue, isError, onChange, options, value }: Props): React.ReactElement<Props> {
function SelectSection ({ className = '', defaultValue, isDisabled, isError, onChange, options, value }: Props): React.ReactElement<Props> {
return (
<Dropdown
className={`ui--DropdownLinked-Sections ${className}`}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
onChange={onChange}
options={options}

View File

@ -22,11 +22,11 @@ interface Props {
isError?: boolean;
isPrivate?: boolean;
label: React.ReactNode;
onChange: (value: SubmittableExtrinsicFunction<'promise'>) => void;
onChange?: (value: SubmittableExtrinsicFunction<'promise'>) => void;
withLabel?: boolean;
}
function InputExtrinsic ({ className = '', defaultValue, help, label, onChange, withLabel }: Props): React.ReactElement<Props> {
function InputExtrinsic ({ className = '', defaultValue, help, isDisabled, label, onChange, withLabel }: Props): React.ReactElement<Props> {
const { api } = useApi();
const [optionsMethod, setOptionsMethod] = useState<DropdownOptions>(() => methodOptions(api, defaultValue.section));
const [optionsSection] = useState<DropdownOptions>(() => sectionOptions(api));
@ -40,7 +40,7 @@ function InputExtrinsic ({ className = '', defaultValue, help, label, onChange,
// set this via callback, since the we are setting a function (alternatively... we have issues)
setValue((): SubmittableExtrinsicFunction<'promise'> => newValue);
onChange(newValue);
onChange && onChange(newValue);
},
[onChange, value]
);
@ -68,14 +68,18 @@ function InputExtrinsic ({ className = '', defaultValue, help, label, onChange,
>
<SelectSection
className='small'
onChange={_onSectionChange}
defaultValue={isDisabled ? value.section : undefined}
isDisabled={isDisabled}
onChange={isDisabled ? undefined : _onSectionChange}
options={optionsSection}
value={value}
/>
<SelectMethod
api={api}
className='large'
onChange={_onKeyChange}
defaultValue={isDisabled ? value.method : undefined}
isDisabled={isDisabled}
onChange={isDisabled ? undefined : _onKeyChange}
options={optionsMethod}
value={value}
/>