@polkadot/util#isFunction TypeScript Examples

The following examples show how to use @polkadot/util#isFunction. 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: util.ts    From crust-apps with Apache License 2.0 7 votes vote down vote up
export function balanceToNumber (amount: BN | ToBN = BN_ZERO, divisor: BN): number {
  const value = isBn(amount)
    ? amount
    : isFunction(amount.toBn)
      ? amount.toBn()
      : BN_ZERO;

  return value.mul(BN_THOUSAND).div(divisor).toNumber() / 1000;
}
Example #2
Source File: Transaction.ts    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
public async signAndSend(
    account: AddressOrPair,
    optionsOrCallback?: Partial<SignerOptions> | TransactionStatusCb,
    optionalCallback?: TransactionStatusCb,
  ): Promise<Hash | (() => void)> {
    const [options, callback] = isFunction(optionsOrCallback)
      ? [undefined, optionsOrCallback]
      : [optionsOrCallback, optionalCallback];

    try {
      return await this.submitted.signAndSend(account, options, callback);
    } catch (error) {
      const errorCode = +error.message.split(':')[0];
      if (errorCode === 1010) {
        throw new TransactionError('Account balance too low');
      } else {
        throw new TransactionError(error.message);
      }
    }
  }
Example #3
Source File: useStakerPayouts.ts    From crust-apps with Apache License 2.0 6 votes vote down vote up
export default function useStakerPayouts (): BN {
  const { api } = useApi();
  const migrateEraOpt = useCall<Option<EraIndex>>(api.query.staking?.migrateEra);

  return useMemo(
    () => (migrateEraOpt && migrateEraOpt.isSome && migrateEraOpt.unwrap()) || (
      isFunction(api.tx.staking.payoutStakers)
        ? BN_ZERO
        : BN_BILLION
    ),
    [api, migrateEraOpt]
  );
}
Example #4
Source File: democracy_proposal.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
public updateVoters = async () => {
    const depositOpt = await this._Chain.api.query.democracy.depositOf(this.data.index);
    if (!depositOpt.isSome) return;
    const depositorsTuple: ITuple<[ BalanceOf | Vec<AccountId>, BalanceOf | Vec<AccountId> ]> = depositOpt.unwrap();
    let depositors: Vec<AccountId>;
    if (isFunction((depositorsTuple[1] as BalanceOf).mul)) {
      depositors = depositorsTuple[0] as Vec<AccountId>;
    } else {
      depositors = depositorsTuple[1] as Vec<AccountId>;
    }
    for (const depositor of depositors) {
      const acct = this._Accounts.fromAddress(depositor.toString());
      const votes = this.getVotes(acct);
      if (!votes.length) {
        this.addOrUpdateVote(new DepositVote(acct, this._Chain.coins(this.data.deposit)));
      } else {
        // if they second a proposal multiple times, sum up the vote weight
        const vote = new DepositVote(acct, this._Chain.coins(votes[0].deposit.add(this.data.deposit)));
        this.addOrUpdateVote(vote);
      }
    }
  }
Example #5
Source File: triggerChange.ts    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
export default function triggerChange(value?: unknown, ...callOnResult: (OnChangeCb | undefined)[]): void {
  if (!callOnResult || !callOnResult.length) {
    return;
  }

  // eslint-disable-next-line @typescript-eslint/no-shadow
  callOnResult.forEach((callOnResult): void => {
    if (isObservable(callOnResult)) {
      callOnResult.next(value);
    } else if (isFunction(callOnResult)) {
      callOnResult(value);
    }
  });
}
Example #6
Source File: index.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function CreateButton ({ assetIds, className }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const { hasAccounts } = useAccounts();
  const [isOpen, toggleOpen] = useToggle();

  return (
    <>
      <Button
        icon='plus'
        isDisabled={!assetIds || !hasAccounts || !isFunction(api.tx.utility.batchAll)}
        label={t<string>('Create')}
        onClick={toggleOpen}
      />
      {isOpen && assetIds && (
        <Create
          assetIds={assetIds}
          className={className}
          onClose={toggleOpen}
        />
      )}
    </>
  );
}
Example #7
Source File: valueToText.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
function toHuman(value: Codec | Codec[]): unknown {
  // eslint-disable-next-line @typescript-eslint/unbound-method
  return isFunction((value as Codec).toHuman) ? (value as Codec).toHuman() : (value as Codec[]).map(toHuman);
}
Example #8
Source File: index.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function BridgeApp ({ basePath, onStatusChange }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const { hasAccounts } = useAccounts();
  const { isIpfs } = useIpfs();
  const itemsRef = isFunction(api.tx.bridgeTransfer?.transferToElrond)
    ? useRef([
      {
        isRoot: true,
        name: 'bridge',
        text: t<string>('Crust to Ethereum')
      },
      {
        name: 'bridgeBack',
        text: t<string>('Ethereum to Crust')
      },
      {
        name: 'bridgeToElrond',
        text: t<string>('Crust to Elrond')
      },
      {
        name: 'elrondToCrust',
        text: t<string>('Elrond to Crust')
      }
    ])
    : useRef([
      {
        isRoot: true,
        name: 'bridge',
        text: t<string>('Crust to Ethereum')
      },
      {
        name: 'bridgeBack',
        text: t<string>('Ethereum to Crust')
      },
      {
        name: 'bridgeToShadow',
        text: t<string>('Maxwell to Shadow')
      }
    ]);

  return (
    <Web3Provider>
      <EthersProvider>
        <main className='accounts--App'>
          <header>
            <Tabs
              basePath={basePath}
              hidden={(hasAccounts && !isIpfs) ? undefined : HIDDEN_ACC}
              items={itemsRef.current}
            />
          </header>
          <Switch>
            <Route path={`${basePath}/bridgeBack`}>
              <EthereumAssets />
            </Route>
            {isFunction(api.tx.bridgeTransfer?.transferToElrond) && <Route path={`${basePath}/bridgeToElrond`}>
              <ElrondAssets />
            </Route>}
            {isFunction(api.tx.bridgeTransfer?.transferToElrond) && <Route path={`${basePath}/elrondToCrust`}>
              <ElrondBackAssets />
            </Route>}
            {isFunction(api.tx.bridgeTransfer?.transferCsmNative) && <Route path={`${basePath}/bridgeToShadow`}>
              <ShadowAssets />
            </Route>}
            <Route basePath={basePath}
              onStatusChange={onStatusChange}>
              <MainnetAssets />
            </Route>

          </Switch>
        </main>
      </EthersProvider>

    </Web3Provider>
  );
}
Example #9
Source File: PaymentInfo.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
function PaymentInfo({ accountId, className = '', extrinsic }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();
  const { api } = useApi();
  const [dispatchInfo, setDispatchInfo] = useState<RuntimeDispatchInfo | null>(null);
  const balances = useCall<DeriveBalancesAll>(api.derive.balances?.all, [accountId]);
  const mountedRef = useIsMountedRef();

  useEffect((): void => {
    accountId &&
      extrinsic &&
      isFunction(extrinsic.paymentInfo) &&
      isFunction(api.rpc.payment?.queryInfo) &&
      setTimeout((): void => {
        try {
          extrinsic
            .paymentInfo(accountId)
            .then((info) => mountedRef.current && setDispatchInfo(info))
            .catch(console.error);
        } catch (error) {
          console.error(error);
        }
      }, 0);
  }, [api, accountId, extrinsic, mountedRef]);

  if (!dispatchInfo || !extrinsic) {
    return null;
  }

  const isFeeError =
    api.consts.balances &&
    !api.tx.balances?.transfer.is(extrinsic) &&
    balances?.accountId.eq(accountId) &&
    (balances.availableBalance.lte(dispatchInfo.partialFee) ||
      balances.freeBalance.sub(dispatchInfo.partialFee).lte(api.consts.balances.existentialDeposit as unknown as BN));

  return (
    <>
      <Expander
        className={className}
        summary={
          <Trans i18nKey="feesForSubmission">
            Fees of <span className="highlight">{formatBalance(dispatchInfo.partialFee, { withSiFull: true })}</span>{' '}
            will be applied to the submission
          </Trans>
        }
      />
      {isFeeError && (
        <MarkWarning
          content={t<string>(
            'The account does not have enough free funds (excluding locked/bonded/reserved) available to cover the transaction fees without dropping the balance below the account existential amount.'
          )}
        />
      )}
    </>
  );
}
Example #10
Source File: Validate.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function Validate ({ className = '', controllerId, onChange, stashId, withSenders }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const [commission, setCommission] = useState<BN | number>(1);
  const [allowNoms, setAllowNoms] = useState(true);

  const blockedOptions = useRef([
    { text: t('Yes, allow nominations'), value: true },
    { text: t('No, block all nominations'), value: false }
  ]);

  useEffect((): void => {
    try {
      onChange({
        validateTx: api.tx.staking.validate({
          // @ts-ignore
          guarantee_fee: new BN(commission).isZero()
          // small non-zero set to avoid isEmpty
            ? '0'
            : (new BN(commission).toNumber() > 1000000000) ? '1000000000' : new BN(commission).toNumber().toString()
        })
      });
    } catch {
      onChange({ validateTx: null });
    }
  }, [api, allowNoms, commission, onChange]);

  const _setCommission = useCallback(
    (value?: BN) => value && setCommission(
      value.isZero()
        ? 1 // small non-zero set to avoid isEmpty
        : value.mul(COMM_MUL)
    ),
    []
  );

  return (
    <div className={className}>
      {withSenders && (
        <Modal.Columns hint={t<string>('The stash and controller pair. This transaction, managing preferences, will be sent from the controller.')}>
          <InputAddress
            defaultValue={stashId}
            isDisabled
            label={t<string>('stash account')}
          />
          <InputAddress
            defaultValue={controllerId}
            isDisabled
            label={t<string>('controller account')}
          />
        </Modal.Columns>
      )}
      <Modal.Columns hint={t<string>('The commission is deducted from all rewards before the remainder is split with nominators.')}>
        <InputNumber
          help={t<string>('The guarantee fee (0-100) that should be applied for the validator')}
          isZeroable
          label={t<string>('guarantee fee')}
          maxValue={MAX_COMM}
          onChange={_setCommission}
        />
      </Modal.Columns>
      {isFunction(api.tx.staking.kick) && (
        <Modal.Columns hint={t<string>('The validator can block any new nominations. By default it is set to allow all nominations.')}>
          <Dropdown
            defaultValue={true}
            help={t<string>('Does this validator allow nominations or is it blocked for all')}
            label={t<string>('allows new nominations')}
            onChange={setAllowNoms}
            options={blockedOptions.current}
          />
        </Modal.Columns>
      )}
    </div>
  );
}
Example #11
Source File: useAccountInfo.ts    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
export function useAccountInfo(value: string | null, isContract = false): UseAccountInfo {
  const { api } = useApi();
  const { isAccount } = useAccounts();
  const { isAddress } = useAddresses();
  const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, [value]);
  const accountFlags = useCall<DeriveAccountFlags>(api.derive.accounts.flags, [value]);
  const nominator = useCall<Nominations>(api.query.staking?.nominators, [value]);
  const validator = useCall<ValidatorPrefs>(api.query.staking?.validators, [value]);
  const [accountIndex, setAccountIndex] = useState<string | undefined>(undefined);
  const [tags, setSortedTags] = useState<string[]>([]);
  const [name, setName] = useState('');
  const [genesisHash, setGenesisHash] = useState<string | null>(null);
  const [identity, setIdentity] = useState<AddressIdentity | undefined>();
  const [flags, setFlags] = useState<AddressFlags>(IS_NONE);
  const [meta, setMeta] = useState<KeyringJson$Meta | undefined>();
  const [isEditingName, toggleIsEditingName] = useToggle();
  const [isEditingTags, toggleIsEditingTags] = useToggle();

  useEffect((): void => {
    // eslint-disable-next-line
    validator &&
      setFlags((flags) => ({
        ...flags,
        isValidator: !validator.isEmpty,
      }));
  }, [validator]);

  useEffect((): void => {
    // eslint-disable-next-line
    nominator &&
      setFlags((flags) => ({
        ...flags,
        isNominator: !nominator.isEmpty,
      }));
  }, [nominator]);

  useEffect((): void => {
    // eslint-disable-next-line
    accountFlags &&
      setFlags((flags) => ({
        ...flags,
        ...accountFlags,
      }));
  }, [accountFlags]);

  useEffect((): void => {
    const { accountIndex, identity, nickname } = accountInfo || {};
    const newIndex = accountIndex?.toString();

    setAccountIndex((oldIndex) => (oldIndex !== newIndex ? newIndex : oldIndex));

    let name;

    if (isFunction(api.query.identity?.identityOf)) {
      if (identity?.display) {
        name = identity.display;
      }
    } else if (nickname) {
      name = nickname;
    }

    setName(name || '');

    if (identity) {
      const judgements = identity.judgements.filter(([, judgement]) => !judgement.isFeePaid);
      const isKnownGood = judgements.some(([, judgement]) => judgement.isKnownGood);
      const isReasonable = judgements.some(([, judgement]) => judgement.isReasonable);
      const isErroneous = judgements.some(([, judgement]) => judgement.isErroneous);
      const isLowQuality = judgements.some(([, judgement]) => judgement.isLowQuality);

      setIdentity({
        ...identity,
        isBad: isErroneous || isLowQuality,
        isErroneous,
        isExistent: !!identity.display,
        isGood: isKnownGood || isReasonable,
        isKnownGood,
        isLowQuality,
        isReasonable,
        judgements,
        waitCount: identity.judgements.length - judgements.length,
      });
    } else {
      setIdentity(undefined);
    }
  }, [accountInfo, api]);

  useEffect((): void => {
    if (value) {
      try {
        const accountOrAddress = keyring.getAccount(value) || keyring.getAddress(value);
        const isOwned = isAccount(value);
        const isInContacts = isAddress(value);

        setGenesisHash(accountOrAddress?.meta.genesisHash || null);
        setFlags(
          (flags): AddressFlags => ({
            ...flags,
            isDevelopment: accountOrAddress?.meta.isTesting || false,
            isEditable:
              !!(
                !identity?.display &&
                (isInContacts ||
                  accountOrAddress?.meta.isMultisig ||
                  (accountOrAddress && !accountOrAddress.meta.isInjected))
              ) || false,
            isExternal: !!accountOrAddress?.meta.isExternal || false,
            isHardware: !!accountOrAddress?.meta.isHardware || false,
            isInContacts,
            isInjected: !!accountOrAddress?.meta.isInjected || false,
            isMultisig: !!accountOrAddress?.meta.isMultisig || false,
            isOwned,
            isProxied: !!accountOrAddress?.meta.isProxied || false,
          })
        );
        setMeta(accountOrAddress?.meta);
        setName(accountOrAddress?.meta.name || '');
        setSortedTags(accountOrAddress?.meta.tags ? (accountOrAddress.meta.tags as string[]).sort() : []);
      } catch (error) {
        // ignore
      }
    }
  }, [identity, isAccount, isAddress, value]);

  const onSaveName = useCallback((): void => {
    if (isEditingName) {
      toggleIsEditingName();
    }

    const meta = { name, whenEdited: Date.now() };

    if (isContract) {
      try {
        if (value) {
          const originalMeta = keyring.getAddress(value)?.meta;

          keyring.saveContract(value, { ...originalMeta, ...meta });
        }
      } catch (error) {
        console.error(error);
      }
    } else if (value) {
      try {
        const pair = keyring.getPair(value);

        // eslint-disable-next-line
        pair && keyring.saveAccountMeta(pair, meta);
      } catch (error) {
        const pair = keyring.getAddress(value);

        if (pair) {
          keyring.saveAddress(value, meta);
        } else {
          keyring.saveAddress(value, { genesisHash: api.genesisHash.toHex(), ...meta });
        }
      }
    }
  }, [api, isContract, isEditingName, name, toggleIsEditingName, value]);

  const onSaveTags = useCallback((): void => {
    const meta = { tags, whenEdited: Date.now() };

    if (isContract) {
      try {
        if (value) {
          const originalMeta = keyring.getAddress(value)?.meta;

          // eslint-disable-next-line
          value && keyring.saveContract(value, { ...originalMeta, ...meta });
        }
      } catch (error) {
        console.error(error);
      }
    } else if (value) {
      try {
        const currentKeyring = keyring.getPair(value);

        // eslint-disable-next-line
        currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
      } catch (error) {
        keyring.saveAddress(value, meta);
      }
    }
  }, [isContract, tags, value]);

  const onForgetAddress = useCallback((): void => {
    if (isEditingName) {
      toggleIsEditingName();
    }

    if (isEditingTags) {
      toggleIsEditingTags();
    }

    try {
      // eslint-disable-next-line
      value && keyring.forgetAddress(value);
    } catch (e) {
      console.error(e);
    }
  }, [isEditingName, isEditingTags, toggleIsEditingName, toggleIsEditingTags, value]);

  const onSetGenesisHash = useCallback(
    (genesisHash: string | null): void => {
      if (value) {
        const account = keyring.getPair(value);

        // eslint-disable-next-line
        account && keyring.saveAccountMeta(account, { ...account.meta, genesisHash });

        setGenesisHash(genesisHash);
      }
    },
    [value]
  );

  const setTags = useCallback((tags: string[]): void => setSortedTags(tags.sort()), []);

  return {
    accountIndex,
    flags,
    genesisHash,
    identity,
    isEditingName,
    isEditingTags,
    isNull: !value,
    meta,
    name,
    onForgetAddress,
    onSaveName,
    onSaveTags,
    onSetGenesisHash,
    setName,
    setTags,
    tags,
    toggleIsEditingName,
    toggleIsEditingTags,
  };
}
Example #12
Source File: CreateGroup.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function CreateGroup ({ className = '', onClose, onSuccess, senderId: propSenderId }: Props): React.ReactElement<Props> {
  const ownStashes = useOwnStashInfos();
  const { t } = useTranslation();
  const { api } = useApi();
  const [amount] = useState<BN | undefined>(BN_ZERO);
  const [hasAvailable] = useState(true);
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const [, setMaxTransfer] = useState<BN | null>(null);
  const [senderId, setSenderId] = useState<string | null>(propSenderId || null);
  const balances = useCall<DeriveBalancesAll>(api.derive.balances.all, [senderId]);

  const stashes = useMemo(
    () => (ownStashes || []).map(({ stashId }) => stashId),
    [ownStashes]
  );

  useEffect((): void => {
    if (balances && balances.accountId.eq(senderId) && senderId && isFunction(api.rpc.payment?.queryInfo)) {
      setTimeout((): void => {
        try {
          api.tx.swork
            .createGroup()
            .paymentInfo(senderId)
            .then(({ partialFee }): void => {
              const maxTransfer = balances.availableBalance.sub(partialFee);

              setMaxTransfer(
                maxTransfer.gt(api.consts.balances.existentialDeposit)
                  ? maxTransfer
                  : null
              );
            })
            .catch(console.error);
        } catch (error) {
          console.error((error as Error).message);
        }
      }, 0);
    } else {
      setMaxTransfer(null);
    }
  }, [api, balances, senderId]);

  return (
    <Modal
      className='app--accounts-Modal'
      header={t<string>('Group create')}
      size='large'
    >
      <Modal.Content>
        <div className={className}>
          <Modal.Content>
            <Modal.Columns hint={t<string>('The transferred balance will be subtracted (along with fees) from the sender account.')}>
              <InputAddress
                defaultValue={propSenderId}
                filter={stashes}
                help={t<string>('The account you will register')}
                isDisabled={!!propSenderId}
                label={t<string>('send from account')}
                labelExtra={
                  <Available
                    label={t<string>('transferrable')}
                    params={senderId}
                  />
                }
                onChange={setSenderId}
                type='account'
              />
            </Modal.Columns>

          </Modal.Content>
        </div>
      </Modal.Content>
      <Modal.Actions onCancel={onClose}>
        <TxButton
          accountId={senderId}
          icon='paper-plane'
          isDisabled={!hasAvailable || !amount}
          label={t<string>('Create')}
          onStart={onClose}
          onSuccess={onSuccess}
          tx={api.tx.swork.createGroup}
        />
      </Modal.Actions>
    </Modal>
  );
}
Example #13
Source File: TxButton.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
function TxButton({
  accountId,
  className = '',
  extrinsic: propsExtrinsic,
  icon,
  isBasic,
  isBusy,
  isDisabled,
  isIcon,
  isToplevel,
  isUnsigned,
  label,
  onClick,
  onFailed,
  onSendRef,
  onStart,
  onSuccess,
  onUpdate,
  params,
  tooltip,
  tx,
  withSpinner,
  withoutLink,
}: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const mountedRef = useIsMountedRef();
  const { queueExtrinsic } = useContext(StatusContext);
  const [isSending, setIsSending] = useState(false);
  const [isStarted, setIsStarted] = useState(false);

  useEffect((): void => {
    // eslint-disable-next-line
    isStarted && onStart && onStart();
  }, [isStarted, onStart]);

  const _onFailed = useCallback(
    (result: SubmittableResult | null): void => {
      // eslint-disable-next-line
      mountedRef.current && setIsSending(false);

      // eslint-disable-next-line
      onFailed && onFailed(result);
    },
    [onFailed, setIsSending, mountedRef]
  );

  const _onSuccess = useCallback(
    (result: SubmittableResult): void => {
      // eslint-disable-next-line
      mountedRef.current && setIsSending(false);

      // eslint-disable-next-line
      onSuccess && onSuccess(result);
    },
    [onSuccess, setIsSending, mountedRef]
  );

  const _onStart = useCallback((): void => {
    // eslint-disable-next-line
    mountedRef.current && setIsStarted(true);
  }, [setIsStarted, mountedRef]);

  const _onSend = useCallback((): void => {
    let extrinsics: SubmittableExtrinsic<'promise'>[] | undefined;

    if (propsExtrinsic) {
      extrinsics = Array.isArray(propsExtrinsic) ? propsExtrinsic : [propsExtrinsic];
    } else if (tx) {
      extrinsics = [tx(...(isFunction(params) ? params() : params || []))];
    }

    assert(extrinsics?.length, 'Expected generated extrinsic passed to TxButton');

    // eslint-disable-next-line
    mountedRef.current && withSpinner && setIsSending(true);

    extrinsics.forEach((extrinsic): void => {
      queueExtrinsic({
        accountId: accountId && accountId.toString(),
        extrinsic,
        isUnsigned,
        txFailedCb: withSpinner ? _onFailed : onFailed,
        txStartCb: _onStart,
        txSuccessCb: withSpinner ? _onSuccess : onSuccess,
        txUpdateCb: onUpdate,
      });
    });

    // eslint-disable-next-line
    onClick && onClick();
  }, [
    _onFailed,
    _onStart,
    _onSuccess,
    accountId,
    isUnsigned,
    onClick,
    onFailed,
    onSuccess,
    onUpdate,
    params,
    propsExtrinsic,
    queueExtrinsic,
    setIsSending,
    tx,
    withSpinner,
    mountedRef,
  ]);

  if (onSendRef) {
    onSendRef.current = _onSend;
  }

  return (
    <Button
      className={className}
      icon={icon || 'check'}
      isBasic={isBasic}
      isBusy={isBusy}
      isDisabled={
        isSending ||
        isDisabled ||
        (!isUnsigned && !accountId) ||
        (tx ? false : Array.isArray(propsExtrinsic) ? propsExtrinsic.length === 0 : !propsExtrinsic)
      }
      isIcon={isIcon}
      isToplevel={isToplevel}
      label={label || (isIcon ? '' : t<string>('Submit'))}
      onClick={_onSend}
      tooltip={tooltip}
      withoutLink={withoutLink}
    />
  );
}
Example #14
Source File: index.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Payouts ({ className = '', isInElection, ownValidators }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const [hasOwnValidators] = useState(() => ownValidators.length !== 0);
  const [myStashesIndex, setMyStashesIndex] = useState(() => (isFunction(api.tx.staking.rewardStakers) && hasOwnValidators) ? 0 : 1);
  const [eraSelectionIndex, setEraSelectionIndex] = useState(0);
  const eraLength = useCall<BN>(api.derive.session.eraLength);
  const historyDepth = useCall<BN>(api.query.staking.historyDepth);
  const stakerPayoutsAfter = useStakerPayouts();
  const isDisabled = isInElection || !isFunction(api.tx.utility?.batch);

  const eraSelection = useMemo(
    () => getOptions(api, eraLength, historyDepth, t),
    [api, eraLength, historyDepth, t]
  );

  const { allRewards, isLoadingRewards } = useOwnEraRewards(eraSelection[eraSelectionIndex].value, myStashesIndex ? undefined : ownValidators);

  const { stashTotal, stashes, validators } = useMemo(
    () => getAvailable(allRewards, stakerPayoutsAfter),
    [allRewards, stakerPayoutsAfter]
  );

  const headerStashes = useMemo(() => [
    [myStashesIndex ? t('payout/stash') : t('overall/validator'), 'start', 2],
    [t('eras'), 'start'],
    // [t('available')],
    [('remaining')],
    [undefined, undefined, 4]
  ], [myStashesIndex, t]);

  const headerValidatorsRef = useRef([
    [t('payout/validator'), 'start', 2],
    [t('eras'), 'start'],
    // [t('available')],
    [('remaining')],
    [undefined, undefined, 3]
  ]);

  const valOptions = useMemo(() => [
    { isDisabled: !hasOwnValidators, text: t('Validator/Candidate rewards'), value: 'val' },
    { text: t('Guarantor rewards'), value: 'all' }
  ], [hasOwnValidators, t]);

  const footer = useMemo(() => (
    <tr>
      <td colSpan={3} />
      <td className='number'>
        {/* {stashTotal && <FormatBalance value={stashTotal} />} */}
      </td>
      <td colSpan={4} />
    </tr>
  ), [stashTotal]);

  const hasPayouts = isFunction(api.tx.staking.rewardStakers);

  return (
    <div className={className}>
      {api.tx.staking.rewardStakers && (
        <Button.Group>
          <ToggleGroup
            onChange={setMyStashesIndex}
            options={valOptions}
            value={myStashesIndex}
          />
          <ToggleGroup
            onChange={setEraSelectionIndex}
            options={eraSelection}
            value={eraSelectionIndex}
          />
          <PayButton
            isAll
            isDisabled={isDisabled}
            payout={validators}
          />
        </Button.Group>
      )}
      <ElectionBanner isInElection={isInElection} />
      {hasPayouts && !isLoadingRewards && !stashes?.length && (
        <article className='warning centered'>
          <p>{t('Payouts of rewards for a validator can be initiated by any account. This means that as soon as a validator or nominator requests a payout for an era, all the nominators for that validator will be rewarded. Each user does not need to claim individually and the suggestion is that validators should claim rewards for everybody as soon as an era ends.')}</p>
          <p>{t('If you have not claimed rewards straight after the end of the era, the validator is in the active set and you are seeing no rewards, this would mean that the reward payout transaction was made by another account on your behalf. Always check your favorite explorer to see any historic payouts made to your accounts.')}</p>
        </article>
      )}
      <Table
        empty={!isLoadingRewards && stashes && t<string>('No pending payouts for your stashes')}
        emptySpinner={t<string>('Retrieving info for the selected eras, this will take some time')}
        footer={footer}
        header={headerStashes}
        isFixed
      >
        {!isLoadingRewards && stashes?.map((payout): React.ReactNode => (
          <Stash
            isDisabled={isDisabled}
            key={payout.stashId}
            payout={payout}
            stakerPayoutsAfter={stakerPayoutsAfter}
          />
        ))}
      </Table>
      {hasPayouts && (myStashesIndex === 1) && !isLoadingRewards && validators && (validators.length !== 0) && (
        <Table
          header={headerValidatorsRef.current}
          isFixed
        >
          {!isLoadingRewards && validators.map((payout): React.ReactNode => (
            <Validator
              isDisabled={isDisabled}
              key={payout.validatorId}
              payout={payout}
            />
          ))}
        </Table>
      )}
    </div>
  );
}
Example #15
Source File: Input.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
function Input({
  autoFocus = false,
  children,
  className,
  defaultValue,
  help,
  icon,
  inputClassName,
  isAction = false,
  isDisabled = false,
  isDisabledError = false,
  isEditable = false,
  isError = false,
  isFull = false,
  isHidden = false,
  isInPlaceEditor = false,
  isReadOnly = false,
  isWarning = false,
  label,
  labelExtra,
  max,
  maxLength,
  min,
  name,
  onBlur,
  onChange,
  onEnter,
  onEscape,
  onKeyDown,
  onKeyUp,
  onPaste,
  placeholder,
  tabIndex,
  type = 'text',
  value,
  withEllipsis,
  withLabel,
}: Props): React.ReactElement<Props> {
  const [stateName] = useState(() => `in_${counter++}_at_${Date.now()}`);

  const _onBlur = useCallback(() => onBlur && onBlur(), [onBlur]);

  const _onChange = useCallback(
    ({ target }: React.SyntheticEvent<HTMLInputElement>): void =>
      onChange && onChange((target as HTMLInputElement).value),
    [onChange]
  );

  const _onKeyDown = useCallback(
    (event: React.KeyboardEvent<HTMLInputElement>): void => onKeyDown && onKeyDown(event),
    [onKeyDown]
  );

  const _onKeyUp = useCallback(
    (event: React.KeyboardEvent<HTMLInputElement>): void => {
      onKeyUp && onKeyUp(event);

      if (onEnter && event.keyCode === 13) {
        (event.target as HTMLInputElement).blur();
        isFunction(onEnter) && onEnter();
      }

      if (onEscape && event.keyCode === 27) {
        (event.target as HTMLInputElement).blur();
        onEscape();
      }
    },
    [onEnter, onEscape, onKeyUp]
  );

  const _onPaste = useCallback(
    (event: React.ClipboardEvent<HTMLInputElement>): void => onPaste && onPaste(event),
    [onPaste]
  );

  return (
    <Labelled
      className={className}
      help={help}
      isFull={isFull}
      label={label}
      labelExtra={labelExtra}
      withEllipsis={withEllipsis}
      withLabel={withLabel}
    >
      <SUIInput
        action={isAction}
        autoFocus={autoFocus}
        className={[
          isEditable ? 'ui--Input edit icon' : 'ui--Input',
          isInPlaceEditor ? 'inPlaceEditor' : '',
          inputClassName || '',
          isWarning && !isError ? 'isWarning' : '',
        ].join(' ')}
        defaultValue={isUndefined(value) ? defaultValue || '' : undefined}
        disabled={isDisabled}
        error={(!isDisabled && isError) || isDisabledError}
        hidden={isHidden}
        iconPosition={isUndefined(icon) ? undefined : 'left'}
        id={name}
        max={max}
        maxLength={maxLength}
        min={min}
        name={name || stateName}
        onBlur={_onBlur}
        onChange={_onChange}
        onKeyDown={_onKeyDown}
        onKeyUp={_onKeyUp}
        placeholder={placeholder}
        readOnly={isReadOnly}
        tabIndex={tabIndex}
        type={type}
        value={value}
      >
        <input
          autoCapitalize="off"
          autoComplete={type === 'password' ? 'new-password' : 'off'}
          autoCorrect="off"
          data-testid={label}
          onPaste={_onPaste}
          spellCheck={false}
        />
        {isEditable && <i className="edit icon" />}
        {icon}
        {children}
      </SUIInput>
    </Labelled>
  );
}
Example #16
Source File: RebondFounds.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function RebondFounds ({ className = '', foundsType, onClose, onSuccess, senderId: propSenderId }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const [amount, setAmount] = useState<BN | undefined>(BN_ZERO);
  const [hasAvailable] = useState(true);
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const [, setMaxTransfer] = useState<BN | null>(null);
  const [senderId, setSenderId] = useState<string | null>(propSenderId || null);
  const balances = useCall<DeriveBalancesAll>(api.derive.balances.all, [senderId]);

  useEffect((): void => {
    if (balances && balances.accountId.eq(senderId) && senderId && isFunction(api.rpc.payment?.queryInfo)) {
      setTimeout((): void => {
        try {
          api.tx.benefits
            .rebondBenefitFunds(balances.availableBalance, foundsType)
            .paymentInfo(senderId)
            .then(({ partialFee }): void => {
              const maxTransfer = balances.availableBalance.sub(partialFee);

              setMaxTransfer(
                maxTransfer.gt(api.consts.balances.existentialDeposit)
                  ? maxTransfer
                  : null
              );
            })
            .catch(console.error);
        } catch (error) {
          console.error((error as Error).message);
        }
      }, 0);
    } else {
      setMaxTransfer(null);
    }
  }, [api, balances, senderId]);

  return (
    <Modal
      className='app--accounts-Modal'
      header={t<string>('Rebond')}
      size='large'
    >
      <Modal.Content>
        <div className={className}>
          <Modal.Content>
            <Modal.Columns hint={t<string>('The transferred balance will be subtracted (along with fees) from the sender account.')}>
              <InputAddress
                defaultValue={propSenderId}
                help={t<string>('The account you will register')}
                isDisabled={!!propSenderId}
                label={t<string>('send from account')}
                labelExtra={
                  <Available
                    label={t<string>('transferrable')}
                    params={senderId}
                  />
                }
                onChange={setSenderId}
                type='account'
              />
            </Modal.Columns>

          </Modal.Content>
          <Modal.Content>
            <Modal.Columns>
              {<InputBalance
                autoFocus
                help={t<string>('Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 milli is equivalent to sending 0.001.')}
                isError={!hasAvailable}
                isZeroable
                label={t<string>('amount')}
                onChange={setAmount}
              />
              }
            </Modal.Columns>
          </Modal.Content>
        </div>
      </Modal.Content>
      <Modal.Actions onCancel={onClose}>
        <TxButton
          accountId={propSenderId || senderId}
          icon='paper-plane'
          isDisabled={!hasAvailable || !amount}
          label={t<string>('Rebond')}
          onStart={onClose}
          onSuccess={onSuccess}
          params={[amount, foundsType]}
          tx={api.tx.benefits.rebondBenefitFunds}
        />
      </Modal.Actions>
    </Modal>
  );
}
Example #17
Source File: WalletState.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
// eslint-disable-next-line complexity
export function WalletState() {
  const { t } = useTranslation();
  const history = useHistory();
  const { network, api, networkConfig } = useApi();
  const {
    multisigAccount,
    setMultisigAccount,
    inProgress,
    queryInProgress,
    confirmedAccount,
    refreshConfirmedAccount,
  } = useMultisigContext();
  const [isAccountsDisplay, setIsAccountsDisplay] = useState<boolean>(false);
  const [isExtrinsicDisplay, setIsExtrinsicDisplay] = useState(false);
  const [isTransferDisplay, setIsTransferDisplay] = useState(false);
  const isExtensionAccount = useIsInjected();
  const [renameModalVisible, setRenameModalVisible] = useState(false);
  const [renameInput, setRenameInput] = useState('');
  const [deleteModalVisible, setDeleteModalVisible] = useState(false);

  const { supportSubql, mainColor } = useMemo(() => {
    return {
      supportSubql: !!networkConfig?.api?.subql,
      mainColor: getThemeColor(network),
    };
  }, [network, networkConfig]);

  const showTransferButton = useMemo(() => {
    return (
      isFunction(api?.tx?.balances?.transfer) &&
      isFunction(api?.tx?.balances?.transferKeepAlive) &&
      isFunction(api?.tx?.balances?.transferAll)
    );
  }, [api]);

  const states = useMemo<{ label: string; count: number | undefined }[]>(() => {
    const res = [];
    res.push({
      label: 'multisig.In Progress',
      count: inProgress.length,
    });
    if (supportSubql) {
      res.push({ label: 'multisig.Confirmed Extrinsic', count: confirmedAccount });
    }
    res.push(
      {
        label: 'multisig.Threshold',
        count: multisigAccount?.meta.threshold as number,
      },
      {
        label: 'multisig.Members',
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        count: (multisigAccount?.meta.who as any)?.length as number,
      }
    );
    return res;
  }, [inProgress.length, confirmedAccount, multisigAccount?.meta.threshold, multisigAccount?.meta.who, supportSubql]);
  const renameWallet = useCallback(
    ({ name }: { name: string }) => {
      try {
        const pair = keyring.getPair(multisigAccount?.address as string);
        keyring.saveAccountMeta(pair, {
          name,
          whenEdited: Date.now(),
        });
        message.success(t('success'));
        setRenameModalVisible(false);

        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const { meta, ...others } = multisigAccount!;
        if (setMultisigAccount) {
          setMultisigAccount({
            ...others,
            meta: {
              ...meta,
              name,
            },
          });
        }
      } catch (error: unknown) {
        if (error instanceof Error) {
          message.error(error.message);
        }
      }
    },
    [multisigAccount, t, setMultisigAccount]
  );
  const deleteWallet = useCallback(() => {
    try {
      keyring.forgetAccount(multisigAccount?.address as string);
      message.success(t('success'));
      history.push('/' + history.location.hash);
    } catch (error: unknown) {
      if (error instanceof Error) {
        message.error(error.message);
      }
    }
  }, [multisigAccount?.address, t, history]);

  useEffect(() => {
    const id = setInterval(() => refreshConfirmedAccount(), LONG_DURATION);

    return () => clearInterval(id);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const exportAccountConfig = () => {
    if (!multisigAccount) {
      return;
    }
    const config = {
      name: multisigAccount.meta.name,
      members: multisigAccount.meta.addressPair as KeyringJson[],
      threshold: multisigAccount.meta.threshold,
      scope: getMultiAccountScope(multisigAccount.publicKey),
    };

    const blob = new Blob([JSON.stringify(config)], { type: 'text/plain;charset=utf-8' });
    saveAs(blob, `${multisigAccount.address}.json`);
  };

  const menu = (
    <Menu>
      {/* <Menu.Item key="0">
        <a href="https://www.antgroup.com">View in Subscan</a>
      </Menu.Item> */}
      <Menu.Item key="1" onClick={exportAccountConfig}>
        Export config
      </Menu.Item>
      <Menu.Item
        key="3"
        onClick={() => {
          setRenameInput(multisigAccount?.meta.name || '');
          setRenameModalVisible(true);
        }}
      >
        Rename
      </Menu.Item>
      <Menu.Item key="4" onClick={() => setDeleteModalVisible(true)}>
        Delete
      </Menu.Item>
    </Menu>
  );

  return (
    <Space direction="vertical" className="w-full">
      <div className="flex flex-col md:flex-row items-start md:items-center md:justify-between">
        <div className="self-stretch md:self-center">
          <div className="flex items-center gap-4 md:w-auto w-full">
            <Text className="whitespace-nowrap font-semibold text-xl leading-none" style={{ color: mainColor }}>
              {multisigAccount?.meta.name}
            </Text>

            <Dropdown overlay={menu} trigger={['click']} placement="bottomCenter">
              <SettingOutlined
                className="rounded-full opacity-60 cursor-pointer p-1"
                style={{
                  color: mainColor,
                  backgroundColor: mainColor + '40',
                }}
                onClick={(e) => e.preventDefault()}
              />
            </Dropdown>
          </div>

          <div className="flex flex-col md:flex-row md:items-center gap-4 md:w-auto w-full mt-2 mb-4 md:mb-0">
            <SubscanLink address={multisigAccount?.address} copyable></SubscanLink>
          </div>
        </div>

        {((multisigAccount?.meta.addressPair as KeyringJson[]) || []).some((pair) =>
          isExtensionAccount(pair.address)
        ) && (
          <div className="flex items-center mt-2 md:mt-0">
            {showTransferButton && (
              <Button
                onClick={() => setIsTransferDisplay(true)}
                type="default"
                size="large"
                className="w-full md:w-auto mt-4 md:mt-0 mr-4"
                style={{ color: mainColor }}
              >
                {t('transfer')}
              </Button>
            )}

            <Button
              onClick={() => setIsExtrinsicDisplay(true)}
              type="primary"
              size="large"
              className="w-full md:w-auto mt-4 md:mt-0"
            >
              {t('submit_extrinsic')}
            </Button>
          </div>
        )}
      </div>

      <div style={{ height: '1px', backgroundColor: mainColor, opacity: 0.1 }} className="mt-2" />

      <Space size="middle" className="items-center hidden md:flex mt-2">
        {states.map((state, index) => (
          <Space key={index}>
            <b>{t(state.label)}</b>
            <span>{state.count}</span>
          </Space>
        ))}

        <div
          style={{ border: 'solid 1px #DBDBDB', transform: isAccountsDisplay ? 'rotate(180deg)' : '' }}
          className="w-12 h-6 flex items-center justify-center rounded-md cursor-pointer"
          onClick={() => setIsAccountsDisplay(!isAccountsDisplay)}
        >
          <img src={iconDown} />
        </div>
      </Space>

      <div className="grid md:hidden grid-cols-2">
        {states.map((state, index) => (
          <Statistic title={t(state.label)} value={state.count} key={index} className="text-center" />
        ))}

        <Button type="ghost" className="col-span-2" onClick={() => setIsAccountsDisplay(!isAccountsDisplay)}>
          {t(isAccountsDisplay ? 'wallet:Hide members' : 'wallet:Show members')}
        </Button>
      </div>

      {isAccountsDisplay && multisigAccount && <Members record={multisigAccount} />}

      <Modal
        title={t('submit_extrinsic')}
        visible={isExtrinsicDisplay}
        onCancel={() => setIsExtrinsicDisplay(false)}
        style={{ minWidth: 800 }}
        footer={null}
        destroyOnClose
      >
        <ExtrinsicLaunch
          onTxSuccess={() => {
            setIsExtrinsicDisplay(false);
            queryInProgress();
          }}
        />
      </Modal>

      {isTransferDisplay && (
        <Transfer
          key="modal-transfer"
          onClose={() => setIsTransferDisplay(false)}
          senderId={multisigAccount?.address}
          onTxSuccess={() => {
            setIsTransferDisplay(false);
            queryInProgress();
          }}
        />
      )}

      <Modal
        title={null}
        footer={null}
        visible={renameModalVisible}
        destroyOnClose
        onCancel={() => setRenameModalVisible(false)}
        bodyStyle={{
          paddingLeft: '80px',
          paddingRight: '80px',
          paddingBottom: '60px',
        }}
      >
        <div className="text-center text-black-800 text-xl font-semibold leading-none">Rename</div>

        <div className="text-sm text-black-800 font-semibold mt-6 mb-1">Name</div>

        <Input
          value={renameInput}
          onChange={(e) => {
            setRenameInput(e.target.value);
          }}
        />

        <Row gutter={16} className="mt-5">
          <Col span={12}>
            <Button
              block
              type="primary"
              onClick={() => {
                if (!renameInput.trim()) {
                  return;
                }
                renameWallet({ name: renameInput });
              }}
            >
              OK
            </Button>
          </Col>

          <Col span={12}>
            <Button
              block
              style={{
                color: mainColor,
              }}
              onClick={() => {
                setRenameModalVisible(false);
              }}
            >
              Cancel
            </Button>
          </Col>
        </Row>
      </Modal>

      <ConfirmDialog
        visible={deleteModalVisible}
        onCancel={() => setDeleteModalVisible(false)}
        title="Delete Wallet"
        content={`Are you sure to delete “${multisigAccount?.meta.name}” ?`}
        onConfirm={deleteWallet}
      />
    </Space>
  );
}