diff --git a/packages/page-staking/src/Payouts/PayButton.tsx b/packages/page-staking/src/Payouts/PayButton.tsx index c1c0e1d04e..03e25fea7a 100644 --- a/packages/page-staking/src/Payouts/PayButton.tsx +++ b/packages/page-staking/src/Payouts/PayButton.tsx @@ -25,37 +25,44 @@ interface SinglePayout { validatorId: string; } -function createExtrinsic (api: ApiPromise, payout: PayoutValidator | PayoutValidator[], maxPayouts: number): SubmittableExtrinsic<'promise'> | null { +function createBatches (api: ApiPromise, maxPayouts: number, payouts: SinglePayout[]): SubmittableExtrinsic<'promise'>[] { + return payouts + .sort((a, b) => a.era.cmp(b.era)) + .reduce((batches: SubmittableExtrinsic<'promise'>[][], { era, validatorId }): SubmittableExtrinsic<'promise'>[][] => { + const tx = api.tx.staking.payoutStakers(validatorId, era); + const batch = batches[batches.length - 1]; + + if (batch.length >= maxPayouts) { + batches.push([tx]); + } else { + batch.push(tx); + } + + return batches; + }, [[]]) + .map((batch) => api.tx.utility.batch(batch)); +} + +function createExtrinsics (api: ApiPromise, payout: PayoutValidator | PayoutValidator[], maxPayouts: number): SubmittableExtrinsic<'promise'>[] | null { if (Array.isArray(payout)) { if (payout.length === 1) { - return createExtrinsic(api, payout[0], maxPayouts); + return createExtrinsics(api, payout[0], maxPayouts); } - return api.tx.utility.batch( - payout - .reduce((payouts: SinglePayout[], { eras, validatorId }): SinglePayout[] => { - eras.forEach(({ era }): void => { - payouts.push({ era, validatorId }); - }); + return createBatches(api, maxPayouts, payout.reduce((payouts: SinglePayout[], { eras, validatorId }): SinglePayout[] => { + eras.forEach(({ era }): void => { + payouts.push({ era, validatorId }); + }); - return payouts; - }, []) - .sort((a, b) => a.era.cmp(b.era)) - .filter((_, index) => index < maxPayouts) - .map(({ era, validatorId }) => api.tx.staking.payoutStakers(validatorId, era)) - ); + return payouts; + }, [])); } const { eras, validatorId } = payout; return eras.length === 1 - ? api.tx.staking.payoutStakers(validatorId, eras[0].era) - : api.tx.utility.batch( - eras - .sort((a, b) => a.era.cmp(b.era)) - .filter((_, index) => index < maxPayouts) - .map(({ era }) => api.tx.staking.payoutStakers(validatorId, era)) - ); + ? [api.tx.staking.payoutStakers(validatorId, eras[0].era)] + : createBatches(api, maxPayouts, eras.map((era): SinglePayout => ({ era: era.era, validatorId }))); } function PayButton ({ className, isAll, isDisabled, payout }: Props): React.ReactElement { @@ -64,7 +71,7 @@ function PayButton ({ className, isAll, isDisabled, payout }: Props): React.Reac const { allAccounts } = useAccounts(); const [isVisible, togglePayout] = useToggle(); const [accountId, setAccount] = useState(null); - const [extrinsic, setExtrinsic] = useState | null>(null); + const [extrinsics, setExtrinsics] = useState[] | null>(null); const [maxPayouts, setMaxPayouts] = useState(0); useEffect((): void => { @@ -90,8 +97,8 @@ function PayButton ({ className, isAll, isDisabled, payout }: Props): React.Reac }, [allAccounts, api, payout]); useEffect((): void => { - api.tx.utility && payout && setExtrinsic( - () => createExtrinsic(api, payout, maxPayouts) + api.tx.utility && payout && setExtrinsics( + () => createExtrinsics(api, payout, maxPayouts) ); }, [api, maxPayouts, payout]); @@ -154,9 +161,9 @@ function PayButton ({ className, isAll, isDisabled, payout }: Props): React.Reac ('Payout')} onStart={togglePayout} /> diff --git a/packages/react-components/src/TxButton.tsx b/packages/react-components/src/TxButton.tsx index 538e10269b..7202054ae9 100644 --- a/packages/react-components/src/TxButton.tsx +++ b/packages/react-components/src/TxButton.tsx @@ -4,9 +4,9 @@ import { SubmittableExtrinsic } from '@polkadot/api/types'; import { TxButtonProps as Props } from './types'; -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; import { SubmittableResult } from '@polkadot/api'; -import { useApi, useToggle } from '@polkadot/react-hooks'; +import { useApi } from '@polkadot/react-hooks'; import { assert, isFunction } from '@polkadot/util'; import Button from './Button'; @@ -17,9 +17,14 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, const { t } = useTranslation(); const { api } = useApi(); const { queueExtrinsic } = useContext(StatusContext); - const [isSending, , setIsSending] = useToggle(false); + const [isSending, setIsSending] = useState(false); + const [isStarted, setIsStarted] = useState(false); const needsAccount = !isUnsigned && !accountId; + useEffect((): void => { + (isStarted && onStart) && onStart(); + }, [isStarted, onStart]); + const _onFailed = useCallback( (result: SubmittableResult | null): void => { setIsSending(false); @@ -38,43 +43,54 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, [onSuccess, setIsSending] ); + const _onStart = useCallback( + () => setIsStarted(true), + [setIsStarted] + ); + const _onSend = useCallback( (): void => { - let extrinsic: SubmittableExtrinsic<'promise'>; + let extrinsics: SubmittableExtrinsic<'promise'>[]; if (propsExtrinsic) { - extrinsic = propsExtrinsic; + extrinsics = Array.isArray(propsExtrinsic) + ? propsExtrinsic + : [propsExtrinsic]; } else { const [section, method] = (tx || '').split('.'); assert(api.tx[section] && api.tx[section][method], `Unable to find api.tx.${section}.${method}`); - extrinsic = api.tx[section][method](...( - isFunction(params) - ? params() - : (params || []) - )); + extrinsics = [ + api.tx[section][method](...( + isFunction(params) + ? params() + : (params || []) + )) + ]; } - assert(extrinsic, 'Expected generated extrinsic passed to TxButton'); + assert(extrinsics?.length, 'Expected generated extrinsic passed to TxButton'); if (withSpinner) { setIsSending(true); } - queueExtrinsic({ - accountId: accountId && accountId.toString(), - extrinsic, - isUnsigned, - txFailedCb: withSpinner ? _onFailed : onFailed, - txStartCb: onStart, - txSuccessCb: withSpinner ? _onSuccess : onSuccess, - txUpdateCb: onUpdate + extrinsics.forEach((extrinsic): void => { + queueExtrinsic({ + accountId: accountId && accountId.toString(), + extrinsic, + isUnsigned, + txFailedCb: withSpinner ? _onFailed : onFailed, + txStartCb: _onStart, + txSuccessCb: withSpinner ? _onSuccess : onSuccess, + txUpdateCb: onUpdate + }); }); onClick && onClick(); }, - [_onFailed, _onSuccess, accountId, api.tx, isUnsigned, onClick, onFailed, onStart, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, setIsSending, tx, withSpinner] + [_onFailed, _onStart, _onSuccess, accountId, api.tx, isUnsigned, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, setIsSending, tx, withSpinner] ); if (onSendRef) { diff --git a/packages/react-components/src/types.ts b/packages/react-components/src/types.ts index 993cb6b206..5c993af9a3 100644 --- a/packages/react-components/src/types.ts +++ b/packages/react-components/src/types.ts @@ -39,7 +39,7 @@ export interface TxTriggerProps { export type TxTrigger = React.ComponentType; export interface TxProps { - extrinsic?: SubmittableExtrinsic | null; + extrinsic?: SubmittableExtrinsic | SubmittableExtrinsic[] | null; tx?: string; params?: any[] | ConstructTxFn; }