// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { Chain } from '@pezkuwi/extension-chains/types'; import type { Call, ExtrinsicEra, ExtrinsicPayload } from '@pezkuwi/types/interfaces'; import type { AnyJson, SignerPayloadJSON } from '@pezkuwi/types/types'; import type { BN } from '@pezkuwi/util'; import type { TFunction } from '../../hooks/useTranslation.js'; import { convertMultilocationToUrl } from '@paraspell/xcm-analyser'; import React, { useMemo, useRef } from 'react'; import { bnToBn, formatNumber } from '@pezkuwi/util'; import { Table } from '../../components/index.js'; import { useMetadata, useTranslation } from '../../hooks/index.js'; interface Decoded { args: AnyJson | null; method: Call | null; } interface Props { className?: string; payload: ExtrinsicPayload; request: SignerPayloadJSON; url: string; } function displayDecodeVersion (message: string, chain: Chain, specVersion: BN): string { return `${message}: chain=${chain.name}, specVersion=${chain.specVersion.toString()} (request specVersion=${specVersion.toString()})`; } function decodeMethod (data: string, chain: Chain, specVersion: BN): Decoded { let args: AnyJson | null = null; let method: Call | null = null; try { if (specVersion.eqn(chain.specVersion)) { method = chain.registry.createType('Call', data); args = (method.toHuman() as { args: AnyJson }).args; } else { console.log(displayDecodeVersion('Outdated metadata to decode', chain, specVersion)); } } catch (error) { console.error(`${displayDecodeVersion('Error decoding method', chain, specVersion)}:: ${(error as Error).message}`); args = null; method = null; } return { args, method }; } function renderMethod (data: string, { args, method }: Decoded, t: TFunction): React.ReactNode { if (!args || !method) { return ( {t('method data')} {data} ); } return ( <> {t('method')}
{method.section}.{method.method}{ method.meta ? `(${method.meta.args.map(({ name }) => name).join(', ')})` : '' }
{JSON.stringify(args, null, 2)}
{method.meta && ( {t('info')}
{method.meta.docs.map((d) => d.toString().trim()).join(' ')}
)} ); } function mortalityAsString (era: ExtrinsicEra, hexBlockNumber: string, t: TFunction): string { if (era.isImmortalEra) { return t('immortal'); } const blockNumber = bnToBn(hexBlockNumber); const mortal = era.asMortalEra; return t('mortal, valid from {{birth}} to {{death}}', { replace: { birth: formatNumber(mortal.birth(blockNumber)), death: formatNumber(mortal.death(blockNumber)) } }); } function getHumanReadableAssetId (assetId: unknown): string | undefined { try { return convertMultilocationToUrl(assetId); } catch (_) { return undefined; } } function Extrinsic ({ className, payload, request: { blockNumber, genesisHash, method, specVersion: hexSpec }, url }: Props): React.ReactElement { const { t } = useTranslation(); const chain = useMetadata(genesisHash); const specVersion = useRef(bnToBn(hexSpec)).current; const decoded = useMemo( () => chain && chain.hasMetadata ? decodeMethod(method, chain, specVersion) : { args: null, method: null }, [method, chain, specVersion] ); const humanReadablePayload = payload.toHuman() as Record; const assetId = humanReadablePayload['assetId']; const humanReadableAssetId = getHumanReadableAssetId(assetId); return ( {!payload.tip.isEmpty && ( )} {!!assetId && ( )} {renderMethod(method, decoded, t)}
{t('from')} {url}
{chain ? t('chain') : t('genesis')} {chain ? chain.name : genesisHash}
{t('version')} {specVersion.toNumber()}
{t('nonce')} {formatNumber(payload.nonce)}
{t('tip')} {formatNumber(payload.tip)}
{t('assetId')}
{humanReadableAssetId || '{...}'}
{JSON.stringify(assetId, null, 2)}
{t('lifetime')} {mortalityAsString(payload.era, blockNumber, t)}
); } export default React.memo(Extrinsic);