@polkadot/util#assert TypeScript Examples

The following examples show how to use @polkadot/util#assert. 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
// split is 65-byte signature into the r, s (combined) and recovery number (derived from v)
export function sigToParts (_signature: string): SignatureParts {
  const signature = hexHasPrefix(_signature) ? hexToU8a(_signature) : hexToU8a(hexAddPrefix(_signature));

  assert(signature.length === 65, `Invalid signature length, expected 65 found ${signature.length}`);

  let v = signature[64];

  if (v < 27) {
    v += 27;
  }

  const recovery = v - 27;

  assert(recovery === 0 || recovery === 1, 'Invalid signature v value');

  return {
    recovery,
    signature: u8aToBuffer(signature.slice(0, 64))
  };
}
Example #2
Source File: TxSigned.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
async function extractParams (api: ApiPromise, address: string, options: Partial<SignerOptions>, getLedger: () => Ledger, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial<SignerOptions>]> {
  const pair = keyring.getPair(address);
  const { meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, isProxied, source } } = pair;

  if (isHardware) {
    return ['signing', address, { ...options, signer: new LedgerSigner(api.registry, getLedger, accountOffset as number || 0, addressOffset as number || 0) }];
  } else if (isExternal && !isProxied) {
    return ['qr', address, { ...options, signer: new QrSigner(api.registry, setQrState) }];
  } else if (isInjected) {
    const injected = await web3FromSource(source as string);

    assert(injected, `Unable to find a signer for ${address}`);

    return ['signing', address, { ...options, signer: injected.signer }];
  }

  assert(addressEq(address, pair.address), `Unable to retrieve keypair for ${address}`);

  return ['signing', address, { ...options, signer: new AccountSigner(api.registry, pair) }];
}
Example #3
Source File: index.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
async function submitRpc(
  api: ApiPromise,
  { method, section }: DefinitionRpcExt,
  values: any[]
): Promise<QueueTxResult> {
  try {
    const rpc = api.rpc as Record<string, Record<string, (...params: unknown[]) => Promise<unknown>>>;

    assert(isFunction(rpc[section] && rpc[section][method]), `api.rpc.${section}.${method} does not exist`);

    const result = await rpc[section][method](...values);

    return {
      result,
      status: 'sent',
    };
  } catch (error) {
    console.error(error);

    return {
      error: error as Error,
      status: 'error',
    };
  }
}
Example #4
Source File: KeyValueArray.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
function parseFile(raw: Uint8Array): Parsed {
  const json = JSON.parse(u8aToString(raw)) as Record<string, string>;
  const keys = Object.keys(json);
  let isValid = keys.length !== 0;
  const value = keys.map((key): [Uint8Array, Uint8Array] => {
    const val = json[key];

    assert(isHex(key) && isHex(val), `Non-hex key/value pair found in ${key.toString()} => ${val.toString()}`);

    const encKey = createParam(key);
    const encValue = createParam(val);

    isValid = isValid && encKey.isValid && encValue.isValid;

    return [encKey.u8a, encValue.u8a];
  });

  return {
    isValid,
    value,
  };
}
Example #5
Source File: useLedger.ts    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
function retrieveLedger(api: ApiPromise): Ledger {
  const currType = uiSettings.ledgerConn as LedgerTypes;

  if (!ledger || ledgerType !== currType) {
    const genesisHex = api.genesisHash.toHex();
    const def = ledgerChains.find(({ genesisHash }) => genesisHash[0] === genesisHex);

    assert(def, `Unable to find supported chain for ${genesisHex}`);

    ledger = new Ledger(currType, def.network);
    ledgerType = currType;
  }

  return ledger;
}
Example #6
Source File: toAddress.ts    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
// eslint-disable-next-line complexity
export default function toAddress(value?: string | Uint8Array | null, allowIndices = false): string | undefined {
  if (value) {
    try {
      const u8a = isHex(value) ? hexToU8a(value) : keyring.decodeAddress(value);

      assert(allowIndices || u8a.length === 32 || u8a.length === 20, 'AccountIndex values not allowed');

      if (u8a.length === 20) {
        return ethereumEncode(u8a);
      } else {
        return keyring.encodeAddress(u8a);
      }
    } catch (error) {
      // noop, undefined return indicates invalid/transient
    }
  }

  return undefined;
}
Example #7
Source File: urlTypes.ts    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
export function decodeUrlTypes(): Record<string, any> | null {
  const urlOptions = queryString.parse(location.href.split('?')[1]);

  if (urlOptions.types) {
    try {
      assert(!Array.isArray(urlOptions.types), 'Expected a single type specification');

      const parts = urlOptions.types.split('#');
      const compressed = base64Decode(decodeURIComponent(parts[0]));
      const uncompressed = unzlibSync(compressed);

      return JSON.parse(u8aToString(uncompressed)) as Record<string, any>;
    } catch (error) {
      console.error(error);
    }
  }

  return null;
}
Example #8
Source File: api.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
export default function withApi<P extends ApiProps>(
  Inner: React.ComponentType<P>,
  defaultProps: DefaultProps = {}
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
): React.ComponentType<any> {
  class WithApi extends React.PureComponent<SubtractProps<P, ApiProps>> {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    private component: any = React.createRef();

    public render(): React.ReactNode {
      return (
        <ApiConsumer>
          {(apiProps?: ApiProps): React.ReactNode => {
            assert(
              apiProps && apiProps.api,
              `Application root must be wrapped inside 'react-api/Api' to provide API context`
            );

            return (
              <Inner
                {...defaultProps}
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                {...(apiProps as any)}
                {...this.props}
                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                ref={this.component}
              />
            );
          }}
        </ApiConsumer>
      );
    }
  }

  return WithApi;
}
Example #9
Source File: index.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
async function submitRpc (api: ApiPromise, { method, section }: DefinitionRpcExt, values: any[]): Promise<QueueTxResult> {
  try {
    const rpc = api.rpc as Record<string, Record<string, (...params: unknown[]) => Promise<unknown>>>;

    assert(isFunction(rpc[section] && rpc[section][method]), `api.rpc.${section}.${method} does not exist`);

    const result = await rpc[section][method](...values);

    console.log('submitRpc: result ::', loggerFormat(result));

    return {
      result,
      status: 'sent'
    };
  } catch (error) {
    console.error(error);

    return {
      error: error as Error,
      status: 'error'
    };
  }
}
Example #10
Source File: KeyValueArray.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function parseFile (raw: Uint8Array): Parsed {
  const json = JSON.parse(u8aToString(raw)) as Record<string, string>;
  const keys = Object.keys(json);
  let isValid = keys.length !== 0;
  const value = keys.map((key): [Uint8Array, Uint8Array] => {
    const value = json[key];

    assert(isHex(key) && isHex(value), `Non-hex key/value pair found in ${key.toString()} => ${value.toString()}`);

    const encKey = createParam(key);
    const encValue = createParam(value);

    isValid = isValid && encKey.isValid && encValue.isValid;

    return [encKey.u8a, encValue.u8a];
  });

  return {
    isValid,
    value
  };
}
Example #11
Source File: useLedger.ts    From crust-apps with Apache License 2.0 6 votes vote down vote up
function retrieveLedger (api: ApiPromise): Ledger {
  if (!ledger) {
    const genesisHex = api.genesisHash.toHex();
    const def = ledgerChains.find(({ genesisHash }) => genesisHash[0] === genesisHex);

    assert(def, `Unable to find supported chain for ${genesisHex}`);

    ledger = new Ledger(uiSettings.ledgerConn as LedgerTypes, def.network);
  }

  return ledger;
}
Example #12
Source File: toAddress.ts    From crust-apps with Apache License 2.0 6 votes vote down vote up
export default function toAddress (value?: string | Uint8Array | null, allowIndices = false): string | undefined {
  if (value) {
    try {
      const u8a = isHex(value)
        ? hexToU8a(value)
        : keyring.decodeAddress(value);

      assert(allowIndices || u8a.length === 32 || u8a.length === 20, 'AccountIndex values not allowed');

      if (u8a.length === 20) {
        return ethereumEncode(u8a);
      } else {
        return keyring.encodeAddress(u8a);
      }
    } catch (error) {
      // noop, undefined return indicates invalid/transient
    }
  }

  return undefined;
}
Example #13
Source File: urlTypes.ts    From crust-apps with Apache License 2.0 6 votes vote down vote up
export function decodeUrlTypes (): Record<string, any> | null {
  const urlOptions = queryString.parse(location.href.split('?')[1]);

  if (urlOptions.types) {
    try {
      assert(!Array.isArray(urlOptions.types), 'Expected a single type specification');

      const parts = urlOptions.types.split('#');
      const compressed = base64Decode(decodeURIComponent(parts[0]));
      const uncompressed = unzlibSync(compressed);

      return JSON.parse(u8aToString(uncompressed)) as Record<string, any>;
    } catch (error) {
      console.error(error);
    }
  }

  return null;
}
Example #14
Source File: api.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
export default function withApi <P extends ApiProps> (Inner: React.ComponentType<P>, defaultProps: DefaultProps = {}): React.ComponentType<any> {
  class WithApi extends React.PureComponent<SubtractProps<P, ApiProps>> {
    private component: any = React.createRef();

    public render (): React.ReactNode {
      return (
        <ApiConsumer>
          {(apiProps?: ApiProps): React.ReactNode => {
            assert(apiProps && apiProps.api, 'Application root must be wrapped inside \'react-api/Api\' to provide API context');

            return (
              <Inner
                {...defaultProps}
                {...(apiProps as any)}
                {...this.props}
                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                ref={this.component}
              />
            );
          }}
        </ApiConsumer>
      );
    }
  }

  return WithApi;
}
Example #15
Source File: initSettings.ts    From crust-apps with Apache License 2.0 5 votes vote down vote up
function getApiUrl (): string {
  // we split here so that both these forms are allowed
  //  - http://localhost:3000/?rpc=wss://substrate-rpc.parity.io/#/explorer
  //  - http://localhost:3000/#/explorer?rpc=wss://substrate-rpc.parity.io
  const urlOptions = queryString.parse(location.href.split('?')[1]);

  // const randomIndex = new Date().getTime() % endPoints.length;

  // return endPoints[randomIndex];

  // if specified, this takes priority
  if (urlOptions.rpc) {
    assert(!Array.isArray(urlOptions.rpc), 'Invalid WS endpoint specified');

    // https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9944#/explorer;
    const url = decodeURIComponent(urlOptions.rpc.split('#')[0]);

    assert(url.startsWith('ws://') || url.startsWith('wss://'), 'Non-prefixed ws/wss url');

    return url;
  }

  const endpoints = createWsEndpoints(<T = string>(): T => ('' as unknown as T));
  const { ipnsChain } = extractIpfsDetails();

  // check against ipns domains (could be expanded to others)
  if (ipnsChain) {
    const option = endpoints.find(({ dnslink }) => dnslink === ipnsChain);

    if (option) {
      return option.value as string;
    }
  }

  const stored = store.get('settings') as Record<string, unknown> || {};
  const fallbackUrl = endpoints.find(({ value }) => !!value);

  // via settings, or the default chain
  return [stored.apiUrl, process.env.WS_URL].includes(settings.apiUrl)
    ? settings.apiUrl // keep as-is
    : fallbackUrl
      ? fallbackUrl.value as string // grab the fallback
      : 'ws://127.0.0.1:9944'; // nothing found, go local
}
Example #16
Source File: networkSpect.ts    From sdk with Apache License 2.0 5 votes vote down vote up
function getGenesis(name: string): string {
  const network = allNetworks.find(({ network }) => network === name);
  assert(network && network.genesisHash[0], `Unable to find genesisHash for ${name}`);
  return network.genesisHash[0];
}
Example #17
Source File: TxSigned.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
async function extractParams(
  api: ApiPromise,
  address: string,
  options: Partial<SignerOptions>,
  getLedger: () => Ledger,
  setQrState: (state: QrState) => void
): Promise<['qr' | 'signing', string, Partial<SignerOptions>]> {
  const pair = keyring.getPair(address);
  const {
    meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, isProxied, source },
  } = pair;

  if (isHardware) {
    return [
      'signing',
      address,
      {
        ...options,
        signer: new LedgerSigner(
          api.registry,
          getLedger,
          (accountOffset as number) || 0,
          (addressOffset as number) || 0
        ),
      },
    ];
  } else if (isExternal && !isProxied) {
    return ['qr', address, { ...options, signer: new QrSigner(api.registry, setQrState) }];
  } else if (isInjected) {
    const injected = await web3FromSource(source as string);

    assert(injected, `Unable to find a signer for ${address}`);

    return ['signing', address, { ...options, signer: injected.signer }];
  }

  assert(addressEq(address, pair.address), `Unable to retrieve keypair for ${address}`);

  return ['signing', address, { ...options, signer: new AccountSigner(api.registry, pair) }];
}
Example #18
Source File: constants.ts    From crust-apps with Apache License 2.0 5 votes vote down vote up
function getGenesis (name: string): string {
  const network = networks.find(({ network }) => network === name);

  assert(network && network.genesisHash[0], `Unable to find genesisHash for ${name}`);

  return network.genesisHash[0];
}
Example #19
Source File: TxButton.tsx    From crust-apps 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 => {
    (isStarted && onStart) && onStart();
  }, [isStarted, onStart]);

  const _onFailed = useCallback(
    (result: SubmittableResult | null): void => {
      mountedRef.current && setIsSending(false);

      onFailed && onFailed(result);
    },
    [onFailed, setIsSending, mountedRef]
  );

  const _onSuccess = useCallback(
    (result: SubmittableResult): void => {
      mountedRef.current && setIsSending(false);

      onSuccess && onSuccess(result);
    },
    [onSuccess, setIsSending, mountedRef]
  );

  const _onStart = useCallback(
    (): void => {
      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');

      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
        });
      });

      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 #20
Source File: call.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
export default function withCall<P extends ApiProps>(
  endpoint: string,
  {
    at,
    atProp,
    callOnResult,
    fallbacks,
    isMulti = false,
    params = [],
    paramName,
    paramPick,
    paramValid = false,
    propName,
    skipIf = NO_SKIP,
    transform = echoTransform,
    withIndicator = false,
  }: Options = {}
): (Inner: React.ComponentType<ApiProps>) => React.ComponentType<any> {
  return (Inner: React.ComponentType<ApiProps>): React.ComponentType<SubtractProps<P, ApiProps>> => {
    class WithPromise extends React.Component<P, State> {
      public state: State = {
        callResult: undefined,
        callUpdated: false,
        callUpdatedAt: 0,
      };

      private destroy?: () => void;

      private isActive = false;

      private propName: string;

      private timerId = -1;

      constructor(props: P) {
        super(props);

        const [, section, method] = endpoint.split('.');

        this.propName = `${section}_${method}`;
      }

      public componentDidUpdate(prevProps: any): void {
        const oldParams = this.getParams(prevProps);
        const newParams = this.getParams(this.props);

        if (this.isActive && !isEqual(newParams, oldParams)) {
          this.subscribe(newParams).then(NOOP).catch(NOOP);
        }
      }

      public componentDidMount(): void {
        this.isActive = true;

        if (withIndicator) {
          this.timerId = window.setInterval((): void => {
            const elapsed = Date.now() - (this.state.callUpdatedAt || 0);
            const callUpdated = elapsed <= 1500;

            if (callUpdated !== this.state.callUpdated) {
              this.nextState({ callUpdated });
            }
          }, 500);
        }

        // The attachment takes time when a lot is available, set a timeout
        // to first handle the current queue before subscribing
        setTimeout((): void => {
          this.subscribe(this.getParams(this.props)).then(NOOP).catch(NOOP);
        }, 0);
      }

      public componentWillUnmount(): void {
        this.isActive = false;

        this.unsubscribe().then(NOOP).catch(NOOP);

        if (this.timerId !== -1) {
          clearInterval(this.timerId);
        }
      }

      private nextState(state: Partial<State>): void {
        if (this.isActive) {
          this.setState(state as State);
        }
      }

      private getParams(props: any): [boolean, any[]] {
        const paramValue = paramPick ? paramPick(props) : paramName ? props[paramName] : undefined;

        if (atProp) {
          at = props[atProp];
        }

        // When we are specifying a param and have an invalid, don't use it. For 'params',
        // we default to the original types, i.e. no validation (query app uses this)
        if (!paramValid && paramName && (isUndefined(paramValue) || isNull(paramValue))) {
          return [false, []];
        }

        const values = isUndefined(paramValue)
          ? params
          : params.concat(Array.isArray(paramValue) && !(paramValue as any).toU8a ? paramValue : [paramValue]);

        return [true, values];
      }

      // eslint-disable-next-line @typescript-eslint/no-shadow
      private constructApiSection = (endpoint: string): [Record<string, Method>, string, string, string] => {
        // eslint-disable-next-line no-invalid-this
        const { api } = this.props;
        const [area, section, method, ...others] = endpoint.split('.');

        assert(
          area.length && section.length && method.length && others.length === 0,
          `Invalid API format, expected <area>.<section>.<method>, found ${endpoint}`
        );
        assert(
          ['consts', 'rpc', 'query', 'derive'].includes(area),
          `Unknown api.${area}, expected consts, rpc, query or derive`
        );
        assert(!at || area === 'query', `Only able to do an 'at' query on the api.query interface`);

        const apiSection = (api as any)[area][section];

        return [apiSection, area, section, method];
      };

      private getApiMethod(newParams: any[]): ApiMethodInfo {
        if (endpoint === 'subscribe') {
          // eslint-disable-next-line @typescript-eslint/no-shadow
          const [fn, ...params] = newParams;

          return [fn, params, 'subscribe'];
        }

        const endpoints: string[] = [endpoint].concat(fallbacks || []);
        const expanded = endpoints.map(this.constructApiSection);
        // eslint-disable-next-line @typescript-eslint/no-shadow
        const [apiSection, area, section, method] = expanded.find(([apiSection]): boolean => !!apiSection) || [
          {},
          expanded[0][1],
          expanded[0][2],
          expanded[0][3],
        ];

        assert(apiSection && apiSection[method], `Unable to find api.${area}.${section}.${method}`);

        const meta = apiSection[method].meta;

        if (area === 'query' && meta?.type.isMap) {
          const arg = newParams[0];

          assert(
            (!isUndefined(arg) && !isNull(arg)) || meta.type.asMap.kind.isLinkedMap,
            `${meta.name} expects one argument`
          );
        }

        return [apiSection[method], newParams, method.startsWith('subscribe') ? 'subscribe' : area];
      }

      private async subscribe([isValid, newParams]: [boolean, any[]]): Promise<void> {
        if (!isValid || skipIf(this.props)) {
          return;
        }

        const { api } = this.props;
        let info: ApiMethodInfo | undefined;

        await api.isReady;

        try {
          assert(at || !atProp, 'Unable to perform query on non-existent at hash');

          info = this.getApiMethod(newParams);
        } catch (error) {
          // don't flood the console with the same errors each time, just do it once, then
          // ignore it going forward
          if (!errorred[(error as Error).message]) {
            console.warn(endpoint, '::', error);

            errorred[(error as Error).message] = true;
          }
        }

        if (!info) {
          return;
        }

        // eslint-disable-next-line @typescript-eslint/no-shadow
        const [apiMethod, params, area] = info;
        const updateCb = (value?: any): void => this.triggerUpdate(this.props, value);

        await this.unsubscribe();

        try {
          if (['derive', 'subscribe'].includes(area) || (area === 'query' && !at && !atProp)) {
            this.destroy = isMulti ? await apiMethod.multi(params, updateCb) : await apiMethod(...params, updateCb);
          } else if (area === 'consts') {
            updateCb(apiMethod);
          } else {
            updateCb(at ? await apiMethod.at(at, ...params) : await apiMethod(...params));
          }
        } catch (error) {
          // console.warn(endpoint, '::', error);
        }
      }

      // eslint-disable-next-line @typescript-eslint/require-await
      private async unsubscribe(): Promise<void> {
        if (this.destroy) {
          this.destroy();
          this.destroy = undefined;
        }
      }

      private triggerUpdate(props: any, value?: any): void {
        try {
          const callResult = (props.transform || transform)(value);

          if (!this.isActive || isEqual(callResult, this.state.callResult)) {
            return;
          }

          triggerChange(callResult, callOnResult, props.callOnResult);

          this.nextState({
            callResult,
            callUpdated: true,
            callUpdatedAt: Date.now(),
          });
        } catch (error) {
          // console.warn(endpoint, '::', (error as Error).message);
        }
      }

      public render(): React.ReactNode {
        const { callResult, callUpdated, callUpdatedAt } = this.state;
        const _props = {
          ...this.props,
          callUpdated,
          callUpdatedAt,
        };

        if (!isUndefined(callResult)) {
          (_props as any)[propName || this.propName] = callResult;
        }

        return <Inner {..._props} />;
      }
    }

    return withApi(WithPromise);
  };
}
Example #21
Source File: call.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
export default function withCall<P extends ApiProps> (endpoint: string, { at, atProp, callOnResult, fallbacks, isMulti = false, params = [], paramName, paramPick, paramValid = false, propName, skipIf = NO_SKIP, transform = echoTransform, withIndicator = false }: Options = {}): (Inner: React.ComponentType<ApiProps>) => React.ComponentType<any> {
  return (Inner: React.ComponentType<ApiProps>): React.ComponentType<SubtractProps<P, ApiProps>> => {
    class WithPromise extends React.Component<P, State> {
      public state: State = {
        callResult: undefined,
        callUpdated: false,
        callUpdatedAt: 0
      };

      private destroy?: () => void;

      private isActive = false;

      private propName: string;

      private timerId = -1;

      constructor (props: P) {
        super(props);

        const [, section, method] = endpoint.split('.');

        this.propName = `${section}_${method}`;
      }

      public componentDidUpdate (prevProps: any): void {
        const oldParams = this.getParams(prevProps);
        const newParams = this.getParams(this.props);

        if (this.isActive && !isEqual(newParams, oldParams)) {
          this
            .subscribe(newParams)
            .then(NOOP)
            .catch(NOOP);
        }
      }

      public componentDidMount (): void {
        this.isActive = true;

        if (withIndicator) {
          this.timerId = window.setInterval((): void => {
            const elapsed = Date.now() - (this.state.callUpdatedAt || 0);
            const callUpdated = elapsed <= 1500;

            if (callUpdated !== this.state.callUpdated) {
              this.nextState({ callUpdated });
            }
          }, 500);
        }

        // The attachment takes time when a lot is available, set a timeout
        // to first handle the current queue before subscribing
        setTimeout((): void => {
          this
            .subscribe(this.getParams(this.props))
            .then(NOOP)
            .catch(NOOP);
        }, 0);
      }

      public componentWillUnmount (): void {
        this.isActive = false;

        this.unsubscribe()
          .then(NOOP)
          .catch(NOOP);

        if (this.timerId !== -1) {
          clearInterval(this.timerId);
        }
      }

      private nextState (state: Partial<State>): void {
        if (this.isActive) {
          this.setState(state as State);
        }
      }

      private getParams (props: any): [boolean, any[]] {
        const paramValue = paramPick
          ? paramPick(props)
          : paramName
            ? props[paramName]
            : undefined;

        if (atProp) {
          at = props[atProp];
        }

        // When we are specifying a param and have an invalid, don't use it. For 'params',
        // we default to the original types, i.e. no validation (query app uses this)
        if (!paramValid && paramName && (isUndefined(paramValue) || isNull(paramValue))) {
          return [false, []];
        }

        const values = isUndefined(paramValue)
          ? params
          : params.concat(
            (Array.isArray(paramValue) && !(paramValue as any).toU8a)
              ? paramValue
              : [paramValue]
          );

        return [true, values];
      }

      private constructApiSection = (endpoint: string): [Record<string, Method>, string, string, string] => {
        const { api } = this.props;
        const [area, section, method, ...others] = endpoint.split('.');

        assert(area.length && section.length && method.length && others.length === 0, `Invalid API format, expected <area>.<section>.<method>, found ${endpoint}`);
        assert(['consts', 'rpc', 'query', 'derive'].includes(area), `Unknown api.${area}, expected consts, rpc, query or derive`);
        assert(!at || area === 'query', 'Only able to do an \'at\' query on the api.query interface');

        const apiSection = (api as any)[area][section];

        return [
          apiSection,
          area,
          section,
          method
        ];
      }

      private getApiMethod (newParams: any[]): ApiMethodInfo {
        if (endpoint === 'subscribe') {
          const [fn, ...params] = newParams;

          return [
            fn,
            params,
            'subscribe'
          ];
        }

        const endpoints: string[] = [endpoint].concat(fallbacks || []);
        const expanded = endpoints.map(this.constructApiSection);
        const [apiSection, area, section, method] = expanded.find(([apiSection]): boolean =>
          !!apiSection
        ) || [{}, expanded[0][1], expanded[0][2], expanded[0][3]];

        assert(apiSection && apiSection[method], `Unable to find api.${area}.${section}.${method}`);

        const meta = apiSection[method].meta;

        if (area === 'query' && meta?.type.isMap) {
          const arg = newParams[0];

          assert((!isUndefined(arg) && !isNull(arg)) || meta.type.asMap.kind.isLinkedMap, `${meta.name} expects one argument`);
        }

        return [
          apiSection[method],
          newParams,
          method.startsWith('subscribe') ? 'subscribe' : area
        ];
      }

      private async subscribe ([isValid, newParams]: [boolean, any[]]): Promise<void> {
        if (!isValid || skipIf(this.props)) {
          return;
        }

        const { api } = this.props;
        let info: ApiMethodInfo | undefined;

        await api.isReady;

        try {
          assert(at || !atProp, 'Unable to perform query on non-existent at hash');

          info = this.getApiMethod(newParams);
        } catch (error) {
          // don't flood the console with the same errors each time, just do it once, then
          // ignore it going forward
          if (!errorred[(error as Error).message]) {
            console.warn(endpoint, '::', error);

            errorred[(error as Error).message] = true;
          }
        }

        if (!info) {
          return;
        }

        const [apiMethod, params, area] = info;
        const updateCb = (value?: any): void =>
          this.triggerUpdate(this.props, value);

        await this.unsubscribe();

        try {
          if (['derive', 'subscribe'].includes(area) || (area === 'query' && (!at && !atProp))) {
            this.destroy = isMulti
              ? await apiMethod.multi(params, updateCb)
              : await apiMethod(...params, updateCb);
          } else if (area === 'consts') {
            updateCb(apiMethod);
          } else {
            updateCb(
              at
                ? await apiMethod.at(at, ...params)
                : await apiMethod(...params)
            );
          }
        } catch (error) {
          // console.warn(endpoint, '::', error);
        }
      }

      // eslint-disable-next-line @typescript-eslint/require-await
      private async unsubscribe (): Promise<void> {
        if (this.destroy) {
          this.destroy();
          this.destroy = undefined;
        }
      }

      private triggerUpdate (props: any, value?: any): void {
        try {
          const callResult = (props.transform || transform)(value);

          if (!this.isActive || isEqual(callResult, this.state.callResult)) {
            return;
          }

          triggerChange(callResult, callOnResult, props.callOnResult);

          this.nextState({
            callResult,
            callUpdated: true,
            callUpdatedAt: Date.now()
          });
        } catch (error) {
          // console.warn(endpoint, '::', (error as Error).message);
        }
      }

      public render (): React.ReactNode {
        const { callResult, callUpdated, callUpdatedAt } = this.state;
        const _props = {
          ...this.props,
          callUpdated,
          callUpdatedAt
        };

        if (!isUndefined(callResult)) {
          (_props as any)[propName || this.propName] = callResult;
        }

        return (
          <Inner {..._props} />
        );
      }
    }

    return withApi(WithPromise);
  };
}
Example #22
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 #23
Source File: InjectKeys.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function InjectKeys ({ onClose }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();
  const { queueRpc } = useContext(StatusContext);
  // this needs to align with what is set as the first value in `type`
  const [crypto, setCrypto] = useState<KeypairType>('sr25519');
  const [publicKey, setPublicKey] = useState(EMPTY_KEY);
  const [suri, setSuri] = useState('');
  const [keyType, setKeyType] = useState('babe');

  const keyTypeOptRef = useRef([
    { text: t<string>('Aura'), value: 'aura' },
    { text: t<string>('Babe'), value: 'babe' },
    { text: t<string>('Grandpa'), value: 'gran' },
    { text: t<string>('I\'m Online'), value: 'imon' },
    { text: t<string>('Parachains'), value: 'para' }
  ]);

  useEffect((): void => {
    setCrypto(CRYPTO_MAP[keyType][0]);
  }, [keyType]);

  useEffect((): void => {
    try {
      const { phrase } = keyExtractSuri(suri);

      assert(mnemonicValidate(phrase), 'Invalid mnemonic phrase');

      setPublicKey(u8aToHex(keyring.createFromUri(suri, {}, crypto).publicKey));
    } catch (error) {
      setPublicKey(EMPTY_KEY);
    }
  }, [crypto, suri]);

  const _onSubmit = useCallback(
    (): void => queueRpc({
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      rpc: { method: 'insertKey', section: 'author' } as any,
      values: [keyType, suri, publicKey]
    }),
    [keyType, publicKey, queueRpc, suri]
  );

  const _cryptoOptions = useMemo(
    () => CRYPTO_MAP[keyType].map((value): { text: string; value: KeypairType } => ({
      text: value === 'ed25519'
        ? t<string>('ed25519, Edwards')
        : t<string>('sr15519, Schnorrkel'),
      value
    })),
    [keyType, t]
  );

  return (
    <Modal
      header={t<string>('Inject Keys')}
      size='large'
    >
      <Modal.Content>
        <Modal.Columns hint={t<string>('The seed and derivation path will be submitted to the validator node. this is an advanced operation, only to be performed when you are sure of the security and connection risks.')}>
          <Input
            autoFocus
            isError={publicKey.length !== 66}
            label={t<string>('suri (seed & derivation)')}
            onChange={setSuri}
            value={suri}
          />
          <MarkWarning content={t<string>('This operation will submit the seed via an RPC call. Do not perform this operation on a public RPC node, but ensure that the node is local, connected to your validator and secure.')} />
        </Modal.Columns>
        <Modal.Columns hint={t<string>('The key type and crypto type to use for this key. Be aware that different keys have different crypto requirements. You should be familiar with the type requirements for the different keys.')}>
          <Dropdown
            label={t<string>('key type to set')}
            onChange={setKeyType}
            options={keyTypeOptRef.current}
            value={keyType}
          />
          <Dropdown
            isDisabled={_cryptoOptions.length === 1}
            label={t<string>('crypto type to use')}
            onChange={setCrypto}
            options={_cryptoOptions}
            value={crypto}
          />
        </Modal.Columns>
        <Modal.Columns hint={t<string>('This pubic key is what will be visible in your queued keys list. It is generated based on the seed and the crypto used.')}>
          <Input
            isDisabled
            label={t<string>('generated public key')}
            value={publicKey}
          />
        </Modal.Columns>
      </Modal.Content>
      <Modal.Actions onCancel={onClose}>
        <Button
          icon='sign-in-alt'
          label={t<string>('Submit key')}
          onClick={_onSubmit}
        />
      </Modal.Actions>
    </Modal>
  );
}
Example #24
Source File: MultisigApprove.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function MultisigApprove ({ className = '', onClose, ongoing, threshold, who }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const { allAccounts } = useAccounts();
  const [callData, setCallData] = useState<Call | null>(null);
  const [callWeight] = useWeight(callData);
  const [hash, setHash] = useState<string | null>(ongoing[0][0].toHex());
  const [{ isMultiCall, multisig }, setMultisig] = useState<MultiInfo>({ isMultiCall: false, multisig: null });
  const [isCallOverride, setCallOverride] = useState(true);
  const [others, setOthers] = useState<AccountId[]>([]);
  const [signatory, setSignatory] = useState<string | null>(null);
  const [whoFilter, setWhoFilter] = useState<string[]>([]);
  const [type, setType] = useState<string | null>('aye');
  const [tx, setTx] = useState<SubmittableExtrinsic<'promise'> | null>(null);
  const calltypes = useMemo<Option[]>(
    () => [
      { text: t<string>('Approve this call hash'), value: 'aye' },
      { text: t<string>('Cancel this call hash'), value: 'nay' }
    ],
    [t]
  );
  const hashes = useMemo<Option[]>(
    () => ongoing.map(([h]) => ({ text: h.toHex(), value: h.toHex() })),
    [ongoing]
  );

  // filter the current multisig by supplied hash
  useEffect((): void => {
    const [, multisig] = ongoing.find(([h]) => h.eq(hash)) || [null, null];

    setMultisig({
      isMultiCall: !!multisig && (multisig.approvals.length + 1) >= threshold,
      multisig
    });
    setCallData(null);
  }, [hash, ongoing, threshold]);

  // the others are all the who elements, without the current signatory (re-encoded)
  useEffect((): void => {
    setOthers(
      who
        .map((w) => api.createType('AccountId', w))
        .filter((w) => !w.eq(signatory))
    );
  }, [api, signatory, who]);

  // Filter the who by those not approved yet that is an actual account we own. In the case of
  // rejections, we defer to the the first approver, since he is the only one to send the cancel
  // On reaching threshold, we include all possible signatories in the list
  useEffect((): void => {
    const hasThreshold = multisig && (multisig.approvals.length >= threshold);

    setWhoFilter(
      who
        .map((w) => api.createType('AccountId', w).toString())
        .filter((w) => allAccounts.some((a) => a === w) && multisig && (
          type === 'nay'
            ? multisig.approvals[0].eq(w)
            : hasThreshold || !multisig.approvals.some((a) => a.eq(w))
        ))
    );
  }, [api, allAccounts, multisig, threshold, type, who]);

  // based on the type, multisig, others create the tx. This can be either an approval or final call
  useEffect((): void => {
    const multiMod = api.tx.multisig || api.tx.utility;

    setTx(() =>
      hash && multisig
        ? type === 'aye'
          ? isMultiCall && isCallOverride
            ? callData
              ? multiMod.asMulti.meta.args.length === 6
                ? multiMod.asMulti(threshold, others, multisig.when, callData.toHex(), false, callWeight)
                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                // @ts-ignore (We are doing toHex here since we have a Vec<u8> input)
                : multiMod.asMulti(threshold, others, multisig.when, callData)
              : null
            : multiMod.approveAsMulti.meta.args.length === 5
              ? multiMod.approveAsMulti(threshold, others, multisig.when, hash, callWeight)
              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
              // @ts-ignore
              : multiMod.approveAsMulti(threshold, others, multisig.when, hash)
          : multiMod.cancelAsMulti(threshold, others, multisig.when, hash)
        : null
    );
  }, [api, callData, callWeight, hash, isCallOverride, isMultiCall, others, multisig, threshold, type]);

  // when the actual call input changes, create a call and set it
  const _setCallData = useCallback(
    (callHex: string): void => {
      try {
        assert(isHex(callHex), 'Hex call data required');

        const callData = api.createType('Call', callHex);

        setCallData(
          callData.hash.eq(hash)
            ? callData
            : null
        );
      } catch (error) {
        setCallData(null);
      }
    },
    [api, hash]
  );

  const isAye = type === 'aye';

  return (
    <Modal
      className={className}
      header={t<string>('Pending call hashes')}
      size='large'
    >
      <Modal.Content>
        <Modal.Columns hint={t('The call hash from the list of available and unapproved calls.')}>
          <Dropdown
            help={t<string>('The call hashes that have not been executed as of yet.')}
            label={t<string>('pending hashes')}
            onChange={setHash}
            options={hashes}
            value={hash}
          />
        </Modal.Columns>
        <Modal.Columns hint={t('The operation type to apply. For approvals both non-final and final approvals are supported.')}>
          <Dropdown
            help={t<string>('Either approve or reject this call.')}
            label={t<string>('approval type')}
            onChange={setType}
            options={calltypes}
            value={type}
          />
        </Modal.Columns>
        {whoFilter.length !== 0 && (
          <>
            <Modal.Columns hint={t('For approvals outstanding approvers will be shown, for hashes that should be cancelled the first approver is required.')}>
              <InputAddress
                filter={whoFilter}
                help={t<string>('The signatory to send the approval/cancel from')}
                label={t<string>('signatory')}
                onChange={setSignatory}
              />
            </Modal.Columns>
            {type === 'aye' && isMultiCall && (
              <>
                {isCallOverride && (
                  <Modal.Columns hint={t('The call data for this transaction matching the hash. Once sent, the multisig will be executed against this.')}>
                    <Input
                      autoFocus
                      help={t('For final approvals, the actual full call data is required to execute the transaction')}
                      isError={!callData}
                      label={t('call data for final approval')}
                      onChange={_setCallData}
                    />
                  </Modal.Columns>
                )}
                <Modal.Columns hint={t('Swap to a non-executing approval type, with subsequent calls providing the actual call data.')}>
                  <Toggle
                    className='tipToggle'
                    label={
                      isMultiCall
                        ? t<string>('Multisig message with call (for final approval)')
                        : t<string>('Multisig approval with hash (non-final approval)')
                    }
                    onChange={setCallOverride}
                    value={isCallOverride}
                  />
                </Modal.Columns>
              </>
            )}
          </>
        )}
      </Modal.Content>
      <Modal.Actions onCancel={onClose}>
        <TxButton
          accountId={signatory}
          extrinsic={tx}
          icon={isAye ? 'check' : 'times'}
          isDisabled={!tx || (isAye && !whoFilter.length)}
          label={isAye ? 'Approve' : 'Reject'}
          onStart={onClose}
        />
      </Modal.Actions>
    </Modal>
  );
}