// Copyright 2017-2026 @pezkuwi/react-signer authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { ApiPromise } from '@pezkuwi/api'; import type { QueueTx, QueueTxMessageSetStatus, QueueTxResult } from '@pezkuwi/react-components/Status/types'; import type { BareProps as Props } from '@pezkuwi/react-components/types'; import type { DefinitionRpcExt } from '@pezkuwi/types/types'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Modal, styled } from '@pezkuwi/react-components'; import { useApi, useQueue } from '@pezkuwi/react-hooks'; import { assert, isFunction, loggerFormat } from '@pezkuwi/util'; import { useTranslation } from './translate.js'; import TxSigned from './TxSigned.js'; import TxUnsigned from './TxUnsigned.js'; export * from './signers/index.js'; interface ItemState { currentItem: QueueTx | null; isRpc: boolean; isVisible: boolean; queueSize: number; requestAddress: string | null; } const NOOP = () => undefined; const AVAIL_STATUS = ['queued', 'qr', 'signing']; async function submitRpc (api: ApiPromise, { method, section }: DefinitionRpcExt, values: unknown[]): Promise { try { const rpc = api.rpc as unknown as Record Promise>>; assert(isFunction(rpc[section]?.[method]), `api.rpc.${section}.${method} does not exist`); const result = await rpc[section][method](...values); console.log('submitRpc: result ::', loggerFormat(result)); return { result, status: 'sent' }; } catch (error) { console.error(error); return { error: error as Error, status: 'error' }; } } async function sendRpc (api: ApiPromise, queueSetTxStatus: QueueTxMessageSetStatus, { id, rpc, values = [] }: QueueTx): Promise { if (rpc) { queueSetTxStatus(id, 'sending'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { error, result, status } = await submitRpc(api, rpc, values); queueSetTxStatus(id, status, result, error); } } function extractCurrent (txqueue: QueueTx[]): ItemState { const available = txqueue.filter(({ status }) => AVAIL_STATUS.includes(status)); const currentItem = available[0] || null; let isRpc = false; let isVisible = false; if (currentItem) { if (currentItem.status === 'queued' && !(currentItem.extrinsic || currentItem.payload)) { isRpc = true; } else if (currentItem.status !== 'signing') { isVisible = true; } } return { currentItem, isRpc, isVisible, queueSize: available.length, requestAddress: (currentItem?.accountId) || null }; } function Signer ({ children, className = '' }: Props): React.ReactElement { const { api } = useApi(); const { t } = useTranslation(); const { queueSetTxStatus, txqueue } = useQueue(); const [isQueueSubmit, setIsQueueSubmit] = useState(false); const { currentItem, isRpc, isVisible, queueSize, requestAddress } = useMemo( () => extractCurrent(txqueue), [txqueue] ); useEffect((): void => { (queueSize === 1) && setIsQueueSubmit(false); }, [queueSize]); useEffect((): void => { isRpc && currentItem && sendRpc(api, queueSetTxStatus, currentItem).catch(console.error); }, [api, isRpc, currentItem, queueSetTxStatus]); const _onCancel = useCallback( (): void => { if (currentItem) { const { id, signerCb = NOOP, txFailedCb = NOOP } = currentItem; queueSetTxStatus(id, 'cancelled'); signerCb(id, null); txFailedCb(null); } }, [currentItem, queueSetTxStatus] ); return ( <> {children} {currentItem && isVisible && ( {t('Authorize transaction')}{(queueSize === 1) ? undefined : <> 1/{queueSize}}} key={currentItem.id} onClose={_onCancel} size='large' > {currentItem.isUnsigned ? : ( ) } )} ); } const StyledModal = styled(Modal)` .signToggle { bottom: 1.5rem; left: 1.5rem; position: absolute; .ui--Toggle { display: inline-block; &+.ui--Toggle { margin-left: 1rem; } } } `; export default React.memo(Signer);