import React, { useContext, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import inspect from 'browser-util-inspect'; import { forAddress } from '@truffle/decoder'; import { useSelector } from 'react-redux'; import * as Codec from '@truffle/codec'; import Spinner from '../../ui/spinner'; import ErrorMessage from '../../ui/error-message'; import fetchWithCache from '../../../helpers/utils/fetch-with-cache'; import { getSelectedAccount, getCurrentChainId } from '../../../selectors'; import { hexToDecimal } from '../../../helpers/utils/conversions.util'; import { I18nContext } from '../../../contexts/i18n'; import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; import { transformTxDecoding } from './transaction-decoding.util'; import { FETCH_PROJECT_INFO_URI, FETCH_SUPPORTED_NETWORKS_URI, } from './constants'; import Address from './components/decoding/address'; import CopyRawData from './components/ui/copy-raw-data'; export default function TransactionDecoding({ to = '', inputData: data = '' }) { const t = useContext(I18nContext); const [tx, setTx] = useState([]); const { address: from } = useSelector(getSelectedAccount); const network = hexToDecimal(useSelector(getCurrentChainId)); const [loading, setLoading] = useState(false); const [hasError, setError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); useEffect(() => { (async () => { setLoading(true); try { const networks = await fetchWithCache(FETCH_SUPPORTED_NETWORKS_URI, { method: 'GET', }); if ( !networks.some( (n) => n.active && Number(n.chainId) === Number(network), ) ) { throw new Error( t('transactionDecodingUnsupportedNetworkError', [network]), ); } const requestUrl = `${FETCH_PROJECT_INFO_URI}?${new URLSearchParams({ to, 'network-id': network, })}`; const response = await fetchWithCache(requestUrl, { method: 'GET' }); const { info: projectInfo } = response; // creating instance of the truffle decoder const decoder = await forAddress(to, { provider: global.ethereumProvider, projectInfo, }); // decode tx input data const decoding = await decoder.decodeTransaction({ from, to, input: data, blockNumber: null, }); // transform tx decoding arguments into tree data const params = transformTxDecoding(decoding?.arguments); setTx(params); setLoading(false); } catch (error) { setLoading(false); setError(true); setErrorMessage(error?.message); } })(); }, [t, from, to, network, data]); // *********************************************************** // component rendering methods // *********************************************************** const renderLeaf = ({ name, kind, typeClass, value }) => { switch (kind) { case 'error': return ( Malformed data ); default: switch (typeClass) { case 'int': return ( {[value.asBN || value.asString].toString()} ); case 'uint': return ( {[value.asBN || value.asString].toString()} ); case 'bytes': return ( {value.asHex} ); case 'array': return (
{name}:
    {value.map((itemValue, index) => { return (
  1. {renderLeaf({ typeClass: itemValue.type?.typeClass, value: itemValue.value, kind: itemValue.kind, })}
  2. ); })}
); case 'address': { const address = value?.asAddress; return (
); } default: return (
                {inspect(new Codec.Format.Utils.Inspect.ResultInspector(value))}
              
); } } }; const renderTree = ( { name, kind, typeClass, type, value, children }, index, ) => { return children ? (
  • {name}:
      {children.map(renderTree)}
  • ) : (
  • {typeClass !== 'array' && !Array.isArray(value) ? ( {name}: ) : null} {renderLeaf({ name, typeClass, type, value, kind })}
  • ); }; const renderTransactionDecoding = () => { if (loading) { return (
    ); } if (hasError) { return (
    ); } return (
      {tx.map(renderTree)}
    ); }; return
    {renderTransactionDecoding()}
    ; } TransactionDecoding.propTypes = { to: PropTypes.string.isRequired, inputData: PropTypes.string.isRequired, };