mirror of
https://github.com/pezkuwichain/pezkuwi-extension.git
synced 2026-07-24 07:55:40 +00:00
181 lines
5.3 KiB
TypeScript
181 lines
5.3 KiB
TypeScript
// 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 (
|
|
<tr>
|
|
<td className='label'>{t('method data')}</td>
|
|
<td className='data'>{data}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<tr>
|
|
<td className='label'>{t('method')}</td>
|
|
<td className='data'>
|
|
<details>
|
|
<summary>{method.section}.{method.method}{
|
|
method.meta
|
|
? `(${method.meta.args.map(({ name }) => name).join(', ')})`
|
|
: ''
|
|
}</summary>
|
|
<pre>{JSON.stringify(args, null, 2)}</pre>
|
|
</details>
|
|
</td>
|
|
</tr>
|
|
{method.meta && (
|
|
<tr>
|
|
<td className='label'>{t('info')}</td>
|
|
<td className='data'>
|
|
<details>
|
|
<summary>{method.meta.docs.map((d) => d.toString().trim()).join(' ')}</summary>
|
|
</details>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
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<Props> {
|
|
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<string, unknown>;
|
|
const assetId = humanReadablePayload['assetId'];
|
|
const humanReadableAssetId = getHumanReadableAssetId(assetId);
|
|
|
|
return (
|
|
<Table
|
|
className={className}
|
|
isFull
|
|
>
|
|
<tr>
|
|
<td className='label'>{t('from')}</td>
|
|
<td className='data'>{url}</td>
|
|
</tr>
|
|
<tr>
|
|
<td className='label'>{chain ? t('chain') : t('genesis')}</td>
|
|
<td className='data'>{chain ? chain.name : genesisHash}</td>
|
|
</tr>
|
|
<tr>
|
|
<td className='label'>{t('version')}</td>
|
|
<td className='data'>{specVersion.toNumber()}</td>
|
|
</tr>
|
|
<tr>
|
|
<td className='label'>{t('nonce')}</td>
|
|
<td className='data'>{formatNumber(payload.nonce)}</td>
|
|
</tr>
|
|
{!payload.tip.isEmpty && (
|
|
<tr>
|
|
<td className='label'>{t('tip')}</td>
|
|
<td className='data'>{formatNumber(payload.tip)}</td>
|
|
</tr>
|
|
)}
|
|
{!!assetId && (
|
|
<tr>
|
|
<td className='label'>{t('assetId')}</td>
|
|
<td className='data'>
|
|
<details>
|
|
<summary>{humanReadableAssetId || '{...}'}</summary>
|
|
<pre>{JSON.stringify(assetId, null, 2)}</pre>
|
|
</details>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{renderMethod(method, decoded, t)}
|
|
<tr>
|
|
<td className='label'>{t('lifetime')}</td>
|
|
<td className='data'>{mortalityAsString(payload.era, blockNumber, t)}</td>
|
|
</tr>
|
|
</Table>
|
|
);
|
|
}
|
|
|
|
export default React.memo(Extrinsic);
|