polkadot-js/apps

View on GitHub
packages/page-parachains/src/Overview/useEvents.ts

Summary

Maintainability
A
2 hrs
Test Coverage
// Copyright 2017-2024 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { ApiPromise } from '@polkadot/api';
import type { SignedBlockExtended } from '@polkadot/api-derive/types';
import type { Event } from '@polkadot/types/interfaces';
import type { PolkadotPrimitivesV6CandidateReceipt } from '@polkadot/types/lookup';
import type { IEvent } from '@polkadot/types/types';
import type { BN } from '@polkadot/util';
import type { EventMapInfo } from './types.js';

import { useEffect, useState } from 'react';

import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
import { stringify } from '@polkadot/util';

type EventMap = Record<string, EventMapInfo>;

interface Result {
  lastBacked: EventMap;
  lastIncluded: EventMap;
  lastTimeout: EventMap;
}

const EMPTY_EVENTS: Result = { lastBacked: {}, lastIncluded: {}, lastTimeout: {} };

function includeEntry (map: EventMap, event: Event, blockHash: string, blockNumber: BN): void {
  try {
    const { descriptor } = (event as unknown as IEvent<[PolkadotPrimitivesV6CandidateReceipt]>).data[0];

    if (descriptor?.paraId) {
      map[descriptor.paraId.toString()] = {
        blockHash,
        blockNumber,
        relayParent: descriptor.relayParent.toHex()
      };
    }
  } catch (error) {
    throw new Error(`${event.section}.${event.method}(${stringify(event.data)}):: ${(error as Error).message}`);
  }
}

function extractEvents (api: ApiPromise, lastBlock: SignedBlockExtended, prev: Result): Result {
  const backed: EventMap = {};
  const included: EventMap = {};
  const timeout: EventMap = {};
  const blockNumber = lastBlock.block.header.number.unwrap();
  const blockHash = lastBlock.block.header.hash.toHex();
  const paraEvents = (api.events.paraInclusion || api.events.parasInclusion || api.events.inclusion);
  let wasBacked = false;
  let wasIncluded = false;
  let wasTimeout = false;

  paraEvents && lastBlock.events.forEach(({ event, phase }) => {
    if (phase.isApplyExtrinsic) {
      if (paraEvents.CandidateBacked.is(event)) {
        includeEntry(backed, event, blockHash, blockNumber);
        wasBacked = true;
      } else if (paraEvents.CandidateIncluded.is(event)) {
        includeEntry(included, event, blockHash, blockNumber);
        wasIncluded = true;
      } else if (paraEvents.CandidateTimedOut.is(event)) {
        includeEntry(timeout, event, blockHash, blockNumber);
        wasTimeout = true;
      }
    }
  });

  return wasBacked || wasIncluded || wasTimeout
    ? {
      lastBacked: wasBacked
        ? { ...prev.lastBacked, ...backed }
        : prev.lastBacked,
      lastIncluded: wasIncluded
        ? { ...prev.lastIncluded, ...included }
        : prev.lastIncluded,
      lastTimeout: wasTimeout
        ? { ...prev.lastTimeout, ...timeout }
        : prev.lastTimeout
    }
    : prev;
}

function useEventsImpl (): Result {
  const { api } = useApi();
  const lastBlock = useCall<SignedBlockExtended>(api.derive.chain.subscribeNewBlocks);
  const [state, setState] = useState<Result>(EMPTY_EVENTS);

  useEffect((): void => {
    lastBlock && setState((prev) =>
      extractEvents(api, lastBlock, prev)
    );
  }, [api, lastBlock]);

  return state;
}

export default createNamedHook('useEvents', useEventsImpl);