Skip to content

Commit

Permalink
Extrinsic inspection (polkadot-js#6948)
Browse files Browse the repository at this point in the history
* Extrinsic inspection

* typing

* as Call

* Correct import

* Adjust

* Decoded component

* Cleanups

* Dedupe

* Adjust
  • Loading branch information
jacogr authored Feb 9, 2022
1 parent c22fe8c commit 1b206a9
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 100 deletions.
95 changes: 95 additions & 0 deletions packages/page-extrinsics/src/Decoded.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2017-2022 @polkadot/app-extrinsics authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { Inspect } from '@polkadot/types/types';

import React, { useMemo } from 'react';
import styled from 'styled-components';

import { Columar, Output } from '@polkadot/react-components';
import { u8aToHex } from '@polkadot/util';

import DecodedInspect from './DecodedInspect';
import { useTranslation } from './translate';

interface Props {
className?: string;
extrinsic?: SubmittableExtrinsic<'promise'> | null;
withData?: boolean;
withHash?: boolean;
}

function extract (extrinsic?: SubmittableExtrinsic<'promise'> | null): [string, string, Inspect | null] {
if (!extrinsic) {
return ['0x', '0x', null];
}

const u8a = extrinsic.method.toU8a();

// don't use the built-in hash, we only want to convert once
return [
u8aToHex(u8a),
extrinsic.registry.hash(u8a).toHex(),
// 0x410284009a81870b7ba4774b2a7efc31a635ec4042088d97ecbdaaf9552c3e21f74aae5201122fea94623d7aecda9870009506865a666d8a962aa2b58f12d5b512258f1a0aab569d7982bfd616c90d7ccf6960dadb29c5de46d2da73a159f5c6bbd5206f8b85029c00040300429599ba5f521844f2332524dec987f9cfb116a2570f83b2417184af0c74ab130700743ba40b
extrinsic.isSigned
? extrinsic.inspect()
: extrinsic.method.inspect()
];
}

function Decoded ({ className, extrinsic, withData = true, withHash = true }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();

const [hex, hash, inspect] = useMemo(
() => extract(extrinsic),
[extrinsic]
);

if (!inspect) {
return null;
}

return (
<Columar
className={className}
isPadded={false}
>
<Columar.Column>
{withData && (
<Output
isDisabled
isTrimmed
label={t<string>('encoded call data')}
value={hex}
withCopy
/>
)}
{withHash && (
<Output
isDisabled
label={t<string>('encoded call hash')}
value={hash}
withCopy
/>
)}
</Columar.Column>
<Columar.Column>
<DecodedInspect
inspect={inspect}
label={t<string>('encoded call details')}
/>
</Columar.Column>
</Columar>
);
}

export default React.memo(styled(Decoded)`
.ui--Column:last-child .ui--Labelled {
padding-left: 0.5rem;
label {
left: 2.05rem; /* 3.55 - 1.5 (diff from padding above) */
}
}
`);
79 changes: 79 additions & 0 deletions packages/page-extrinsics/src/DecodedInspect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2017-2022 @polkadot/app-extrinsics authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Inspect } from '@polkadot/types/types';

import React, { useMemo } from 'react';
import styled from 'styled-components';

import { Output } from '@polkadot/react-components';
import { u8aToHex } from '@polkadot/util';

interface Props {
className?: string;
inspect?: Inspect | null;
label: React.ReactNode;
}

interface Inspected {
name: string;
value: string;
}

function formatInspect ({ inner, name = '', value }: Inspect, result: Inspected[] = []): Inspected[] {
if (value && value.length) {
result.push({ name, value: u8aToHex(value, undefined, false) });
}

for (let i = 0; i < inner.length; i++) {
formatInspect(inner[i], result);
}

return result;
}

function DecodedInspect ({ className, inspect, label }: Props): React.ReactElement<Props> | null {
const formatted = useMemo(
() => inspect && formatInspect(inspect),
[inspect]
);

if (!formatted) {
return null;
}

return (
<Output
className={className}
isDisabled
label={label}
>
<table>
<tbody>
{formatted.map(({ name, value }, i) => (
<tr key={i}>
<td>{name}</td>
<td>{value}</td>
</tr>
))}
</tbody>
</table>
</Output>
);
}

export default React.memo(styled(DecodedInspect)`
table {
tr {
td:first-child {
color: var(--color-label);
padding-right: 0.5em;
text-align: right;
white-space: nowrap;
}
td:last-child {
font: var(--font-mono);
}
}
`);
40 changes: 23 additions & 17 deletions packages/page-extrinsics/src/Decoder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,42 @@ import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@polkad
import type { Call } from '@polkadot/types/interfaces';

import React, { useCallback, useState } from 'react';
import styled from 'styled-components';

import { Button, Call as CallDisplay, Input, InputAddress, InputExtrinsic, MarkError, Output, TxButton } from '@polkadot/react-components';
import { Button, Call as CallDisplay, Input, InputAddress, InputExtrinsic, MarkError, TxButton } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { BalanceFree } from '@polkadot/react-query';
import { assert, isHex } from '@polkadot/util';

import Decoded from './Decoded';
import { useTranslation } from './translate';

interface Props {
className?: string;
}

interface ExtrinsicInfo {
decoded: SubmittableExtrinsic<'promise'> | null;
extrinsic: SubmittableExtrinsic<'promise'> | null;
extrinsicCall: Call | null;
extrinsicError: string | null;
extrinsicFn: SubmittableExtrinsicFunction<'promise'> | null;
extrinsicHash: string | null;
extrinsicHex: string | null;
}

const DEFAULT_INFO: ExtrinsicInfo = {
decoded: null,
extrinsic: null,
extrinsicCall: null,
extrinsicError: null,
extrinsicFn: null,
extrinsicHash: null,
extrinsicHex: null
};

function Decoder ({ className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [{ extrinsic, extrinsicCall, extrinsicError, extrinsicFn, extrinsicHash }, setExtrinsicInfo] = useState<ExtrinsicInfo>(DEFAULT_INFO);
const [{ decoded, extrinsic, extrinsicCall, extrinsicError, extrinsicFn }, setExtrinsicInfo] = useState<ExtrinsicInfo>(DEFAULT_INFO);
const [accountId, setAccountId] = useState<string | null>(null);

const _setExtrinsicHex = useCallback(
Expand All @@ -47,21 +49,25 @@ function Decoder ({ className }: Props): React.ReactElement<Props> {
assert(isHex(extrinsicHex), 'Expected a hex-encoded call');

let extrinsicCall: Call;
let decoded: SubmittableExtrinsic<'promise'> | null = null;

try {
// cater for an extrinsic input...
extrinsicCall = api.createType('Call', api.tx(extrinsicHex).method);
decoded = api.tx(extrinsicHex);
extrinsicCall = api.createType('Call', decoded.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];

const extrinsic = extrinsicFn(...extrinsicCall.args);

setExtrinsicInfo({ ...DEFAULT_INFO, extrinsic, extrinsicCall, extrinsicFn, extrinsicHash, extrinsicHex });
if (!decoded) {
decoded = extrinsic;
}

setExtrinsicInfo({ ...DEFAULT_INFO, decoded, extrinsic, extrinsicCall, extrinsicFn, extrinsicHex });
} catch (e) {
setExtrinsicInfo({ ...DEFAULT_INFO, extrinsicError: (e as Error).message });
}
Expand Down Expand Up @@ -93,14 +99,10 @@ function Decoder ({ className }: Props): React.ReactElement<Props> {
/>
</>
)}
{extrinsicHash && (
<Output
isDisabled
label='encoded call hash'
value={extrinsicHash}
withCopy
/>
)}
<Decoded
extrinsic={decoded}
withData={false}
/>
<InputAddress
label={t<string>('using the selected account')}
labelExtra={
Expand Down Expand Up @@ -131,4 +133,8 @@ function Decoder ({ className }: Props): React.ReactElement<Props> {
);
}

export default React.memo(Decoder);
export default React.memo(styled(Decoder)`
.ui--Extrinsic--toplevel {
margin-top: 0;
}
`);
34 changes: 4 additions & 30 deletions packages/page-extrinsics/src/Submission.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

import type { SubmittableExtrinsic } from '@polkadot/api/types';

import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useState } from 'react';

import { Button, Extrinsic, InputAddress, MarkError, Output, TxButton } from '@polkadot/react-components';
import { Button, Extrinsic, InputAddress, MarkError, TxButton } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { BalanceFree } from '@polkadot/react-query';
import { u8aToHex } from '@polkadot/util';

import Decoded from './Decoded';
import { useTranslation } from './translate';

interface Props {
Expand All @@ -33,20 +33,6 @@ function Selection ({ className }: Props): React.ReactElement<Props> {
[]
);

const [extrinsicHex, extrinsicHash] = useMemo(
(): [string, string] => {
if (!extrinsic) {
return ['0x', '0x'];
}

const u8a = extrinsic.method.toU8a();

// don't use the built-in hash, we only want to convert once
return [u8aToHex(u8a), extrinsic.registry.hash(u8a).toHex()];
},
[extrinsic]
);

return (
<div className={className}>
<InputAddress
Expand All @@ -66,19 +52,7 @@ function Selection ({ className }: Props): React.ReactElement<Props> {
onChange={_onExtrinsicChange}
onError={_onExtrinsicError}
/>
<Output
isDisabled
isTrimmed
label={t<string>('encoded call data')}
value={extrinsicHex}
withCopy
/>
<Output
isDisabled
label={t<string>('encoded call hash')}
value={extrinsicHash}
withCopy
/>
<Decoded extrinsic={extrinsic} />
{error && !extrinsic && (
<MarkError content={error} />
)}
Expand Down
Loading

0 comments on commit 1b206a9

Please sign in to comment.