// Copyright 2017-2026 @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 { 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(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 ( {extrinsicError && ( )} {extrinsicFn && extrinsicCall && ( <> )} ); } const StyledDiv = styled.div` .ui--Call--toplevel { margin-top: 0; } .ui--Call > .ui--Params.withBorder { padding-left: 2rem; } `; export default React.memo(Decoder);