@polkadot/util#BN_MILLION TypeScript Examples

The following examples show how to use @polkadot/util#BN_MILLION. 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: useInflation.ts    From crust-apps with Apache License 2.0 6 votes vote down vote up
export function calcInflation (api: ApiPromise, totalStaked: BN, totalIssuance: BN): Inflation {
  const { falloff, idealStake, maxInflation, minInflation } = getInflationParams(api);
  const stakedFraction = totalStaked.isZero() || totalIssuance.isZero()
    ? 0
    : totalStaked.mul(BN_MILLION).div(totalIssuance).toNumber() / BN_MILLION.toNumber();
  const idealInterest = maxInflation / idealStake;
  const inflation = 100 * (minInflation + (
    stakedFraction <= idealStake
      ? (stakedFraction * (idealInterest - (minInflation / idealStake)))
      : (((idealInterest * idealStake) - minInflation) * Math.pow(2, (idealStake - stakedFraction) / falloff))
  ));

  return {
    inflation,
    stakedReturn: stakedFraction
      ? (inflation / stakedFraction)
      : 0
  };
}
Example #2
Source File: useTreasury.ts    From crust-apps with Apache License 2.0 6 votes vote down vote up
export function useTreasury (): Result {
  const { api } = useApi();
  const [result, setResult] = useState<Result>({
    spendPeriod: api.consts.treasury
      ? api.consts.treasury.spendPeriod
      : BN_ZERO
  });

  const treasuryBalance = useCall<DeriveBalancesAccount>(api.derive.balances.account, [TREASURY_ACCOUNT]);

  useEffect(() => {
    if (!api.consts.treasury) {
      return;
    }

    setResult(({ spendPeriod }) => ({
      burn: treasuryBalance?.freeBalance.gtn(0) && !api.consts.treasury.burn.isZero()
        ? api.consts.treasury.burn.mul(treasuryBalance.freeBalance).div(BN_MILLION)
        : BN_ZERO,
      spendPeriod,
      value: treasuryBalance?.freeBalance.gtn(0)
        ? treasuryBalance.freeBalance
        : undefined
    }));
  }, [api, treasuryBalance]);

  return result;
}
Example #3
Source File: gov.ts    From sdk with Apache License 2.0 6 votes vote down vote up
/**
 * Query overview of treasury and spend proposals.
 */
async function getTreasuryOverview(api: ApiPromise) {
  const [bounties, proposals, balance] = await Promise.all([
    api.derive.bounties?.bounties(),
    api.derive.treasury.proposals(),
    api.derive.balances.account(TREASURY_ACCOUNT as AccountId),
  ]);
  const pendingBounties = bounties.reduce(
    (total, { bounty: { status, value } }) => total.iadd(status.isApproved ? value : BN_ZERO),
    new BN(0)
  );
  const pendingProposals = proposals.approvals.reduce((total, { proposal: { value } }) => total.iadd(value), new BN(0));
  const burn =
    balance.freeBalance.gt(BN_ZERO) && !(api.consts.treasury.burn as any).isZero()
      ? (api.consts.treasury.burn as any).mul(balance.freeBalance).div(BN_MILLION)
      : BN_ZERO;
  const res: any = {
    ...proposals,
  };
  res["balance"] = balance.freeBalance.toString();
  res["burn"] = burn.toString();
  res["approved"] = pendingProposals.toString();
  res["spendable"] = balance.freeBalance
    .sub(pendingBounties)
    .sub(pendingProposals)
    .toString();
  res.proposals.forEach((e: any) => {
    if (e.council.length) {
      e.council = e.council.map((i: any) => ({
        ...i,
        proposal: _transfromProposalMeta(i.proposal),
      }));
    }
  });
  return res;
}
Example #4
Source File: useInflation.ts    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
function calcInflation(api: ApiPromise, totalStaked: BN, totalIssuance: BN, numAuctions: BN): Inflation {
  const { auctionAdjust, auctionMax, falloff, maxInflation, minInflation, stakeTarget } = getInflationParams(api);
  const stakedFraction =
    totalStaked.isZero() || totalIssuance.isZero()
      ? 0
      : totalStaked.mul(BN_MILLION).div(totalIssuance).toNumber() / BN_MILLION.toNumber();
  const idealStake = stakeTarget - Math.min(auctionMax, numAuctions.toNumber()) * auctionAdjust;
  const idealInterest = maxInflation / idealStake;
  const inflation =
    100 *
    (minInflation +
      (stakedFraction <= idealStake
        ? stakedFraction * (idealInterest - minInflation / idealStake)
        : (idealInterest * idealStake - minInflation) * Math.pow(2, (idealStake - stakedFraction) / falloff)));

  return {
    idealInterest,
    idealStake,
    inflation,
    stakedFraction,
    stakedReturn: stakedFraction ? inflation / stakedFraction : 0,
  };
}
Example #5
Source File: useTreasury.ts    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
export function useTreasury(): Result {
  const { api } = useApi();
  const [result, setResult] = useState<Result>({
    spendPeriod: api.consts.treasury ? (api.consts.treasury.spendPeriod as unknown as BN) : BN_ZERO,
  });

  const treasuryBalance = useCall<DeriveBalancesAccount>(api.derive.balances?.account, [TREASURY_ACCOUNT]);

  useEffect(() => {
    if (!api.consts.treasury) {
      return;
    }

    setResult(({ spendPeriod }) => ({
      burn:
        treasuryBalance?.freeBalance.gtn(0) && !(api.consts.treasury.burn as unknown as BN).isZero()
          ? (api.consts.treasury.burn as unknown as BN).mul(treasuryBalance.freeBalance).div(BN_MILLION)
          : BN_ZERO,
      spendPeriod,
      value: treasuryBalance?.freeBalance.gtn(0) ? treasuryBalance.freeBalance : undefined,
    }));
  }, [api, treasuryBalance]);

  return result;
}
Example #6
Source File: permillOf.ts    From crust-apps with Apache License 2.0 5 votes vote down vote up
export function permillOf (value: BN, perMill: BN): BN {
  return value.mul(perMill).div(BN_MILLION);
}
Example #7
Source File: InputMegaGas.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function InputMegaGas ({ className, estimatedWeight, help, isCall, weight: { executionTime, isValid, megaGas, percentage, setIsEmpty, setMegaGas } }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const [withEstimate, setWithEstimate] = useState(true);

  const estimatedMg = useMemo(
    () => estimatedWeight
      ? estimatedWeight.div(BN_MILLION).iadd(BN_ONE)
      : null,
    [estimatedWeight]
  );

  useEffect((): void => {
    withEstimate && estimatedMg && setMegaGas(estimatedMg);
  }, [estimatedMg, setMegaGas, withEstimate]);

  useEffect((): void => {
    setIsEmpty(withEstimate && !!isCall);
  }, [isCall, setIsEmpty, withEstimate]);

  const isDisabled = !!estimatedMg && withEstimate;

  return (
    <div className={className}>
      <InputNumber
        defaultValue={estimatedMg && isDisabled ? estimatedMg.toString() : undefined}
        help={help}
        isDisabled={isDisabled}
        isError={!isValid}
        isZeroable={isCall}
        label={
          estimatedMg && (isCall ? !withEstimate : true)
            ? t<string>('max gas allowed (M, {{estimatedMg}} estimated)', { replace: { estimatedMg: estimatedMg.toString() } })
            : t<string>('max gas allowed (M)')
        }
        onChange={isDisabled ? undefined : setMegaGas}
        value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : megaGas)}
      >
        {(estimatedWeight || isCall) && (
          <Toggle
            isOverlay
            label={
              isCall
                ? t<string>('max read gas')
                : t<string>('use estimated gas')
            }
            onChange={setWithEstimate}
            value={withEstimate}
          />
        )}
      </InputNumber>
      <div className='contracts--InputMegaGas-meter'>
        {t<string>('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}{', '}
        {t<string>('{{percentage}}% of block weight', { replace: { percentage: percentage.toFixed(2) } })}
      </div>
    </div>
  );
}
Example #8
Source File: useWeight.ts    From crust-apps with Apache License 2.0 5 votes vote down vote up
export default function useWeight (): UseWeight {
  const { api } = useApi();
  const [blockTime] = useBlockTime();
  const [megaGas, _setMegaGas] = useState<BN>(
    (api.consts.system.blockWeights
      ? api.consts.system.blockWeights.maxBlock
      : api.consts.system.maximumBlockWeight as Weight
    ).div(BN_MILLION).div(BN_TEN)
  );
  const [isEmpty, setIsEmpty] = useState(false);

  const setMegaGas = useCallback(
    (value?: BN | undefined) => _setMegaGas(value || (
      (api.consts.system.blockWeights
        ? api.consts.system.blockWeights.maxBlock
        : api.consts.system.maximumBlockWeight as Weight
      ).div(BN_MILLION).div(BN_TEN)
    )),
    [api]
  );

  return useMemo((): UseWeight => {
    let executionTime = 0;
    let percentage = 0;
    let weight = BN_ZERO;
    let isValid = false;

    if (megaGas) {
      weight = megaGas.mul(BN_MILLION);
      executionTime = weight.muln(blockTime).div(
        api.consts.system.blockWeights
          ? api.consts.system.blockWeights.maxBlock
          : api.consts.system.maximumBlockWeight as Weight
      ).toNumber();
      percentage = (executionTime / blockTime) * 100;

      // execution is 2s of 6s blocks, i.e. 1/3
      executionTime = executionTime / 3000;
      isValid = !megaGas.isZero() && percentage < 65;
    }

    return {
      executionTime,
      isEmpty,
      isValid: isEmpty || isValid,
      megaGas: megaGas || BN_ZERO,
      percentage,
      setIsEmpty,
      setMegaGas,
      weight
    };
  }, [api, blockTime, isEmpty, megaGas, setIsEmpty, setMegaGas]);
}
Example #9
Source File: InputGas.tsx    From contracts-ui with GNU General Public License v3.0 5 votes vote down vote up
export function InputGas({
  className,
  defaultWeight,
  estimatedWeight,
  executionTime,
  isActive,
  isCall,
  isValid,
  megaGas,
  percentage,
  setIsActive,
  setMegaGas,
  weight,
  withEstimate,
  ...props
}: Props) {
  return (
    <div className={classes(className)} {...props}>
      <InputNumber
        value={megaGas}
        isDisabled={!isActive}
        onChange={value => {
          if (isActive) {
            setMegaGas(value);
          }
        }}
        placeholder="MGas"
      />
      <Meter
        accessory={
          isActive ? (
            <a
              href="#"
              onClick={e => {
                e.preventDefault();

                setIsActive(false);
              }}
              className="text-green-500"
            >
              {isCall
                ? `Use Estimated Gas (${(estimatedWeight || BN_ZERO)
                    .div(BN_MILLION)
                    .add(BN_ONE)
                    .toString()}M)`
                : 'Use Maximum Query Gas'}
            </a>
          ) : (
            <>
              {isCall ? 'Using Estimated Gas' : 'Using Maximum Query Gas'}
              &nbsp;{' ยท '}&nbsp;
              <a
                href="#"
                onClick={e => {
                  e.preventDefault();

                  setIsActive(true);
                }}
                className="text-green-500"
              >
                Use Custom
              </a>
            </>
          )
        }
        label={`${
          executionTime < 0.001 ? '<0.001' : executionTime.toFixed(3)
        }s execution time (${percentage.toFixed(2)}% of block time)}`}
        percentage={percentage}
        withAccessory={withEstimate}
      />
    </div>
  );
}
Example #10
Source File: useWeight.ts    From contracts-ui with GNU General Public License v3.0 5 votes vote down vote up
function getEstimatedMegaGas(api: ApiPromise, estimatedWeight: OrFalsy<BN>, withBuffer = true): BN {
  return (estimatedWeight || maximumBlockWeight(api)).div(BN_MILLION).addn(withBuffer ? 1 : 0);
}
Example #11
Source File: useWeight.ts    From contracts-ui with GNU General Public License v3.0 5 votes vote down vote up
function getDefaultMegaGas(api: OrFalsy<ApiPromise>, estimatedWeight?: OrFalsy<BN>): BN {
  if (api && estimatedWeight) {
    return getEstimatedMegaGas(api, estimatedWeight);
  }

  return maximumBlockWeight(api).div(BN_MILLION).div(BN_TEN);
}
Example #12
Source File: useWeight.ts    From contracts-ui with GNU General Public License v3.0 5 votes vote down vote up
useWeight = (estimatedWeight: OrFalsy<BN>): UseWeight => {
  // const estimatedWeightRef = useRef(estimatedWeight);
  const { api } = useApi();
  const [blockTime] = useBlockTime();
  const [megaGas, _setMegaGas] = useState<BN>(getDefaultMegaGas(api, estimatedWeight));
  const [isActive, setIsActive] = useState(!!estimatedWeight);

  const defaultWeight = useMemo((): BN => maximumBlockWeight(api), [api]);

  const setMegaGas = useCallback(
    (value?: BN | undefined) => {
      _setMegaGas(value || getDefaultMegaGas(api, null));
    },
    [api]
  );

  useEffect((): void => {
    if (!isActive) {
      _setMegaGas(getDefaultMegaGas(api, estimatedWeight));
    }
  }, [api, estimatedWeight, isActive]);

  return useMemo((): UseWeight => {
    let executionTime = 0;
    let percentage = 0;
    let weight = BN_ZERO;
    let isValid = false;

    if (megaGas) {
      weight = megaGas.mul(BN_MILLION);
      executionTime = weight.muln(blockTime).div(maximumBlockWeight(api)).toNumber();
      percentage = (executionTime / blockTime) * 100;

      // execution is 2s of 6s blocks, i.e. 1/3
      executionTime = executionTime / 3000;
      isValid = !megaGas.isZero() && percentage < 65;
    }

    return {
      defaultWeight,
      estimatedWeight,
      executionTime,
      isActive,
      isValid: !isActive || isValid,
      megaGas: megaGas || BN_ZERO,
      percentage,
      setIsActive,
      setMegaGas,
      weight,
    };
  }, [api, blockTime, defaultWeight, estimatedWeight, isActive, megaGas, setIsActive, setMegaGas]);
}
Example #13
Source File: index.ts    From sdk with Apache License 2.0 5 votes vote down vote up
function _extractTargetsInfo(api: ApiPromise, electedDerive: DeriveStakingElected, waitingDerive: DeriveStakingWaiting, totalIssuance: BN, lastEraInfo: LastEra, historyDepth?: BN): Partial<SortedTargets> {
  const [elected, nominators] = _extractSingleTarget(api, electedDerive, lastEraInfo, historyDepth);
  const [waiting] = _extractSingleTarget(api, waitingDerive, lastEraInfo);
  const activeTotals = elected
    .filter(({ isActive }) => isActive)
    .map(({ bondTotal }) => bondTotal)
    .sort((a, b) => a.cmp(b));
  const totalStaked = activeTotals.reduce((total: BN, value) => total.iadd(value), new BN(0));
  const avgStaked = totalStaked.divn(activeTotals.length);
  const inflation = _calcInflation(api, totalStaked, totalIssuance);

  // add the explicit stakedReturn
  !avgStaked.isZero() && elected.forEach((e): void => {
    if (!e.skipRewards) {
      e.stakedReturn = inflation.stakedReturn * avgStaked.mul(BN_MILLION).div(e.bondTotal).toNumber() / BN_MILLION.toNumber();
      e.stakedReturnCmp = e.stakedReturn * (100 - e.commissionPer) / 100;
    }
  });

  const minNominated = elected.reduce((min: BN, { minNominated }) => {
    return min.isZero() || minNominated.lt(min)
      ? minNominated
      : min;
  }, BN_ZERO);
  // all validators, calc median commission
  const validators = sortValidators(arrayFlatten([elected, waiting]));
  const commValues = validators.map(({ commissionPer }) => commissionPer).sort((a, b) => a - b);
  const midIndex = Math.floor(commValues.length / 2);
  const medianComm = commValues.length
    ? commValues.length % 2
      ? commValues[midIndex]
      : (commValues[midIndex - 1] + commValues[midIndex]) / 2
    : 0;

  // ids
  const electedIds = elected.map(({ key }) => key);
  const waitingIds = waiting.map(({ key }) => key);
  const validatorIds = arrayFlatten([electedIds, waitingIds]);

  return {
    avgStaked,
    inflation,
    lowStaked: activeTotals[0] || BN_ZERO,
    medianComm,
    minNominated,
    nominators,
    totalIssuance,
    totalStaked,
    validatorIds,
    validators,
    waitingIds
  };
}
Example #14
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>
  );
}
Example #15
Source File: ProposalCreate.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Propose ({ className }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();
  const { api } = useApi();
  const [accountId, setAccountId] = useState<string | null>(null);
  const [beneficiary, setBeneficiary] = useState<string | null>(null);
  const [isOpen, toggleOpen] = useToggle();
  const [value, setValue] = useState<BN | undefined>();
  const hasValue = value?.gtn(0);

  const bondPercentage = useMemo(
    () => `${api.consts.treasury.proposalBond.mul(BN_HUNDRED).div(BN_MILLION).toNumber().toFixed(2)}%`,
    [api]
  );

  return (
    <>
      {isOpen && (
        <Modal
          className={className}
          header={t<string>('Submit treasury proposal')}
          size='large'
        >
          <Modal.Content>
            <Modal.Columns hint={t<string>('This account will make the proposal and be responsible for the bond.')}>
              <InputAddress
                help={t<string>('Select the account you wish to submit the proposal from.')}
                label={t<string>('submit with account')}
                onChange={setAccountId}
                type='account'
                withLabel
              />
            </Modal.Columns>
            <Modal.Columns hint={t<string>('The beneficiary will receive the full amount if the proposal passes.')}>
              <InputAddress
                help={t<string>('The account to which the proposed balance will be transferred if approved')}
                label={t<string>('beneficiary')}
                onChange={setBeneficiary}
                type='allPlus'
              />
            </Modal.Columns>
            <Modal.Columns hint={
              <>
                <p>{t<string>('The value is the amount that is being asked for and that will be allocated to the beneficiary if the proposal is approved.')}</p>
                <p>{t<string>('Of the beneficiary amount, at least {{bondPercentage}} would need to be put up as collateral. The maximum of this and the minimum bond will be used to secure the proposal, refundable if it passes.', { replace: { bondPercentage } })}</p>
              </>
            }>
              <InputBalance
                help={t<string>('The amount that will be allocated from the treasury pot')}
                isError={!hasValue}
                label={t<string>('value')}
                onChange={setValue}
              />
              <Static
                help={t<string>('The on-chain percentage for the treasury')}
                label={t<string>('proposal bond')}
              >
                {bondPercentage}
              </Static>
              <InputBalance
                defaultValue={api.consts.treasury.proposalBondMinimum.toString()}
                help={t<string>('The minimum amount that will be bonded')}
                isDisabled
                label={t<string>('minimum bond')}
              />
              <MarkWarning content={t<string>('Be aware that once submitted the proposal will be put to a council vote. If the proposal is rejected due to a lack of info, invalid requirements or non-benefit to the network as a whole, the full bond posted (as describe above) will be lost.')} />
            </Modal.Columns>
          </Modal.Content>
          <Modal.Actions onCancel={toggleOpen}>
            <TxButton
              accountId={accountId}
              icon='plus'
              isDisabled={!accountId || !hasValue}
              label={t<string>('Submit proposal')}
              onStart={toggleOpen}
              params={[value, beneficiary]}
              tx={api.tx.treasury.proposeSpend}
            />
          </Modal.Actions>
        </Modal>
      )}
      <Button
        icon='plus'
        label={t<string>('Submit proposal')}
        onClick={toggleOpen}
      />
    </>
  );
}