// Copyright 2017-2026 @pezkuwi/app-staking authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { ApiPromise } from '@pezkuwi/api'; import type { ContractPromise } from '@pezkuwi/api-contract'; import type { ContractCallOutcome } from '@pezkuwi/api-contract/types'; import type { SignedBlockExtended } from '@pezkuwi/api-derive/types'; import type { ContractLink } from './types.js'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Table } from '@pezkuwi/react-components'; import { useApi, useCall } from '@pezkuwi/react-hooks'; import { formatNumber } from '@pezkuwi/util'; import { useTranslation } from '../translate.js'; import Call from './Call.js'; import Contract from './Contract.js'; import { getContractForAddress } from './util.js'; export interface Props { contracts: string[]; updated: number; } interface Indexes { contractIndex: number; messageIndex: number; onCallResult?: (messageIndex: number, result?: ContractCallOutcome) => void; } function filterContracts (api: ApiPromise, keyringContracts: string[] = []): ContractPromise[] { return keyringContracts .map((address) => getContractForAddress(api, address.toString())) .filter((contract): contract is ContractPromise => !!contract); } function ContractsTable ({ contracts: keyringContracts }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const newBlock = useCall(api.derive.chain.subscribeNewBlocks); const [{ contractIndex, messageIndex, onCallResult }, setIndexes] = useState({ contractIndex: 0, messageIndex: 0 }); const [isCallOpen, setIsCallOpen] = useState(false); const [contractLinks, setContractLinks] = useState>({}); const headerRef = useRef<[string?, string?, number?][]>([ [t('contracts'), 'start'], [undefined, undefined, 3], [t('status'), 'start'], [] ]); useEffect((): void => { if (newBlock) { const exts = newBlock.block.extrinsics .filter(({ method }) => api.tx.contracts.call.is(method)) .map(({ args }): ContractLink | null => { const contractId = keyringContracts.find((a) => args[0].eq(a)); if (!contractId) { return null; } return { blockHash: newBlock.block.header.hash.toHex(), blockNumber: formatNumber(newBlock.block.header.number), contractId }; }) .filter((value): value is ContractLink => !!value); exts.length && setContractLinks((links): Record => { exts.forEach((value): void => { links[value.contractId] = [value].concat(links[value.contractId] || []).slice(0, 3); }); return { ...links }; }); } }, [api, keyringContracts, newBlock]); const contracts = useMemo( () => filterContracts(api, keyringContracts), [api, keyringContracts] ); const _toggleCall = useCallback( () => setIsCallOpen((isCallOpen) => !isCallOpen), [] ); const _onCall = useCallback( (contractIndex: number, messageIndex: number, onCallResult: (messageIndex: number, result?: ContractCallOutcome) => void): void => { setIndexes({ contractIndex, messageIndex, onCallResult }); setIsCallOpen(true); }, [] ); const _setMessageIndex = useCallback( (messageIndex: number) => setIndexes((state) => ({ ...state, messageIndex })), [] ); const contract = contracts[contractIndex] || null; return ( <> {contracts.map((contract, index): React.ReactNode => ( ))}
{isCallOpen && contract && ( )} ); } export default React.memo(ContractsTable);