diff --git a/packages/page-extrinsics/src/Decoded.tsx b/packages/page-extrinsics/src/Decoded.tsx index 26c3d38ca2..d3fb3da922 100644 --- a/packages/page-extrinsics/src/Decoded.tsx +++ b/packages/page-extrinsics/src/Decoded.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { SubmittableExtrinsic } from '@polkadot/api/types'; +import type { ExtrinsicPayload } from '@polkadot/types/interfaces'; import type { Inspect } from '@polkadot/types/types'; import type { HexString } from '@polkadot/util/types'; @@ -17,33 +18,50 @@ 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): [HexString, HexString, Inspect | null] { +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(), - isCall - ? extrinsic.method.inspect() - : extrinsic.inspect() + inspect ]; } -function Decoded ({ className, extrinsic, isCall, withData = true, withHash = true }: Props): React.ReactElement | null { +function Decoded ({ className, extrinsic, isCall, payload, withData = true, withHash = true }: Props): React.ReactElement | null { const { t } = useTranslation(); const [hex, hash, inspect] = useMemo( - () => extract(isCall, extrinsic), - [extrinsic, isCall] + () => extract(isCall, extrinsic, payload), + [extrinsic, isCall, payload] ); if (!inspect) { diff --git a/packages/page-extrinsics/src/Decoder.tsx b/packages/page-extrinsics/src/Decoder.tsx index fe5d4f44d5..ff0b99910e 100644 --- a/packages/page-extrinsics/src/Decoder.tsx +++ b/packages/page-extrinsics/src/Decoder.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@polkadot/api/types'; -import type { Call } from '@polkadot/types/interfaces'; +import type { Call, ExtrinsicPayload } from '@polkadot/types/interfaces'; import type { HexString } from '@polkadot/util/types'; import type { DecodedExtrinsic } from './types'; @@ -12,7 +12,7 @@ import styled from 'styled-components'; import { Call as CallDisplay, Input, InputExtrinsic, MarkError } from '@polkadot/react-components'; import { useApi } from '@polkadot/react-hooks'; -import { assert, isHex } from '@polkadot/util'; +import { assert, compactToU8a, isHex, u8aConcat, u8aEq } from '@polkadot/util'; import Decoded from './Decoded'; import { useTranslation } from './translate'; @@ -30,6 +30,7 @@ interface ExtrinsicInfo { extrinsicFn: SubmittableExtrinsicFunction<'promise'> | null; extrinsicHex: string | null; extrinsicKey: string; + extrinsicPayload: ExtrinsicPayload | null; isCall: boolean; } @@ -40,6 +41,7 @@ const DEFAULT_INFO: ExtrinsicInfo = { extrinsicFn: null, extrinsicHex: null, extrinsicKey: 'none', + extrinsicPayload: null, isCall: true }; @@ -48,7 +50,7 @@ function Decoder ({ className, defaultValue, setLast }: Props): React.ReactEleme const [initialValue] = useState(() => defaultValue || encoded); const { t } = useTranslation(); const { api } = useApi(); - const [{ decoded, extrinsicCall, extrinsicError, extrinsicFn, extrinsicKey, isCall }, setExtrinsicInfo] = useState(DEFAULT_INFO); + const [{ decoded, extrinsicCall, extrinsicError, extrinsicFn, extrinsicKey, extrinsicPayload, isCall }, setExtrinsicInfo] = useState(DEFAULT_INFO); const _setExtrinsicHex = useCallback( (hex: string): void => { @@ -56,8 +58,9 @@ function Decoder ({ className, defaultValue, setLast }: Props): React.ReactEleme assert(isHex(hex), 'Expected a hex-encoded call'); let extrinsicCall: Call; + let extrinsicPayload: ExtrinsicPayload | null = null; let decoded: SubmittableExtrinsic<'promise'> | null = null; - let isCall = true; + let isCall = false; try { // cater for an extrinsic input @@ -68,13 +71,36 @@ function Decoder ({ className, defaultValue, setLast }: Props): React.ReactEleme decoded = tx; extrinsicCall = api.createType('Call', decoded.method); - isCall = false; - } catch (e) { - // attempt to decode as Call - extrinsicCall = api.createType('Call', hex); + } catch { + try { + // attempt to decode as Call + extrinsicCall = api.createType('Call', hex); - // ensure we used all bytes (as we did above) - assert(extrinsicCall.toHex() === hex, 'Unable to decode data as Call, length mismatch in supplied data'); + 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); @@ -85,7 +111,7 @@ function Decoder ({ className, defaultValue, setLast }: Props): React.ReactEleme decoded = extrinsicFn(...extrinsicCall.args); } - setExtrinsicInfo({ ...DEFAULT_INFO, decoded, extrinsicCall, extrinsicFn, extrinsicHex: hex, extrinsicKey, isCall }); + 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 }); @@ -124,6 +150,7 @@ function Decoder ({ className, defaultValue, setLast }: Props): React.ReactEleme