@polkadot/util#formatNumber TypeScript Examples

The following examples show how to use @polkadot/util#formatNumber. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Nonce.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
function Nonce({ children, className = '', label, params }: Props): React.ReactElement<Props> {
  const { api } = useApi();
  const allBalances = useCall<DeriveBalancesAll>(api.derive.balances?.all, [params]);

  return (
    <div className={className}>
      {label || ''}
      {formatNumber(allBalances?.accountNonce)}
      {children}
    </div>
  );
}
Example #2
Source File: NominatedBy.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function NominatedBy ({ nominators, slashingSpans }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();

  const { active, chilled } = useMemo(
    () => extractChilled(nominators, slashingSpans),
    [nominators, slashingSpans]
  );

  return (
    <td className='expand all'>
      {active && (
        <Expander
          renderChildren={active[1]}
          summary={t<string>('Nominations ({{count}})', { replace: { count: formatNumber(active[0]) } })}
        />
      )}
      {chilled && (
        <Expander
          renderChildren={chilled[1]}
          summary={t<string>('Renomination required ({{count}})', { replace: { count: formatNumber(chilled[0]) } })}
        />
      )}
    </td>
  );
}
Example #3
Source File: AddressInfo.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
function renderExtended({ address, balancesAll, withExtended }: Props, t: TFunction): React.ReactNode {
  const extendedDisplay = withExtended === true ? DEFAULT_EXTENDED : withExtended || undefined;

  if (!extendedDisplay) {
    return null;
  }

  return (
    <div className="column">
      {balancesAll && extendedDisplay.nonce && (
        <>
          <Label label={t<string>('transactions')} />
          <div className="result">{formatNumber(balancesAll.accountNonce)}</div>
        </>
      )}
      {extendedDisplay.crypto && (
        <>
          <Label label={t<string>('type')} />
          <CryptoType accountId={address} className="result" />
        </>
      )}
    </div>
  );
}
Example #4
Source File: Events.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function Events ({ className = '', emptyLabel, eventClassName, events, label }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();

  const header = useMemo(() => [
    [label || t<string>('recent events'), 'start']
  ], [label, t]);

  return (
    <Table
      className={className}
      empty={emptyLabel || t<string>('No events available')}
      header={header}
    >
      {events && events.map(({ blockHash, blockNumber, indexes, key, record }): React.ReactNode => (
        <tr
          className={eventClassName}
          key={key}
        >
          <td className='overflow'>
            <Event value={record} />
            {blockNumber && (
              <div className='event-link'>
                {indexes.length !== 1 && <span>({formatNumber(indexes.length)}x)&nbsp;</span>}
                <Link to={`/explorer/query/${blockHash || ''}`}>{formatNumber(blockNumber)}-{indexes[0]}</Link>
              </div>
            )}
          </td>
        </tr>
      ))}
    </Table>
  );
}
Example #5
Source File: BestFinalized.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
function BestFinalized({ children, className = '', label }: Props): React.ReactElement<Props> {
  const { api } = useApi();
  const bestNumberFinalized = useCall<BlockNumber>(api.derive.chain.bestNumberFinalized);

  return (
    <div className={className}>
      {label || ''}
      {bestNumberFinalized ? <Digits value={formatNumber(bestNumberFinalized)} /> : '-'}
      {children}
    </div>
  );
}
Example #6
Source File: DueBlocks.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function DueBlocks ({ dueBlocks, endBlock, label }: Props): React.ReactElement<Props> {
  return (
    <>
      {dueBlocks.gtn(0) && (
        <>
          <BlockToTime value={dueBlocks}>
            &nbsp;({label})
          </BlockToTime>
          #{formatNumber(endBlock)}
        </>
      )}
    </>
  );
}
Example #7
Source File: BestNumber.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
function BestNumber({ children, className = '', isFinalized, label, withPound }: Props): React.ReactElement<Props> {
  const { api, isApiReady } = useApi();
  const bestNumber = useCall<BlockNumber>(
    isApiReady && (isFinalized ? api.derive.chain.bestNumberFinalized : api.derive.chain.bestNumber)
  );

  return (
    <div className={className}>
      {label || ''}
      {withPound && '#'}
      {bestNumber ? <Digits value={formatNumber(bestNumber)} /> : '-'}
      {children}
    </div>
  );
}
Example #8
Source File: Summary.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function Summary ({ className, numAssets }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();

  return (
    <SummaryBox className={className}>
      <CardSummary label={t<string>('assets')}>
        {formatNumber(numAssets)}
      </CardSummary>
    </SummaryBox>
  );
}
Example #9
Source File: Amount.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
function Amount({
  className = '',
  defaultValue: { value },
  isDisabled,
  isError,
  label,
  onChange,
  onEnter,
  registry,
  type,
  withLabel,
}: Props): React.ReactElement<Props> {
  const defaultValue = useMemo(
    () =>
      isDisabled
        ? value instanceof registry.createClass('AccountIndex')
          ? value.toString()
          : formatNumber(value as number)
        : bnToBn((value as number) || 0).toString(),
    [isDisabled, registry, value]
  );

  const bitLength = useMemo((): number => {
    try {
      return registry.createType(type.type as 'u32').bitLength();
    } catch (error) {
      // eslint-disable-next-line no-magic-numbers
      return 32;
    }
  }, [registry, type]);

  const _onChange = useCallback(
    (val?: BN) =>
      onChange &&
      onChange({
        isValid: !isUndefined(val),
        value: val,
      }),
    [onChange]
  );

  return (
    <Bare className={className}>
      {isDisabled ? (
        <Input
          className="full"
          defaultValue={defaultValue}
          isDisabled
          label={label}
          withEllipsis
          withLabel={withLabel}
        />
      ) : (
        <InputNumber
          bitLength={bitLength}
          className="full"
          defaultValue={defaultValue}
          isError={isError}
          isZeroable
          label={label}
          onChange={_onChange}
          onEnter={onEnter}
          withLabel={withLabel}
        />
      )}
    </Bare>
  );
}
Example #10
Source File: Messages.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function Messages ({ className = '', contract, contractAbi: { constructors, messages, project: { source } }, isLabelled, isWatching, onSelect, onSelectConstructor, withConstructors, withMessages, withWasm }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const optInfo = useCall<Option<ContractInfo>>(contract && api.query.contracts.contractInfoOf, [contract?.address]);
  const [isUpdating, setIsUpdating] = useState(false);
  const [lastResults, setLastResults] = useState<(ContractCallOutcome | undefined)[]>([]);

  const _onExpander = useCallback(
    (isOpen: boolean): void => {
      isWatching && setIsUpdating(isOpen);
    },
    [isWatching]
  );

  useEffect((): void => {
    isUpdating && optInfo && contract && Promise
      .all(messages.map((m) =>
        m.isMutating || m.args.length !== 0
          ? Promise.resolve(undefined)
          : contract.read(m, 0, -1).send(READ_ADDR).catch(() => undefined)
      ))
      .then(setLastResults)
      .catch(console.error);
  }, [contract, isUpdating, isWatching, messages, optInfo]);

  const _setMessageResult = useCallback(
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    (messageIndex: number, result?: ContractCallOutcome): void => {
      // ignore... for now
      // setLastResults((all) => all.map((r, index) => index === messageIndex ? result : r));
    },
    []
  );

  const _onSelect = useCallback(
    (index: number) => onSelect && onSelect(index, _setMessageResult),
    [_setMessageResult, onSelect]
  );

  return (
    <div className={`ui--Messages ${className}${isLabelled ? ' isLabelled' : ''}`}>
      {withConstructors && (
        <Expander summary={t<string>('Constructors ({{count}})', { replace: { count: constructors.length } })}>
          {sortMessages(constructors).map(([message, index]) => (
            <Message
              index={index}
              key={index}
              message={message}
              onSelect={onSelectConstructor}
            />
          ))}
        </Expander>
      )}
      {withMessages && (
        <Expander
          onClick={_onExpander}
          summary={t<string>('Messages ({{count}})', { replace: { count: messages.length } })}
        >
          {sortMessages(messages).map(([message, index]) => (
            <Message
              index={index}
              key={index}
              lastResult={lastResults[index]}
              message={message}
              onSelect={_onSelect}
            />
          ))}
        </Expander>
      )}
      {withWasm && source.wasm.length !== 0 && (
        <div>{t<string>('{{size}} WASM bytes', { replace: { size: formatNumber(source.wasm.length) } })}</div>
      )}
    </div>
  );
}
Example #11
Source File: ProposedAction.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
function ProposedAction({
  className = '',
  expandNested,
  idNumber,
  proposal,
  withLinks,
}: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const stringId = isString(idNumber) ? idNumber : formatNumber(idNumber);

  if (!proposal) {
    return (
      <h3>
        #{stringId}&nbsp;{t<string>('No execution details available for this proposal')}
      </h3>
    );
  }

  const { meta, method, section } = proposal.registry.findMetaCall(proposal.callIndex);

  const header = `#${stringId}: ${section}.${method}`;

  return (
    <div className={`ui--ProposedAction ${className}`}>
      <h3>{header}</h3>
      <Expander summaryMeta={meta}>
        {isTreasuryProposalVote(proposal) && expandNested ? (
          <TreasuryProposal
            asInset={withLinks}
            insetProps={{
              withBottomMargin: true,
              withTopMargin: true,
              ...(withLinks ? { href: '/treasury' } : {}),
            }}
            proposalId={proposal.args[0].toString()}
          />
        ) : (
          <Call value={proposal} />
        )}
      </Expander>
    </div>
  );
}
Example #12
Source File: Summary.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function Summary ({ bestNumber, className = '', electionsInfo }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();

  if (!electionsInfo) {
    return null;
  }

  const { candidateCount, desiredRunnersUp, desiredSeats, members, runnersUp, termDuration, voteCount } = electionsInfo;

  return (
    <SummaryBox className={className}>
      <section>
        <CardSummary label={t<string>('seats')}>
          {formatNumber(members.length)}&nbsp;/&nbsp;{formatNumber(desiredSeats)}
        </CardSummary>
        <CardSummary label={t<string>('runners up')}>
          {formatNumber(runnersUp.length)}&nbsp;/&nbsp;{formatNumber(desiredRunnersUp)}
        </CardSummary>
        <CardSummary label={t<string>('candidates')}>
          {formatNumber(candidateCount)}
        </CardSummary>
      </section>
      {voteCount && (
        <section>
          <CardSummary label={t<string>('voting round')}>
            #{formatNumber(voteCount)}
          </CardSummary>
        </section>
      )}
      {bestNumber && termDuration?.gtn(0) && (
        <section>
          <CardSummary
            label={t<string>('term progress')}
            progress={{
              total: termDuration,
              value: bestNumber.mod(termDuration),
              withTime: true
            }}
          />
        </section>
      )}
    </SummaryBox>
  );
}
Example #13
Source File: InputFile.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
function InputFile({
  accept,
  className = '',
  clearContent,
  help,
  isDisabled,
  isError = false,
  isFull,
  label,
  onChange,
  placeholder,
  withEllipsis,
  withLabel,
}: InputFileProps): React.ReactElement<InputFileProps> {
  const { t } = useTranslation();
  const dropRef = createRef<DropzoneRef>();
  const [file, setFile] = useState<FileState | undefined>();

  const _onDrop = useCallback(
    (files: File[]): void => {
      files.forEach((file): void => {
        const reader = new FileReader();

        reader.onabort = NOOP;
        reader.onerror = NOOP;

        reader.onload = ({ target }: ProgressEvent<FileReader>): void => {
          if (target && target.result) {
            const name = file.name;
            const data = convertResult(target.result as ArrayBuffer);

            // eslint-disable-next-line
            onChange && onChange(data, name);
            // eslint-disable-next-line
            dropRef &&
              setFile({
                name,
                size: data.length,
              });
          }
        };

        reader.readAsArrayBuffer(file);
      });
    },
    [dropRef, onChange]
  );

  const dropZone = (
    <Dropzone accept={accept} disabled={isDisabled} multiple={false} onDrop={_onDrop} ref={dropRef}>
      {({ getInputProps, getRootProps }: Record<string, any>): JSX.Element => (
        <div {...getRootProps({ className: `ui--InputFile${isError ? ' error' : ''} ${className}` })}>
          <input {...getInputProps()} />
          <em className="label">
            {!file || clearContent
              ? placeholder || t<string>('click to select or drag and drop the file here')
              : placeholder ||
                t<string>('{{name}} ({{size}} bytes)', {
                  replace: {
                    name: file.name,
                    size: formatNumber(file.size),
                  },
                })}
          </em>
        </div>
      )}
    </Dropzone>
  );

  return label ? (
    <Labelled help={help} isFull={isFull} label={label} withEllipsis={withEllipsis} withLabel={withLabel}>
      {dropZone}
    </Labelled>
  ) : (
    dropZone
  );
}
Example #14
Source File: Summary.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function Summary({ info, isLoading }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();

  const daily = info ? 1000 / info.totalEffectiveStakes : 0;
  return (!isLoading) ? (
    <SummaryBox>
      <section>
        <CardSummary label={t<string>('providers / guarantors')}>
            {info
              ? <>{formatNumber(info.totalProviders)}&nbsp;/&nbsp;{formatNumber(info.totalGuarantors)}</>
              : <Spinner noLabel />
            }
        </CardSummary>
      </section>
      <section className='media--800'>
        <CardSummary label={t<string>('Total Rewards')}>
          {<>{`54,000 CRU`}</>}
        </CardSummary>
      </section>
      <section>
        <CardSummary
          className='media--1000'
          label={t<string>('Issued Rewards')}
        >
          { info ? <> {formatNumber(info.calculatedRewards)} CRU </>: (<Spinner noLabel />)}
        </CardSummary>
      </section>

      <section>
        <CardSummary
          className='media--1100'
          label={t<string>('Daily Rewards')}
        >
          {<>{`0 CRU`}</>}
        </CardSummary>
      </section>
      <section>

        <CardSummary
          className='media--1100'
          label={t<string>('Data Power')}
        >
          { info ? <FormatDataPower value={info.dataPower} />  : (<Spinner noLabel />) }
        </CardSummary>
      </section>
      <section>

        <CardSummary
          className='media--1100'
          label={t<string>('Effective stake')}
        >
          { info ? <FormatCsmBalance value={UNIT.muln(info.totalEffectiveStakes)} />:  (<Spinner noLabel />)}
        </CardSummary>
      </section>
      <section>
        <CardSummary
          className='media--1100'
          label={t<string>('Daily Rewards per 1K CSM')}
        >
          { info ? <FormatBalance value={UNIT.muln((daily > 1 ? 1 :daily) * 200)} /> : (<Spinner noLabel />)}
        </CardSummary>
      </section>
    </SummaryBox>
  ) : <Spinner noLabel />;
}
Example #15
Source File: DemocracyLocks.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
// group by header & details
//   - all unlockable together
//   - all ongoing together
//   - unlocks are displayed individually
function groupLocks(t: TFunction, bestNumber: BN, locks: Partial<DeriveDemocracyLock>[] = []): State {
  return {
    maxBalance: bnMax(...locks.map(({ balance }) => balance).filter((b): b is Balance => !!b)),
    sorted: locks
      .map((info): [Partial<DeriveDemocracyLock>, BN] => [
        info,
        info.unlockAt && info.unlockAt.gt(bestNumber) ? info.unlockAt.sub(bestNumber) : BN_ZERO,
      ])
      .sort((a, b) => (a[0].referendumId || BN_ZERO).cmp(b[0].referendumId || BN_ZERO))
      .sort((a, b) => a[1].cmp(b[1]))
      .sort((a, b) => (a[0].isFinished === b[0].isFinished ? 0 : a[0].isFinished ? -1 : 1))
      .reduce(
        (sorted: Entry[], [{ balance, isDelegated, isFinished = false, referendumId, vote }, blocks]): Entry[] => {
          const isCountdown = blocks.gt(BN_ZERO);
          const header =
            referendumId && vote ? (
              <div>
                #{referendumId.toString()} {formatBalance(balance, { forceUnit: '-' })} {vote.conviction.toString()}
                {isDelegated && '/d'}
              </div>
            ) : (
              <div>{t('Prior locked voting')}</div>
            );
          const prev = sorted.length ? sorted[sorted.length - 1] : null;

          if (!prev || isCountdown || isFinished !== prev.isFinished) {
            sorted.push({
              details: (
                <div className="faded">
                  {isCountdown ? (
                    <BlockToTime
                      label={`${t<string>('{{blocks}} blocks', { replace: { blocks: formatNumber(blocks) } })}, `}
                      value={blocks}
                    />
                  ) : isFinished ? (
                    t<string>('lock expired')
                  ) : (
                    t<string>('ongoing referendum')
                  )}
                </div>
              ),
              headers: [header],
              isCountdown,
              isFinished,
            });
          } else {
            prev.headers.push(header);
          }

          return sorted;
        },
        []
      ),
  };
}
Example #16
Source File: ReferendumVotes.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function ReferendumVotes ({ change, className, count, isAye, isWinning, total, votes }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();

  const sorted = useMemo(
    () => votes.sort((a, b) => {
      const ta = a.balance.muln(LOCKS[a.vote.conviction.toNumber()]).div(BN_TEN);
      const tb = b.balance.muln(LOCKS[b.vote.conviction.toNumber()]).div(BN_TEN);

      return tb.cmp(ta);
    }),
    [votes]
  );

  return (
    <Expander
      className={className}
      help={change.gtn(0) && (
        <>
          <FormatBalance value={change} />
          <p>{isWinning
            ? t<string>('The amount this total can be reduced by to change the referendum outcome. This assumes changes to the convictions of the existing votes, with no additional turnout.')
            : t<string>('The amount this total should be increased by to change the referendum outcome. This assumes additional turnout with new votes at 1x conviction.')
          }</p>
        </>
      )}
      helpIcon={isWinning ? 'arrow-circle-down' : 'arrow-circle-up'}
      summary={
        <>
          {isAye
            ? t<string>('Aye {{count}}', { replace: { count: count ? ` (${formatNumber(count)})` : '' } })
            : t<string>('Nay {{count}}', { replace: { count: count ? ` (${formatNumber(count)})` : '' } })
          }
          <div><FormatBalance value={total} /></div>
        </>
      }
    >
      {sorted.map((vote) =>
        <ReferendumVote
          key={vote.accountId.toString()}
          vote={vote}
        />
      )}
    </Expander>
  );
}
Example #17
Source File: CardSummary.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
function CardSummary({ children, className = '', help, label, progress }: Props): React.ReactElement<Props> | null {
  const value = progress && progress.value;
  const total = progress && progress.total;
  const left =
    progress && !isUndefined(value) && !isUndefined(total) && value.gten(0) && total.gtn(0)
      ? value.gt(total)
        ? `>${progress.isPercent ? '100' : formatNumber(total)}`
        : progress.isPercent
        ? value.mul(BN_HUNDRED).div(total).toString()
        : formatNumber(value)
      : undefined;

  if (progress && isUndefined(left)) {
    return null;
  }

  const isTimed = progress && progress.withTime && !isUndefined(progress.total);

  return (
    <article className={className}>
      <Labelled help={help} isSmall label={label}>
        {children}
        {progress && !progress.hideValue && (
          <>
            {isTimed && !children && <BlockToTime value={progress.total} />}
            <div className={isTimed ? 'isSecondary' : 'isPrimary'}>
              {!left || isUndefined(progress.total) ? (
                '-'
              ) : !isTimed || progress.isPercent || !progress.value ? (
                `${left}${progress.isPercent ? '' : '/'}${progress.isPercent ? '%' : formatNumber(progress.total)}`
              ) : (
                <BlockToTime className="timer" value={progress.total.sub(progress.value)} />
              )}
            </div>
          </>
        )}
      </Labelled>
      {progress && !progress.hideGraph && <Progress {...progress} />}
    </article>
  );
}
Example #18
Source File: Summary.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function Summary ({ info: { extrinsics, health, peers } = EMPTY_INFO, nextRefresh }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const [peerBest, setPeerBest] = useState(BN_ZERO);

  useEffect((): void => {
    if (peers) {
      const bestPeer = peers.sort((a, b): number => b.bestNumber.cmp(a.bestNumber))[0];

      setPeerBest(
        bestPeer
          ? bestPeer.bestNumber
          : BN_ZERO
      );
    }
  }, [peers]);

  return (
    <SummaryBox>
      <section>
        <CardSummary label={t<string>('refresh in')}>
          <Elapsed value={nextRefresh} />
        </CardSummary>
        {health && (
          <>
            <CardSummary
              className='media--800'
              label={t<string>('total peers')}
            >
              {formatNumber(health.peers)}
            </CardSummary>
            <CardSummary
              className='media--800'
              label={t<string>('syncing')}
            >
              {health.isSyncing.valueOf()
                ? t<string>('yes')
                : t<string>('no')
              }
            </CardSummary>
          </>
        )}
      </section>
      {extrinsics && (extrinsics.length > 0) && (
        <section className='media--1200'>
          <CardSummary label={t<string>('queued tx')}>
            {extrinsics.length}
          </CardSummary>
        </section>
      )}
      <section>
        {peerBest?.gtn(0) && (
          <CardSummary label={t<string>('peer best')}>
            {formatNumber(peerBest)}
          </CardSummary>
        )}
        <CardSummary label={t<string>('our best')}>
          <BestNumber />
        </CardSummary>
      </section>
    </SummaryBox>
  );
}
Example #19
Source File: DryRun.tsx    From contracts-ui with GNU General Public License v3.0 5 votes vote down vote up
export function DryRun() {
  const { api } = useApi();
  const { dryRunResult } = useInstantiate();

  const dryRunError =
    dryRunResult?.result.isErr && dryRunResult?.result.asErr.isModule
      ? api.registry.findMetaError(dryRunResult?.result.asErr.asModule)
      : null;

  return (
    <SidePanel className="instantiate-outcome" header="Predicted Outcome">
      <div className="body">
        <div className="row">
          <div>Gas Required:</div>
          <div>{dryRunResult?.gasRequired && <>{formatNumber(dryRunResult.gasRequired)}</>}</div>
        </div>
        <div className="row">
          <div>Gas Consumed:</div>
          <div>{dryRunResult?.gasConsumed && <>{formatNumber(dryRunResult.gasConsumed)}</>}</div>
        </div>
        <div className="row">
          <div>Storage Deposit:</div>
          <div>
            {dryRunResult?.storageDeposit &&
              (() => {
                if (dryRunResult.storageDeposit.isCharge) {
                  if (dryRunResult.storageDeposit.asCharge.eqn(0)) {
                    return 'None';
                  }

                  return formatBalance(dryRunResult.storageDeposit.asCharge, { decimals: 12 });
                }

                return 'None';
              })()}
          </div>
        </div>
        <div className="row h-8">
          <div>Contract Address:</div>
          <div>
            {dryRunResult?.result.isOk ? (
              <Account size={26} value={dryRunResult?.result.asOk.accountId.toString()} />
            ) : (
              'None'
            )}
          </div>
        </div>
        <div>
          {dryRunResult?.result.isOk && (
            <div className="validation success font-bold">
              <CheckCircleIcon className="mr-3" />
              The instantiation will be successful.
            </div>
          )}
          {dryRunError && dryRunResult && (
            <>
              <div className="validation error text-mono font-bold items-start">
                <ExclamationCircleIcon className="mr-3" style={{ marginTop: 1 }} />
                <div>
                  <p>{dryRunError.name}</p>
                  <p>{dryRunError.docs}</p>
                </div>
              </div>
              {dryRunResult.debugMessage.length > 0 && (
                <div className="validation error block text-mono break-words pl-4 mt-1">
                  {dryRunResult.debugMessage.toString()}
                </div>
              )}
            </>
          )}
        </div>
      </div>
    </SidePanel>
  );
}
Example #20
Source File: Bid.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function Bid ({ auctionInfo, className, id, ownedIds }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const { hasAccounts } = useAccounts();
  const bestNumber = useBestNumber();
  const [{ accountId, paraId }, setOwnerInfo] = useState<OwnerInfo>({ accountId: null, paraId: 0 });
  const [amount, setAmount] = useState<BN | undefined>(BN_ZERO);
  const [range, setRange] = useState(0);
  const [isOpen, toggleOpen] = useToggle();

  const rangeOpts = useMemo(
    (): Option[] => {
      const [leasePeriod] = auctionInfo || [null, null];

      return leasePeriod
        ? RANGES.map(([first, last], value): Option => ({
          text: `${formatNumber(leasePeriod.addn(first))} - ${formatNumber(leasePeriod.addn(last))}`,
          value
        }))
        : [];
    },
    [auctionInfo]
  );

  const [leasePeriod, endBlock] = auctionInfo || [null, null];

  return (
    <>
      <Button
        icon='plus'
        isDisabled={!ownedIds.length || !hasAccounts || !id || !leasePeriod || !endBlock || bestNumber?.gte(endBlock.add(api.consts.auctions.endingPeriod as BlockNumber))}
        label={t<string>('Bid')}
        onClick={toggleOpen}
      />
      {isOpen && (
        <Modal
          className={className}
          header={t<string>('Place bid')}
          size='large'
        >
          <Modal.Content>
            <InputOwner
              onChange={setOwnerInfo}
              ownedIds={ownedIds}
            />
            <Modal.Columns hint={t<string>('The amount to to bid for this parachain slot')}>
              <InputBalance
                isZeroable={false}
                label={t<string>('bid amount')}
                onChange={setAmount}
              />
            </Modal.Columns>
            <Modal.Columns hint={t<string>('The first and last slots for this bid. The last slot should be after the first and a maximum of 3 slots more than the first')}>
              <Dropdown
                label={t<string>('bid slot range (start slot, end slot)')}
                onChange={setRange}
                options={rangeOpts}
                value={range}
              />
            </Modal.Columns>
          </Modal.Content>
          <Modal.Actions onCancel={toggleOpen}>
            <TxButton
              accountId={accountId}
              icon='plus'
              isDisabled={!paraId || !amount?.gt(BN_ZERO) || !leasePeriod}
              label={t<string>('Bid')}
              onStart={toggleOpen}
              params={[paraId, id, leasePeriod?.addn(RANGES[range][0]), leasePeriod?.addn(RANGES[range][1]), amount]}
              tx={api.tx.auctions.bid}
            />
          </Modal.Actions>
        </Modal>
      )}
    </>
  );
}
Example #21
Source File: Statistics.tsx    From contracts-ui with GNU General Public License v3.0 5 votes vote down vote up
export function Statistics(): React.ReactElement | null {
  const { api } = useApi();

  const [blockNumber, setBlockNumber] = useState(0);
  const { data: statistics } = useStatistics();

  useEffect(() => {
    async function listenToBlocks() {
      return api?.rpc.chain.subscribeNewHeads(header => {
        setBlockNumber(header.number.toNumber());
      });
    }

    let cleanUp: VoidFunction;

    listenToBlocks()
      .then(unsub => (cleanUp = unsub))
      .catch(console.error);

    return () => cleanUp();
  }, [api]);

  const entries = useMemo((): Record<string, React.ReactNode> => {
    return {
      'Highest Block': `#${formatNumber(blockNumber)}`,
      Nodes: 1,
      'Code Bundles Uploaded': statistics?.codeBundlesCount || 0,
      'Contracts Instantiated': statistics?.contractsCount || 0,
    };
  }, [blockNumber, statistics]);

  return (
    <>
      <div className="grid grid-cols-4 xl:grid-cols-2 w-full mb-8 pb-8 border-b border-gray-200 dark:border-gray-800">
        <div className="text-sm mb-4 col-span-4 xl:col-span-2 w-full">Chain Metrics</div>
        {Object.entries(entries).map(([label, value], i) => {
          return (
            <div key={`entry-${i}`} className="mb-4">
              <div className="text-xs mb-1">{label}</div>
              <div className="dark:text-gray-400">{value}</div>
            </div>
          );
        })}
      </div>
    </>
  );
}
Example #22
Source File: balances.ts    From subsocial-js with GNU General Public License v3.0 5 votes vote down vote up
formatBalanceWithoutDecimals = (balance: BigNumber, symbol: string) =>
  `${formatNumber(new BN(balance.toString()))} ${symbol}`
Example #23
Source File: AddressInfo.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
function createBalanceItems(
  formatIndex: number,
  lookup: Record<string, string>,
  t: TFunction,
  {
    address,
    balanceDisplay,
    balancesAll,
    bestNumber,
    democracyLocks,
    isAllLocked,
    otherBonded,
    ownBonded,
    stakingInfo,
    votingOf,
    withBalanceToggle,
  }: {
    address: string;
    balanceDisplay: BalanceActiveType;
    balancesAll?: DeriveBalancesAll | DeriveBalancesAccountData;
    bestNumber: BlockNumber;
    democracyLocks?: DeriveDemocracyLock[];
    isAllLocked: boolean;
    otherBonded: BN[];
    ownBonded: BN;
    stakingInfo?: DeriveStakingAccount;
    votingOf?: Voting;
    withBalanceToggle: boolean;
  }
): React.ReactNode {
  const allItems: React.ReactNode[] = [];
  const deriveBalances = balancesAll as DeriveBalancesAll;

  // eslint-disable-next-line
  !withBalanceToggle &&
    balancesAll &&
    balanceDisplay.total &&
    allItems.push(
      <React.Fragment key={0}>
        <Label label={t<string>('total')} />
        <FormatBalance
          className="result"
          formatIndex={formatIndex}
          value={balancesAll.freeBalance.add(balancesAll.reservedBalance)}
        />
      </React.Fragment>
    );
  // eslint-disable-next-line
  balancesAll &&
    balanceDisplay.available &&
    (balancesAll as DeriveBalancesAll).availableBalance &&
    allItems.push(
      <React.Fragment key={1}>
        <Label label={t<string>('transferrable')} />
        <FormatBalance
          className="result"
          formatIndex={formatIndex}
          value={(balancesAll as DeriveBalancesAll).availableBalance}
        />
      </React.Fragment>
    );
  if (balanceDisplay.vested && deriveBalances?.isVesting) {
    const allVesting = deriveBalances.vesting.filter(({ endBlock }) => bestNumber.lt(endBlock));

    allItems.push(
      <React.Fragment key={2}>
        <Label label={t<string>('vested')} />
        <FormatBalance
          className='result'
          formatIndex={formatIndex}
          labelPost={
            <Icon
              icon='info-circle'
              tooltip={`${address}-vested-trigger`}
            />
          }
          value={deriveBalances.vestedBalance}
        >
          <Tooltip
            text={
              <>
                <div>
                  {formatBalance(deriveBalances.vestedClaimable, { forceUnit: '-' })}
                  <div className='faded'>{t('available to be unlocked')}</div>
                </div>
                {allVesting.map(({ endBlock, locked, perBlock, vested }, index) => (
                  <div
                    className='inner'
                    key={`item:${index}`}
                  >
                    <div>
                      {formatBalance(vested, { forceUnit: '-' })}
                      <div className='faded'>{t('of {{locked}} vested', { replace: { locked: formatBalance(locked, { forceUnit: '-' }) } })}</div>
                    </div>
                    <div>
                      <BlockToTime value={endBlock.sub(bestNumber)} />
                      <div className='faded'>{t('until block')} {formatNumber(endBlock)}</div>
                    </div>
                    <div>
                      {formatBalance(perBlock)}
                      <div className='faded'>{t('per block')}</div>
                    </div>
                  </div>
                ))}
              </>
            }
            trigger={`${address}-vested-trigger`}
          />
        </FormatBalance>
      </React.Fragment>
    );
  }
  balanceDisplay.locked &&
    balancesAll &&
    (isAllLocked || (balancesAll as DeriveBalancesAll).lockedBalance?.gtn(0)) &&
    allItems.push(
      <React.Fragment key={3}>
        <Label label={t<string>('locked')} />
        <FormatBalance
          className="result"
          formatIndex={formatIndex}
          label={<Icon icon="info-circle" tooltip={`${address}-locks-trigger`} />}
          value={isAllLocked ? 'all' : (balancesAll as DeriveBalancesAll).lockedBalance}
        >
          <Tooltip
            text={(balancesAll as DeriveBalancesAll).lockedBreakdown.map(
              ({ amount, id, reasons }, index): React.ReactNode => (
                <div key={index}>
                  {amount?.isMax() ? t<string>('everything') : formatBalance(amount, { forceUnit: '-' })}
                  {id && <div className="faded">{lookupLock(lookup, id)}</div>}
                  <div className="faded">{reasons.toString()}</div>
                </div>
              )
            )}
            trigger={`${address}-locks-trigger`}
          />
        </FormatBalance>
      </React.Fragment>
    );
  balanceDisplay.reserved &&
    balancesAll?.reservedBalance?.gtn(0) &&
    allItems.push(
      <React.Fragment key={4}>
        <Label label={t<string>('reserved')} />
        <FormatBalance className="result" formatIndex={formatIndex} value={balancesAll.reservedBalance} />
      </React.Fragment>
    );
  balanceDisplay.bonded &&
    (ownBonded.gtn(0) || otherBonded.length !== 0) &&
    allItems.push(
      <React.Fragment key={5}>
        <Label label={t<string>('bonded')} />
        <FormatBalance className="result" formatIndex={formatIndex} value={ownBonded}>
          {otherBonded.length !== 0 && (
            <>
              &nbsp;(+
              {otherBonded.map(
                (bonded, index): React.ReactNode => (
                  <FormatBalance formatIndex={formatIndex} key={index} value={bonded} />
                )
              )}
              )
            </>
          )}
        </FormatBalance>
      </React.Fragment>
    );
  balanceDisplay.redeemable &&
    stakingInfo?.redeemable?.gtn(0) &&
    allItems.push(
      <React.Fragment key={6}>
        <Label label={t<string>('redeemable')} />
        <StakingRedeemable className="result" stakingInfo={stakingInfo} />
      </React.Fragment>
    );

  if (balanceDisplay.unlocking) {
    stakingInfo?.unlocking &&
      allItems.push(
        <React.Fragment key={7}>
          <Label label={t<string>('unbonding')} />
          <div className="result">
            <StakingUnbonding stakingInfo={stakingInfo} />
          </div>
        </React.Fragment>
      );

    if (democracyLocks && democracyLocks.length !== 0) {
      allItems.push(
        <React.Fragment key={8}>
          <Label label={t<string>('democracy')} />
          <div className="result">
            <DemocracyLocks value={democracyLocks} />
          </div>
        </React.Fragment>
      );
    } else if (votingOf && votingOf.isDirect) {
      const {
        prior: [unlockAt, balance],
      } = votingOf.asDirect;

      balance.gt(BN_ZERO) &&
        unlockAt.gt(BN_ZERO) &&
        allItems.push(
          <React.Fragment key={8}>
            <Label label={t<string>('democracy')} />
            <div className="result">
              <DemocracyLocks value={[{ balance, isFinished: bestNumber.gt(unlockAt), unlockAt }]} />
            </div>
          </React.Fragment>
        );
    }
  }

  if (withBalanceToggle) {
    return (
      <React.Fragment key={formatIndex}>
        <Expander
          summary={
            <FormatBalance
              formatIndex={formatIndex}
              value={balancesAll && balancesAll.freeBalance.add(balancesAll.reservedBalance)}
            />
          }
        >
          {allItems.length !== 0 && <div className="body column">{allItems}</div>}
        </Expander>
      </React.Fragment>
    );
  }

  return <React.Fragment key={formatIndex}>{allItems}</React.Fragment>;
}
Example #24
Source File: Step3.tsx    From contracts-ui with GNU General Public License v3.0 4 votes vote down vote up
export function Step3() {
  const apiState = useApi();
  const { codeHash: codeHashUrlParam } = useParams<{ codeHash: string }>();
  const { data, currentStep, onUnFinalize, tx, onError, onInstantiate } = useInstantiate();
  const { accountId, value, metadata, weight, name, constructorIndex } = data;
  const isConstructorPayable = metadata?.constructors[constructorIndex].isPayable;

  const displayHash = codeHashUrlParam || metadata?.info.source.wasmHash.toHex();

  const [onSubmit, onCancel, isValid, isProcessing] = useQueueTx(
    tx,
    data.accountId,
    onInstantiate,
    onError,
    isResultValid
  );

  if (currentStep !== 3) return null;

  return (
    <>
      <div className="review">
        <div className="field full">
          <p className="key">Account</p>
          <div className="value">
            <Account value={accountId} />
          </div>
        </div>

        <div className="field full">
          <p className="key">Name</p>
          <p className="value">{name}</p>
        </div>
        {isConstructorPayable && value && (
          <div className="field">
            <p className="key">Value</p>
            <p className="value">
              {formatBalance(value, { decimals: apiState.tokenDecimals, forceUnit: '-' })}
            </p>
          </div>
        )}

        <div className="field">
          <p className="key">Weight</p>
          <p className="value">{formatNumber(weight)}</p>
        </div>

        {displayHash && (
          <div className="field">
            <p className="key">Code Hash</p>
            <p className="value">{truncate(displayHash)}</p>
          </div>
        )}

        {tx?.args[3] && (
          <div className="field">
            <p className="key">Data</p>
            <p className="value">{truncate(tx?.args[3].toHex())}</p>
          </div>
        )}
      </div>
      <Buttons>
        <Button
          variant="primary"
          isDisabled={!isValid}
          isLoading={isProcessing}
          onClick={onSubmit}
          data-cy="submit-btn"
        >
          Upload and Instantiate
        </Button>

        <Button
          onClick={(): void => {
            onCancel();
            onUnFinalize && onUnFinalize();
          }}
          isDisabled={isProcessing}
        >
          Go Back
        </Button>
      </Buttons>
    </>
  );
}
Example #25
Source File: BlockAuthors.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
function BlockAuthorsBase({ children }: Props): React.ReactElement<Props> {
  const { api, isApiReady } = useApi();
  const queryPoints = useCall<EraRewardPoints>(isApiReady && api.derive.staking?.currentPoints);
  const [state, setState] = useState<Authors>({ byAuthor, eraPoints, lastBlockAuthors: [], lastHeaders: [] });
  const [validators, setValidators] = useState<string[]>([]);

  useEffect((): void => {
    // No unsub, global context - destroyed on app close
    api?.isReady
      .then((): void => {
        let lastHeaders: HeaderExtendedWithMapping[] = [];
        let lastBlockAuthors: string[] = [];
        let lastBlockNumber = '';
        const isAuthorMapping = isFunction(api.query.authorMapping?.mapping);

        // subscribe to all validators
        api.query.session &&
          api.query.session
            .validators((validatorIds: any): void => {
              setValidators(validatorIds.map((validatorId: any) => validatorId.toString()));
            })
            .catch(console.error);

        // subscribe to new headers
        api.derive.chain
          .subscribeNewHeads(async (lastHeader: HeaderExtendedWithMapping): Promise<void> => {
            if (lastHeader?.number) {
              const blockNumber = lastHeader.number.unwrap();
              let thisBlockAuthor = '';

              if (lastHeader.author) {
                thisBlockAuthor = lastHeader.author.toString();
              } else if (
                isAuthorMapping &&
                lastHeader.digest.logs &&
                lastHeader.digest.logs[0] &&
                lastHeader.digest.logs[0].isConsensus &&
                lastHeader.digest.logs[0].asConsensus[1]
              ) {
                // Some blockchains such as Moonbeam need to fetch the author accountId from a mapping
                thisBlockAuthor = (
                  await api.query.authorMapping.mapping(lastHeader.digest.logs[0].asConsensus[1])
                ).toString();
                lastHeader.authorFromMapping = thisBlockAuthor;
              }

              const thisBlockNumber = formatNumber(blockNumber);

              if (thisBlockAuthor) {
                byAuthor[thisBlockAuthor] = thisBlockNumber;

                if (thisBlockNumber !== lastBlockNumber) {
                  lastBlockNumber = thisBlockNumber;
                  lastBlockAuthors = [thisBlockAuthor];
                } else {
                  lastBlockAuthors.push(thisBlockAuthor);
                }
              }

              lastHeaders = lastHeaders
                .filter((old, index) => index < MAX_HEADERS && old.number.unwrap().lt(blockNumber))
                .reduce(
                  (next, header): HeaderExtendedWithMapping[] => {
                    next.push(header);

                    return next;
                  },
                  [lastHeader]
                )
                .sort((a, b) => b.number.unwrap().cmp(a.number.unwrap()));

              setState({
                byAuthor,
                eraPoints,
                lastBlockAuthors: lastBlockAuthors.slice(),
                lastBlockNumber,
                lastHeader,
                lastHeaders,
              });
            }
          })
          .catch(console.error);
      })
      .catch(console.error);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect((): void => {
    if (queryPoints) {
      const entries = [...queryPoints.individual.entries()].map(([accountId, points]) => [
        accountId.toString(),
        formatNumber(points),
      ]);
      const current = Object.keys(eraPoints);

      // we have an update, clear all previous
      if (current.length !== entries.length) {
        current.forEach((accountId): void => {
          delete eraPoints[accountId];
        });
      }

      entries.forEach(([accountId, points]): void => {
        eraPoints[accountId] = points;
      });
    }
  }, [queryPoints]);

  return (
    <ValidatorsContext.Provider value={validators}>
      <BlockAuthorsContext.Provider value={state}>{children}</BlockAuthorsContext.Provider>
    </ValidatorsContext.Provider>
  );
}
Example #26
Source File: index.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function PollApp ({ basePath, className }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const bestNumber = useBestNumber();
  const [totalIssuance, totals] = useCallMulti<MultiResult>([
    api.query.balances.totalIssuance,
    api.query.poll.totals
  ], optMulti);
  const [accountId, setAccountId] = useState<string | null>(null);
  const [turnout, setTurnout] = useState<Turnout | null>(null);
  const [opt10m, setOpt10m] = useState(false);
  const [opt100m, setOpt100m] = useState(false);
  const [opt1b, setOpt1b] = useState(false);
  const [opt10b, setOpt10b] = useState(false);
  const [progress, setProgress] = useState<BN[] | undefined>();

  const itemsRef = useRef([{
    isRoot: true,
    name: 'poll',
    text: t<string>('Denomination poll')
  }]);

  useEffect((): void => {
    if (totalIssuance && totals) {
      const max = bnMax(BN_ONE, ...totals);

      setProgress(totals.map((total) => total.mul(BN_MILLION).div(max)));

      api.query.poll.voteOf
        .entries<ITuple<[Approvals, Balance]>>()
        .then((entries): void => {
          const voted = entries.reduce((voted: BN, [, [, balance]]) => voted.iadd(balance), new BN(0));
          const percentage = voted.muln(10_000).div(totalIssuance).toNumber() / 100;

          setTurnout({ percentage, voted });
        })
        .catch(console.log);
    }
  }, [api, totalIssuance, totals]);

  if (!totals || !progress || !bestNumber) {
    return (
      <main className={className}>
        <div className='pollContainer'>
          <Spinner label={t<string>('Retrieving totals...')} />
        </div>
      </main>
    );
  }

  const blocksLeft = (api.consts.poll.end as BlockNumber).sub(bestNumber);
  const canVote = blocksLeft.gt(BN_ZERO);
  const options: [string, string, boolean, (value: boolean) => void][] = [
    [t('No change'), t('No change from the original 2017 sale definitions; will mean a total of 10 million DOT from genesis.'), opt10m, setOpt10m],
    [t('Split of 10x'), t('Split of 10x from the original sale; will mean a total of 100 million DOT from genesis. Apparent DOT price would be 10x lower and apparent account balances 10x higher.'), opt100m, setOpt100m],
    [t('Split of 100x'), t('Split of 100x from the original sale; will mean a total of 1 billion DOT from genesis. Apparent DOT price would be 100x lower and apparent account balances 100x higher.'), opt1b, setOpt1b],
    [t('Split of 1000x'), t('Split of 1000x from the original sale; will mean a total of 10 billion DOT from genesis. Apparent DOT price would be 1000x lower and apparent account balances 1000x higher.'), opt10b, setOpt10b]
  ];
  const hasValue = opt10m || opt100m || opt1b || opt10b;

  /* eslint-disable react/jsx-max-props-per-line */

  return (
    <main className={className}>
      <Tabs
        basePath={basePath}
        items={itemsRef.current}
      />
      <div className='pollContainer'>
        <div className='pollHeader'>
          <h1>{t('denomination vote')}</h1>
          <div className='pollBlocksRight'>
            {turnout && (
              <div>
                <div>{t('{{balance}} voted', { replace: { balance: formatBalance(turnout.voted) } })}</div>
                <div>{t('{{percentage}}% turnout', { replace: { percentage: turnout.percentage.toFixed(2) } })}</div>
              </div>
            )}
            <div>
              {canVote
                ? <BlockToTime value={blocksLeft} />
                : t<string>('Completed')
              }
              <div>#{formatNumber(api.consts.poll.end as BlockNumber)}</div>
            </div>
          </div>
        </div>
        <article className='keepAlive'>
          <p><Trans key='poll1'>The Polkadot DOT denomination vote: Seventy-two hours after the DOT token becomes transferable, the most popular option from this poll will decide the denomination used for the DOT token.</Trans></p>
          <p><Trans key='poll2'>This is an <a href='https://en.wikipedia.org/wiki/Approval_voting' rel='noreferrer' target='_blank'>approval vote</a>. There are four options and you may select any combination of them. The most popular of the four will be selected as the final DOT denomination three days after DOT token transfers are enabled.</Trans></p>
          <p><Trans key='poll3'>Please see the <a href='https://medium.com/polkadot-network/the-first-polkadot-vote-1fc1b8bd357b' rel='noreferrer' target='_blank'>Medium article </a> for more information</Trans></p>
          {canVote && (
            <p className='pollAll'><Trans key='poll4'><b>Please vote for any combination of options</b></Trans></p>
          )}
          <div className={`options ${canVote ? 'canVote' : ''}`}>
            {options.map(([label, desc, value, onChange], index) =>
              <Columar
                is60
                key={index}
              >
                <Columar.Column className='option'>
                  <div className='optionName'>{label}</div>
                  <div className='optionDesc'>{desc}</div>
                  {canVote && (
                    <Toggle
                      className='pollToggle'
                      isDisabled={!canVote}
                      label={
                        canVote
                          ? value
                            ? t<string>('Aye, I support this')
                            : t<string>('Nay, I do not support this')
                          : t<string>('Voting closed')
                      }
                      onChange={onChange}
                      value={canVote && value}
                    />
                  )}
                </Columar.Column>
                <Columar.Column>
                  {totals[index].isZero()
                    ? <div className='result' />
                    : (
                      <div className='result'>
                        <FormatBalance value={totals[index]} />
                        <Progress
                          isDisabled={!turnout}
                          total={turnout?.voted}
                          value={totals[index]}
                        />
                      </div>
                    )
                  }
                </Columar.Column>
              </Columar>
            )}
          </div>
          {canVote && (
            <>
              <InputAddress
                label={t('vote using my account')}
                onChange={setAccountId}
                type='account'
              />
              <Button.Group>
                <TxButton
                  accountId={accountId}
                  icon='paper-plane'
                  isDisabled={!hasValue}
                  label={t('Vote')}
                  params={[[opt10m, opt100m, opt1b, opt10b]]}
                  tx={api.tx.poll.vote}
                />
              </Button.Group>
            </>
          )}
        </article>
        <div className='pollActions'>
          <ul>
            <li>{t('Any combination of the four options may be approved of by the voter. There is no need to select only one option!')}</li>
            <li>{t('Approving of all or none of the options is equivalent and will not affect the outcome of the poll.')}</li>
            <li>{t('All voters may alter their votes any number of times prior to the close of the poll.')}</li>
            <li>{t('Voting costs nothing other than the transaction fee and can be done from all accounts with a non-zero spendable balance.')}</li>
            <li>{t('Locked funds (e.g. for staking) are counted.')}</li>
            <li>{t('No discretionary lock-voting is in place; all DOT used to vote counts the same.')}</li>
            <li>{t('Voting is made on a per-account basis; a single account must all vote the same way and cannot split its vote.')}</li>
            <li>{t('This vote does not affect any economics of the Polkadot platform. Staking rewards, inflation, effective market capitalisation and the underlying balances of every account remain completely unchanged. It is "merely" about what units we use to denominate the balances into "DOT" for the purpose of display.')}</li>
          </ul>
        </div>
      </div>
    </main>
  );
}