@polkadot/util#u8aToString TypeScript Examples

The following examples show how to use @polkadot/util#u8aToString. 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: InputFile.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function convertResult (result: ArrayBuffer): Uint8Array {
  const data = new Uint8Array(result);

  // this converts the input (if detected as hex), via the hex conversion route
  if (data[0] === BYTE_STR_0 && data[1] === BYTE_STR_X) {
    let hex = u8aToString(data);

    while (hex[hex.length - 1] === STR_NL) {
      hex = hex.substr(0, hex.length - 1);
    }

    if (isHex(hex)) {
      return hexToU8a(hex);
    }
  }

  return data;
}
Example #2
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 #3
Source File: InputFile.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
function convertResult(result: ArrayBuffer): Uint8Array {
  const data = new Uint8Array(result);

  // this converts the input (if detected as hex), via the hex conversion route
  if (data[0] === BYTE_STR_0 && data[1] === BYTE_STR_X) {
    let hex = u8aToString(data);

    while (hex[hex.length - 1] === STR_NL) {
      hex = hex.substr(0, hex.length - 1);
    }

    if (isHex(hex)) {
      return hexToU8a(hex);
    }
  }

  return data;
}
Example #4
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 #5
Source File: InputFile.tsx    From contracts-ui with GNU General Public License v3.0 6 votes vote down vote up
function convertResult(result: ArrayBuffer): Uint8Array {
  const data = new Uint8Array(result);

  // this converts the input (if detected as hex), via the hex conversion route
  if (data[0] === BYTE_STR_0 && data[1] === BYTE_STR_X) {
    let hex = u8aToString(data);

    while (hex[hex.length - 1] === STR_NL) {
      hex = hex.substr(0, hex.length - 1);
    }

    if (isHex(hex)) {
      return hexToU8a(hex);
    }
  }

  return data;
}
Example #6
Source File: identity.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
// keeps track of changing registration info
  public async update() {
    const rOpt = await this._Chain.api.query.identity.identityOf(this.account.address);
    if (rOpt.isSome) {
      const { judgements, deposit, info } = rOpt.unwrap();
      this._judgements = judgements;
      this._info = info;
      this._deposit = this._Chain.coins(deposit);

      // update username
      const d2s = (d: Data) => u8aToString(d.toU8a()).replace(/[^\x20-\x7E]/g, '');
      this.username = d2s(this._info.display);
      const quality = getIdentityQuality(this._judgements.map((j) => j[1].toString()));
      if (!this._quality || quality !== IdentityQuality.Unknown) {
        this._quality = quality;
      }
      if (this._Identities.store.getById(this.id)) {
        this._Identities.store.update(this);
      } else {
        this._Identities.store.add(this);
      }

      this._exists = true;
    } else {
      this._exists = false;
      this._judgements = [];
      this._quality = IdentityQuality.Unknown;
      this._deposit = this._Chain.coins(0);
    }
  }
Example #7
Source File: blocks.errors.ts    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
constructor(message: string, hash?: string | Uint8Array) {
    super();
    const splittedMessage = message.split(':');
    if (isU8a(hash)) {
      hash = u8aToString(hash);
    }
    const errorCode = splittedMessage.length > 0 ? parseInt(splittedMessage[0]) : NaN;
    switch (errorCode) {
      case -32603:
        this.message = `State already discarded for block ${hash}`;
        break;
      default:
        this.message = 'Unknow error occurred';
        break;
    }
  }
Example #8
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 #9
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 #10
Source File: Query.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function keyToName (isConst: boolean, _key: Uint8Array | QueryableStorageEntry<'promise'> | ConstValue): string {
  if (isConst) {
    const key = _key as ConstValue;

    return `const ${key.section}.${key.method}`;
  }

  const key = _key as Uint8Array | QueryableStorageEntry<'promise'>;

  if (isU8a(key)) {
    const [, u8a] = compactStripLength(key);

    // If the string starts with `:`, handle it as a pure string
    return u8a[0] === 0x3a
      ? u8aToString(u8a)
      : u8aToHex(u8a);
  }

  return `${key.creator.section}.${key.creator.method}`;
}
Example #11
Source File: Import.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function parseFile (file: Uint8Array, genesisHash?: string | null): KeyringPair | null {
  try {
    return keyring.createFromJson(JSON.parse(u8aToString(file)) as KeyringPair$Json, { genesisHash });
  } catch (error) {
    console.error(error);
  }

  return null;
}
Example #12
Source File: IdentitySub.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
function extractInfo ([[ids], opts]: [[string[]], Option<ITuple<[AccountId, Data]>>[]]): [string, string][] {
  return ids.reduce((result: [string, string][], id, index): [string, string][] => {
    const opt = opts[index];

    if (opt.isSome) {
      const [, data] = opt.unwrap();

      if (data.isRaw) {
        result.push([id, u8aToString(data.asRaw)]);
      }
    }

    return result;
  }, []);
}
Example #13
Source File: useAbi.ts    From crust-apps with Apache License 2.0 5 votes vote down vote up
export default function useAbi (initialValue: [string | null | undefined, Abi | null | undefined] = [null, null], codeHash: string | null = null, isRequired = false): UseAbi {
  const [state, setAbi] = useState<AbiState>(() => fromInitial(initialValue, isRequired));

  useEffect(
    () => setAbi((state) =>
      initialValue[0] && state.abi !== initialValue[0]
        ? fromInitial(initialValue, isRequired)
        : state
    ),
    [initialValue, isRequired]
  );

  const onChangeAbi = useCallback(
    (u8a: Uint8Array, name: string): void => {
      const json = u8aToString(u8a);

      try {
        setAbi({
          abi: json,
          abiName: name.replace('.contract', '').replace('.json', '').replace('_', ' '),
          contractAbi: new Abi(json, api.registry.getChainProperties()),
          errorText: null,
          isAbiError: false,
          isAbiSupplied: true,
          isAbiValid: true
        });

        codeHash && store.saveCode(codeHash, { abi: json });
      } catch (error) {
        console.error(error);

        setAbi({ ...EMPTY, errorText: (error as Error).message });
      }
    },
    [codeHash]
  );

  const onRemoveAbi = useCallback(
    (): void => {
      setAbi(EMPTY);

      codeHash && store.saveCode(codeHash, { abi: null });
    },
    [codeHash]
  );

  return {
    ...state,
    onChangeAbi,
    onRemoveAbi
  };
}
Example #14
Source File: test-precompile-local-assets-erc20.ts    From moonbeam with GNU General Public License v3.0 5 votes vote down vote up
describeDevMoonbeamAllEthTxTypes(
  "Precompiles - Assets-ERC20 Wasm",
  (context) => {
    let sudoAccount, baltatharAccount, assetId, iFace, assetAddress;
    before("Setup contract and mock balance", async () => {
      const keyring = new Keyring({ type: "ethereum" });
      sudoAccount = await keyring.addFromUri(ALITH_PRIV_KEY, null, "ethereum");
      baltatharAccount = await keyring.addFromUri(BALTATHAR_PRIV_KEY, null, "ethereum");

      // registerAsset
      const { events: eventsRegister } = await createBlockWithExtrinsic(
        context,
        sudoAccount,
        context.polkadotApi.tx.sudo.sudo(
          context.polkadotApi.tx.assetManager.registerLocalAsset(
            baltatharAccount.address,
            baltatharAccount.address,
            true,
            new BN(1)
          )
        )
      );

      // Look for assetId in events
      eventsRegister.forEach((e) => {
        if (e.section.toString() === "assetManager") {
          assetId = e.data[0].toHex();
        }
      });
      assetId = assetId.replace(/,/g, "");

      assetAddress = u8aToHex(new Uint8Array([...hexToU8a("0xFFFFFFFE"), ...hexToU8a(assetId)]));

      const contractData = await getCompiled("LocalAssetExtendedErc20Instance");
      iFace = new ethers.utils.Interface(contractData.contract.abi);
      const { contract, rawTx } = await createContract(context, "LocalAssetExtendedErc20Instance");
      const address = contract.options.address;
      await context.createBlock({ transactions: [rawTx] });
    });
    it("allows to set metadata", async function () {
      let data = iFace.encodeFunctionData(
        // action
        "set_metadata",
        ["Local", "LOC", 12]
      );

      const tx = await createTransaction(context, {
        from: BALTATHAR,
        privateKey: BALTATHAR_PRIV_KEY,
        value: "0x0",
        gas: "0x200000",
        gasPrice: GAS_PRICE,
        to: assetAddress,
        data: data,
      });

      const block = await context.createBlock({
        transactions: [tx],
      });

      const receipt = await context.web3.eth.getTransactionReceipt(block.txResults[0].result);

      expect(receipt.status).to.equal(true);

      const metadata = (await context.polkadotApi.query.localAssets.metadata(assetId)) as any;

      expect(u8aToString(metadata.name)).to.eq("Local");
      expect(u8aToString(metadata.symbol)).to.eq("LOC");
      expect(metadata.decimals.toString()).to.eq("12");
    });
  },
  true
);
Example #15
Source File: BaseBytes.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function BaseBytes ({ asHex, children, className = '', defaultValue: { value }, isDisabled, isError, label, length = -1, onChange, onEnter, onEscape, size = 'full', validate = defaultValidate, withCopy, withLabel, withLength }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const [defaultValue] = useState(
    value
      ? isDisabled && isU8a(value) && isAscii(value)
        ? u8aToString(value)
        : isHex(value)
          ? value
          : u8aToHex(value as Uint8Array, isDisabled ? 256 : -1)
      : undefined
  );
  const [isValid, setIsValid] = useState(false);

  const _onChange = useCallback(
    (hex: string): void => {
      let [isValid, value] = convertInput(hex);

      isValid = isValid && validate(value) && (
        length !== -1
          ? value.length === length
          : value.length !== 0
      );

      if (withLength && isValid) {
        value = compactAddLength(value);
      }

      onChange && onChange({
        isValid,
        value: asHex
          ? u8aToHex(value)
          : value
      });

      setIsValid(isValid);
    },
    [asHex, length, onChange, validate, withLength]
  );

  return (
    <Bare className={className}>
      <Input
        className={size}
        defaultValue={defaultValue as string}
        isAction={!!children}
        isDisabled={isDisabled}
        isError={isError || !isValid}
        label={label}
        onChange={_onChange}
        onEnter={onEnter}
        onEscape={onEscape}
        placeholder={t<string>('0x prefixed hex, e.g. 0x1234 or ascii data')}
        type='text'
        withEllipsis
        withLabel={withLabel}
      >
        {children}
        {withCopy && (
          <CopyButton value={defaultValue} />
        )}
      </Input>
    </Bare>
  );
}
Example #16
Source File: useMetadata.ts    From contracts-ui with GNU General Public License v3.0 5 votes vote down vote up
export function useMetadata(
  initialValue?: Record<string, unknown>,
  options: Options & Callbacks = {}
): UseMetadata {
  const { api } = useApi();

  const { isWasmRequired = false, ...callbacks } = options;
  const [state, setState] = useState<MetadataState>(() =>
    deriveFromJson({ isWasmRequired }, initialValue, api)
  );

  function onChange(file: FileState): void {
    try {
      const json = JSON.parse(u8aToString(file.data)) as Record<string, unknown>;
      const name = file.name.replace('.contract', '').replace('.json', '').replace('_', ' ');

      const newState = deriveFromJson({ isWasmRequired, name }, json, api);

      setState(newState);

      callbacks.onChange && callbacks.onChange(file, json);
    } catch (error) {
      console.error(error);

      setState({
        ...EMPTY,
        message: 'This contract file is not in a valid format.',
        isError: true,
        isSupplied: true,
        isValid: false,
      });
    }
  }

  function onRemove(): void {
    setState(EMPTY);

    callbacks.onChange && callbacks.onChange(undefined);
    callbacks.onRemove && callbacks.onRemove();
  }

  useEffect((): void => {
    setState(deriveFromJson({ isWasmRequired }, initialValue, api));
  }, [api, initialValue, isWasmRequired]);

  return {
    ...state,
    onChange,
    onRemove,
  };
}
Example #17
Source File: IdentityMain.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function setData (data: Data, setActive: null | ((isActive: boolean) => void), setVal: (val: string) => void): void {
  if (data.isRaw) {
    setActive && setActive(true);
    setVal(u8aToString(data.asRaw.toU8a(true)));
  }
}
Example #18
Source File: BaseBytes.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
function BaseBytes({
  asHex,
  children,
  className = '',
  defaultValue: { value },
  isDisabled,
  isError,
  label,
  length = -1,
  onChange,
  onEnter,
  onEscape,
  size = 'full',
  validate = defaultValidate,
  withCopy,
  withLabel,
  withLength,
}: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const [defaultValue] = useState(
    value
      ? isDisabled && isU8a(value) && isAscii(value)
        ? u8aToString(value)
        : isHex(value)
        ? value
        : // eslint-disable-next-line no-magic-numbers
          u8aToHex(value as Uint8Array, isDisabled ? 256 : -1)
      : undefined
  );
  const [isValid, setIsValid] = useState(false);

  const _onChange = useCallback(
    (hex: string): void => {
      let [beValid, val] = convertInput(hex);

      beValid = beValid && validate(val) && (length !== -1 ? val.length === length : val.length !== 0);

      if (withLength && beValid) {
        val = compactAddLength(val);
      }

      // eslint-disable-next-line
      onChange &&
        onChange({
          isValid: beValid,
          value: asHex ? u8aToHex(val) : val,
        });

      setIsValid(beValid);
    },
    [asHex, length, onChange, validate, withLength]
  );

  return (
    <Bare className={className}>
      <Input
        className={size}
        defaultValue={defaultValue as string}
        isAction={!!children}
        isDisabled={isDisabled}
        isError={isError || !isValid}
        label={label}
        onChange={_onChange}
        onEnter={onEnter}
        onEscape={onEscape}
        placeholder={t<string>('0x prefixed hex, e.g. 0x1234 or ascii data')}
        type="text"
        withEllipsis
        withLabel={withLabel}
      >
        {children}
        {withCopy && <CopyButton value={defaultValue} />}
      </Input>
    </Bare>
  );
}
Example #19
Source File: test-precompile-local-assets-erc20.ts    From moonbeam with GNU General Public License v3.0 5 votes vote down vote up
describeDevMoonbeamAllEthTxTypes(
  "Precompiles - Assets-ERC20 Wasm",
  (context) => {
    let sudoAccount, baltatharAccount, assetId, iFace, assetAddress;
    before("Setup contract and mock balance", async () => {
      const keyring = new Keyring({ type: "ethereum" });
      sudoAccount = await keyring.addFromUri(ALITH_PRIV_KEY, null, "ethereum");
      baltatharAccount = await keyring.addFromUri(BALTATHAR_PRIV_KEY, null, "ethereum");

      // registerAsset
      const { events: eventsRegister } = await createBlockWithExtrinsic(
        context,
        sudoAccount,
        context.polkadotApi.tx.sudo.sudo(
          context.polkadotApi.tx.assetManager.registerLocalAsset(
            baltatharAccount.address,
            baltatharAccount.address,
            true,
            new BN(1)
          )
        )
      );

      // Look for assetId in events
      eventsRegister.forEach((e) => {
        if (e.section.toString() === "assetManager") {
          assetId = e.data[0].toHex();
        }
      });
      assetId = assetId.replace(/,/g, "");

      assetAddress = u8aToHex(new Uint8Array([...hexToU8a("0xFFFFFFFE"), ...hexToU8a(assetId)]));

      // Set metadata
      await createBlockWithExtrinsic(
        context,
        baltatharAccount,
        context.polkadotApi.tx.localAssets.setMetadata(assetId, "Local", "Local", new BN(12))
      );

      const contractData = await getCompiled("LocalAssetExtendedErc20Instance");
      iFace = new ethers.utils.Interface(contractData.contract.abi);
      const { contract, rawTx } = await createContract(context, "LocalAssetExtendedErc20Instance");
      const address = contract.options.address;
      await context.createBlock({ transactions: [rawTx] });
    });
    it("allows to clear metadata", async function () {
      let data = iFace.encodeFunctionData(
        // action
        "clear_metadata",
        []
      );

      const tx = await createTransaction(context, {
        from: BALTATHAR,
        privateKey: BALTATHAR_PRIV_KEY,
        value: "0x0",
        gas: "0x200000",
        gasPrice: GAS_PRICE,
        to: assetAddress,
        data: data,
      });

      const block = await context.createBlock({
        transactions: [tx],
      });

      const receipt = await context.web3.eth.getTransactionReceipt(block.txResults[0].result);

      expect(receipt.status).to.equal(true);

      const metadata = (await context.polkadotApi.query.localAssets.metadata(assetId)) as any;

      expect(u8aToString(metadata.name)).to.eq("");
      expect(u8aToString(metadata.symbol)).to.eq("");
      expect(metadata.decimals.toString()).to.eq("0");
    });
  },
  true
);
Example #20
Source File: Developer.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Developer ({ className = '', onStatusChange }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const [code, setCode] = useState(EMPTY_CODE);
  const [isJsonValid, setIsJsonValid] = useState(true);
  const [isTypesValid, setIsTypesValid] = useState(true);
  const [types, setTypes] = useState<Record<string, any>>(EMPTY_TYPES);
  const [typesPlaceholder, setTypesPlaceholder] = useState<string | null>(null);
  const [sharedUrl, setSharedUrl] = useState<string | null>(null);

  useEffect((): void => {
    const types = decodeUrlTypes() || store.get('types') as Record<string, unknown> || {};

    if (Object.keys(types).length) {
      setCode(JSON.stringify(types, null, 2));
      setTypes({});
      setTypesPlaceholder(Object.keys(types).join(', '));
      setSharedUrl(encodeUrlTypes(types));
    }
  }, []);

  const _setState = useCallback(
    ({ code, isJsonValid, isTypesValid, types, typesPlaceholder }: AllState): void => {
      setCode(code);
      setIsJsonValid(isJsonValid);
      setIsTypesValid(isTypesValid);
      setTypes(types);
      setTypesPlaceholder(typesPlaceholder);
    },
    []
  );

  const _clearTypes = useCallback(
    (): void => {
      store.remove('types');

      _setState({
        code: EMPTY_CODE,
        isJsonValid: true,
        isTypesValid: true,
        types: EMPTY_TYPES,
        typesPlaceholder: null
      });
    },
    [_setState]
  );

  const _onChangeTypes = useCallback(
    (data: Uint8Array): void => {
      const code = u8aToString(data);

      try {
        const types = JSON.parse(code) as Record<string, unknown>;
        const typesPlaceholder = Object.keys(types).join(', ');

        console.log('Detected types:', typesPlaceholder);

        _setState({
          code,
          isJsonValid: true,
          isTypesValid: true,
          types: Object.keys(types).length === 0 ? {} : types,
          typesPlaceholder
        });
      } catch (error) {
        console.error('Error registering types:', error);

        _setState({
          code,
          isJsonValid: false,
          isTypesValid: false,
          types: {},
          typesPlaceholder: (error as Error).message
        });
      }
    },
    [_setState]
  );

  const _onEditTypes = useCallback(
    (code: string): void => {
      try {
        if (!isJsonObject(code)) {
          throw Error(t('This is not a valid JSON object.'));
        }

        _onChangeTypes(stringToU8a(code));
      } catch (error) {
        setCode(code);
        setIsJsonValid(false);
        setTypesPlaceholder((error as Error).message);
      }
    },
    [_onChangeTypes, t]
  );

  const _saveDeveloper = useCallback(
    (): void => {
      let url = null;

      try {
        api.registerTypes(types);
        store.set('types', types);
        setIsTypesValid(true);
        onStatusChange({
          action: t<string>('Your custom types have been added'),
          status: 'success'
        });

        if (Object.keys(types).length) {
          url = encodeUrlTypes(types);

          console.log(url);
        }
      } catch (error) {
        console.error(error);
        setIsTypesValid(false);
        onStatusChange({
          action: t(`Error saving your custom types. ${(error as Error).message}`),
          status: 'error'
        });
      }

      setSharedUrl(url);
    },
    [api, onStatusChange, t, types]
  );

  const typesHasNoEntries = Object.keys(types).length === 0;

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

  return (
    <div className={className}>
      <div className='ui--row'>
        <div className='full'>
          <InputFile
            clearContent={typesHasNoEntries && isTypesValid}
            help={t<string>('Save the type definitions for your custom structures as key-value pairs in a valid JSON file. The key should be the name of your custom structure and the value an object containing your type definitions.')}
            isError={!isTypesValid}
            label={t<string>('Additional types as a JSON file (or edit below)')}
            onChange={_onChangeTypes}
            placeholder={typesPlaceholder}
          />
        </div>
      </div>
      <div className='ui--row'>
        <div className='full'>
          <Editor
            className='editor'
            code={code}
            isValid={isJsonValid}
            onEdit={_onEditTypes}
          />
        </div>
      </div>
      <div className='ui--row'>
        <div className='full'>
          <Trans i18nKey='devConfig'><div className='help'>If you are a development team with at least a test network available, consider adding the types directly <a href='https://github.com/polkadot-js/apps/tree/master/packages/apps-config' rel='noopener noreferrer' target='_blank'>to the apps-config</a>, allowing out of the box operation for your spec &amp; chains, both for you and anybody trying to connect to it. This is not a replacement for your chain-specific UI, however doing so does help in allowing users to easily discover and use with zero-config.</div></Trans>
        </div>
      </div>
      <Button.Group>
        <CopyButton
          label={t<string>('Share')}
          type={t<string>('url')}
          value={sharedUrl}
        />
        <Button
          icon='sync'
          label={t<string>('Reset')}
          onClick={_clearTypes}
        />
        <Button
          icon='save'
          isDisabled={!isTypesValid || !isJsonValid}
          label={t<string>('Save')}
          onClick={_saveDeveloper}
        />
      </Button.Group>
    </div>
  );
}
Example #21
Source File: index.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function MaxwellClaims (): React.ReactElement<Props> {
  const [didCopy, setDidCopy] = useState(false);
  const [ethereumAddress, setEthereumAddress] = useState<string | undefined | null>(null);
  const [signature, setSignature] = useState<EcdsaSignature | null>(null);
  const [step, setStep] = useState<Step>(Step.Account);
  const [accountId, setAccountId] = useState<string | null>(null);
  const { api, systemChain } = useApi();
  const { t } = useTranslation();
  const [statusOpen, setStatusOpen] = useState<boolean>(false);
  const [result, setResult] = useState<string>('');
  const [status, setStatus] = useState<string>('');
  const [ethereumTxHashValid, setEthereumTxHashValid] = useState<boolean>(false);
  const [isBusy, setIsBusy] = useState<boolean>(false);
  const [isValid, setIsValid] = useState(false);
  const [ethereumTxHash, setEthereumTxHash] = useState<string | undefined | null>(null);

  // This preclaimEthereumAddress holds the result of `api.query.claims.preclaims`:
  // - an `EthereumAddress` when there's a preclaim
  // - null if no preclaim
  // - `PRECLAIMS_LOADING` if we're fetching the results
  const [preclaimEthereumAddress, setPreclaimEthereumAddress] = useState<string | null | undefined | typeof PRECLAIMS_LOADING>(PRECLAIMS_LOADING);
  const isPreclaimed = !!preclaimEthereumAddress && preclaimEthereumAddress !== PRECLAIMS_LOADING;
  const claimLimit = useCall<BalanceOf>(api.query.claims.claimLimit);

  // Everytime we change account, reset everything, and check if the accountId
  // has a preclaim.
  useEffect(() => {
    if (!accountId) {
      return;
    }

    setStep(Step.Account);
    setEthereumAddress(null);
    setEthereumTxHash(null);
    setPreclaimEthereumAddress(PRECLAIMS_LOADING);

    if (!api.query.claims || !api.query.claims.preclaims) {
      return setPreclaimEthereumAddress(null);
    }

    api.query.claims
      .preclaims<Option<EthereumAddress>>(accountId)
      .then((preclaim): void => {
        const address = preclaim.unwrapOr(null)?.toString();

        setEthereumAddress(address);
        setPreclaimEthereumAddress(address);
      })
      .catch((): void => setPreclaimEthereumAddress(null));
  }, [accountId, api.query.claims, api.query.claims.preclaims]);

  // Old claim process used `api.tx.claims.claim`, and didn't have attest
  const isOldClaimProcess = !api.tx.claims.claimAttest;

  useEffect(() => {
    if (didCopy) {
      setTimeout((): void => {
        setDidCopy(false);
      }, 1000);
    }
  }, [didCopy]);

  const goToStepAccount = useCallback(() => {
    setStep(Step.Account);
    setEthereumTxHash("");
    setEthereumTxHashValid(false);
  }, []);

  const goToStepSign = useCallback(() => {
    setStep(Step.Sign);
  }, []);

  const goToStepClaim = useCallback(() => {
    setStep(Step.Claim);
  }, []);

  const handleAccountStep = useCallback(async () => {
    setIsBusy(true);
    const result = await httpPost("https://bridge-api.crust.network/claim/" + ethereumTxHash);

    setIsBusy(false);
    setResult(result.statusText);
    setStatus(result.status);

    if (result.code == 200) {
      setStatusOpen(true);
      setEthereumTxHashValid(true);
      goToStepSign();
    } else {
      api.query.claims
        .claims<Option<BalanceOf>>(ethereumTxHash?.toString())
        .then((claim): void => {
          const claimOpt = JSON.parse(JSON.stringify(claim));

          if (claimOpt) {
            api.query.claims
              .claimed<Option<BalanceOf>>(ethereumTxHash?.toString())
              .then((claimed): void => {
                const isClaimed = JSON.parse(JSON.stringify(claimed));

                if (isClaimed) {
                  setStatusOpen(true);
                } else {
                  setStatusOpen(true);
                  setResult('MintClaimSuccess');
                  setStatus('success');
                  setEthereumTxHashValid(true);
                  goToStepSign();
                }
              });
          } else {
            setStatusOpen(true);
          }
        })
        .catch((): void => setIsBusy(false));
    }
  }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess, ethereumTxHash]);

  const onChangeEthereumTxHash = useCallback((hex: string) => {
    let [isValid, value] = convertInput(hex);

    isValid = isValid && (
      length !== -1
        ? value.length === 32
        : value.length !== 0
    );
    setIsValid(isValid);
    setEthereumTxHash(hex.trim());
  }, [ethereumTxHash]);

  function convertInput (value: string): [boolean, Uint8Array] {
    if (value === '0x') {
      return [true, new Uint8Array([])];
    } else if (value.startsWith('0x')) {
      try {
        return [true, hexToU8a(value)];
      } catch (error) {
        return [false, new Uint8Array([])];
      }
    }

    // maybe it is an ss58?
    try {
      return [true, decodeAddress(value)];
    } catch (error) {
      // we continue
    }

    return isAscii(value)
      ? [true, stringToU8a(value)]
      : [value === '0x', new Uint8Array([])];
  }

  // Depending on the account, decide which step to show.
  // const handleAccountStep = useCallback(() => {
  //   if (isPreclaimed) {
  //     goToStepClaim();
  //   } else if (ethereumAddress || isOldClaimProcess) {
  //     goToStepSign();
  //   } else {
  //     setStep(Step.ETHAddress);
  //   }
  // }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess]);

  const onChangeSignature = useCallback((event: React.SyntheticEvent<Element>) => {
    const { value: signatureJson } = event.target as HTMLInputElement;

    const { ethereumAddress, signature } = recoverFromJSON(signatureJson);

    setEthereumAddress(ethereumAddress?.toString());
    setSignature(signature);
  }, []);

  const onChangeEthereumAddress = useCallback((value: string) => {
    // FIXME We surely need a better check than just a trim

    setEthereumAddress(value.trim());
  }, []);

  const onCopy = useCallback(() => {
    setDidCopy(true);
  }, []);

  // If it's 1/ not preclaimed and 2/ not the old claiming process, fetch the
  // statement kind to sign.
  const statementKind = useCall<StatementKind | null>(!isPreclaimed && !isOldClaimProcess && !!ethereumAddress && api.query.claims.signing, [ethereumAddress], transformStatement);

  const statementSentence = getStatement(systemChain, statementKind)?.sentence || '';

  const prefix = u8aToString(api.consts.claims.prefix.toU8a(true));
  const payload = accountId
    ? `${prefix}${u8aToHex(decodeAddress(accountId), -1, false)}${statementSentence}${ethereumTxHash?.substring(2)}`
    : '';

  return (
    <main>
      {!isOldClaimProcess && <Warning />}
      <h1>
        <Trans>Claim your <em>{TokenUnit.abbr}</em> tokens</Trans>
      </h1>
      <Columar>
        <Columar.Column>
          <Card withBottomMargin>
            <h3><span style={{"wordWrap": "break-word", "wordBreak": "break-all"}}>{t<string>(`0. Please make sure you have the authority to make signature with the private key of the wallet account `)}<span style={{ 'fontWeight': 'bold' }}>({t<string>('address: ')}<a href='https://etherscan.io/address/0x17a9037cdfb24ffcc13697d03c3bcd4dff34732b' target="_blank">0x17A9037cdFB24FfcC13697d03C3bcd4DFF34732b</a>)</span><span>{t<string>(', using an exchange account to sent a transfer (withdrawal) transaction will be invalidated and cause asset loss.')}</span> <span style={{ 'fontWeight': 'bold', 'color': 'red' }}>{t<string>(` This is Maxwell's claim. If you want to claim your tokens to the mainnet, please click on the upper left corner to switch to the mainnet, You are responsible for the consequences!`)}</span></span></h3>
            <img style={{'marginLeft': 'auto', 'marginRight': 'auto', 'display': 'block', "width": "150px" }} src={claimPng as string} />
          </Card>
          {(<Card withBottomMargin>
            <h3>{t<string>(`1. Select your {{chain}} account and enter`, {
                replace: {
                  chain: systemChain
                }
              })} <a href='https://etherscan.io/token/0x32a7C02e79c4ea1008dD6564b35F131428673c41'>{t('ERC20 CRU')}</a> {t<string>('transfer tx hash')}, <span>{t<string>(`The remaining claim limit is `)}<span style={{'color': '#ff8812', 'textDecoration': 'underline', 'fontStyle': 'italic'}}>{formatBalance(claimLimit, { withUnit: 'CRU' })}</span><span>{t<string>(`, If your claim amount is greater than the claim limit, please wait for the limit update`)}</span></span> </h3>
            <InputAddress
              defaultValue={accountId}
              help={t<string>('The account you want to claim to.')}
              isDisabled={ethereumTxHashValid}
              label={t<string>('claim to account')}
              onChange={setAccountId}
              type='account'
            />
            <Input
              autoFocus
              className='full'
              help={t<string>('The Ethereum CRU transfer tx hash (starting by "0x")')}
              isDisabled={ethereumTxHashValid}
              isError={!isValid}
              label={t<string>('Ethereum tx hash')}
              onChange={onChangeEthereumTxHash}
              placeholder={t<string>('0x prefixed hex, e.g. 0x1234 or ascii data')}
              value={ethereumTxHash || ''}
            />    
            {(step === Step.Account) && (<Button.Group>
              <Button
                icon='sign-in-alt'
                isBusy={isBusy}
                isDisabled={preclaimEthereumAddress === PRECLAIMS_LOADING || ethereumTxHash === null || ethereumTxHash === '' || !isValid}
                label={preclaimEthereumAddress === PRECLAIMS_LOADING
                  ? t<string>('Loading')
                  : t<string>('Continue')
                }
                onClick={handleAccountStep}
              />
            </Button.Group>)}   
            <HttpStatus
              isStatusOpen={statusOpen}
              message={result}
              setStatusOpen={setStatusOpen}
              status={status}
            />
          </Card>)}
          {
            // We need to know the ethereuem address only for the new process
            // to be able to know the statement kind so that the users can sign it
            (step >= Step.ETHAddress && !isPreclaimed && !isOldClaimProcess) && (
              <Card withBottomMargin>
                <h3>{t<string>('2. Enter the ETH address from the sale.')}</h3>
                <Input
                  autoFocus
                  className='full'
                  help={t<string>('The the Ethereum address you used during the pre-sale (starting by "0x")')}
                  label={t<string>('Pre-sale ethereum address')}
                  onChange={onChangeEthereumAddress}
                  value={ethereumAddress || ''}
                />
                {(step === Step.ETHAddress) && (
                  <Button.Group>
                    <Button
                      icon='sign-in-alt'
                      isDisabled={!ethereumAddress}
                      label={t<string>('Continue')}
                      onClick={goToStepSign}
                    />
                  </Button.Group>
                )}
              </Card>
            )}
          {(step >= Step.Sign && !isPreclaimed) && (
            <Card>
              <h3>{t<string>('{{step}}. Sign with your ETH address', { replace: { step: isOldClaimProcess ? '2' : '3' } })}</h3>
              {!isOldClaimProcess && (
                <Statement
                  kind={statementKind}
                  systemChain={systemChain}
                />
              )}
              <div>{t<string>('Copy the following string and sign it with the Ethereum account you used during the pre-sale in the wallet of your choice, using the string as the payload, and then paste the transaction signature object below:')}</div>
              <CopyToClipboard
                onCopy={onCopy}
                text={payload}
              >
                <Payload
                  data-for='tx-payload'
                  data-tip
                >
                  {payload}
                </Payload>
              </CopyToClipboard>
              <Tooltip
                place='right'
                text={didCopy ? t<string>('copied') : t<string>('click to copy')}
                trigger='tx-payload'
              />
              <div>{t<string>('Paste the signed message into the field below. The placeholder text is there as a hint to what the message should look like:')}</div>
              <Signature
                onChange={onChangeSignature}
                placeholder={`{\n  "address": "0x ...",\n  "msg": "${prefix}...",\n  "sig": "0x ...",\n  "version": "3",\n  "signer": "..."\n}`}
                rows={10}
              />
              {(step === Step.Sign) && (
                <Button.Group>
                  <Button
                    icon='sign-in-alt'
                    isDisabled={!accountId || !signature}
                    label={t<string>('Confirm claim')}
                    onClick={goToStepClaim}
                  />
                </Button.Group>
              )}
            </Card>
          )}
        </Columar.Column>
        <Columar.Column>
          
          {(step >= Step.Claim) && (
            isPreclaimed
              ? <AttestDisplay
                accountId={accountId}
                ethereumAddress={ethereumAddress}
                onSuccess={goToStepAccount}
                statementKind={statementKind}
                systemChain={systemChain}
              />
              : <ClaimDisplay
                accountId={accountId}
                ethereumAddress={ethereumAddress}
                ethereumSignature={signature}
                isOldClaimProcess={isOldClaimProcess}
                onSuccess={goToStepAccount}
                statementKind={statementKind}
                ethereumTxHash={ethereumTxHash}
              />
          )}
        </Columar.Column>
      </Columar>
    </main>
  );
}
Example #22
Source File: index.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function ClaimsMainnet (): React.ReactElement<Props> {
  const [didCopy, setDidCopy] = useState(false);
  const [ethereumAddress, setEthereumAddress] = useState<string | undefined | null>(null);
  const [signature, setSignature] = useState<EcdsaSignature | null>(null);
  const [step, setStep] = useState<Step>(Step.Account);
  const [accountId, setAccountId] = useState<string | null>(null);
  const { api, systemChain } = useApi();
  const { t, i18n } = useTranslation();
  const [statusOpen, setStatusOpen] = useState<boolean>(false);
  const [result] = useState<string>('');
  const [status] = useState<string>('');
  const [ethereumTxHashValid] = useState<boolean>(false);
  const [isBusy] = useState<boolean>(false);
  const [ethereumTxHash, setEthereumTxHash] = useState<string | undefined | null>(null);

  // This preclaimEthereumAddress holds the result of `api.query.claims.preclaims`:
  // - an `EthereumAddress` when there's a preclaim
  // - null if no preclaim
  // - `PRECLAIMS_LOADING` if we're fetching the results
  const [preclaimEthereumAddress, setPreclaimEthereumAddress] = useState<string | null | undefined | typeof PRECLAIMS_LOADING>(PRECLAIMS_LOADING);
  const isPreclaimed = !!preclaimEthereumAddress && preclaimEthereumAddress !== PRECLAIMS_LOADING;

  // Everytime we change account, reset everything, and check if the accountId
  // has a preclaim.
  useEffect(() => {
    if (!accountId) {
      return;
    }

    setStep(Step.Account);
    setEthereumAddress(null);
    setEthereumTxHash(null);
    setPreclaimEthereumAddress(PRECLAIMS_LOADING);

    if (!api.query.claims || !api.query.claims.preclaims) {
      return setPreclaimEthereumAddress(null);
    }

    api.query.claims
      .preclaims<Option<EthereumAddress>>(accountId)
      .then((preclaim): void => {
        const address = preclaim.unwrapOr(null)?.toString();

        setEthereumAddress(address);
        setPreclaimEthereumAddress(address);
      })
      .catch((): void => setPreclaimEthereumAddress(null));
  }, [accountId, api.query.claims, api.query.claims.preclaims]);

  // Old claim process used `api.tx.claims.claim`, and didn't have attest
  const isOldClaimProcess = !api.tx.claims.claimAttest;

  useEffect(() => {
    if (didCopy) {
      setTimeout((): void => {
        setDidCopy(false);
      }, 1000);
    }
  }, [didCopy]);


  const goToStepSign = useCallback(() => {
    setStep(Step.Sign);
  }, []);

  const goToStepClaim = useCallback(() => {
    setStep(Step.Claim);
  }, []);

  const handleAccountStep = useCallback(async () => {
    goToStepSign();
  }, [goToStepSign, isPreclaimed, isOldClaimProcess, ethereumTxHash]);

  const onChangeSignature = useCallback((event: React.SyntheticEvent<Element>) => {
    const { value: signatureJson } = event.target as HTMLInputElement;

    const { ethereumAddress, signature } = recoverFromJSON(signatureJson);

    setEthereumAddress(ethereumAddress?.toString());
    setSignature(signature);
    goToStepSign();
  }, []);

  const onChangeEthereumAddress = useCallback((value: string) => {
    // FIXME We surely need a better check than just a trim

    setEthereumAddress(value.trim());
  }, []);

  const onCopy = useCallback(() => {
    setDidCopy(true);
  }, []);

  // If it's 1/ not preclaimed and 2/ not the old claiming process, fetch the
  // statement kind to sign.
  const statementKind = useCall<StatementKind | null>(!isPreclaimed && !isOldClaimProcess && !!ethereumAddress && api.query.claims.signing, [ethereumAddress], transformStatement);

  const statementSentence = getStatement(systemChain, statementKind)?.sentence || '';

  const prefix = u8aToString(api.consts.claims.prefix.toU8a(true));
  const payload = accountId
    ? `${prefix}${u8aToHex(decodeAddress(accountId), -1, false)}${statementSentence}`
    : '';

  return (
    <main>
      {!isOldClaimProcess && <Warning />}
      
      
      <Columar>
        <Columar.Column>
          <Banner type='warning'>
            <p>
              <span style={{ "wordWrap": "break-word", "wordBreak": "break-all"  }}>
                <span>{t<string>('Since the mainnet democracy go live, users who have not yet claimed CRU18 can claim CRU18 ')}</span>
                <span style={{ 'fontWeight': 'bold' }}>{t<string>('before the block height #4,866,666 of mainnet,')}&nbsp;</span>
                <span>{t<string>('For the claiming tutorial, please refer to')}&nbsp;</span>
                <a href={ i18n.language == 'zh' ? "https://wiki-maxwell.crust.network/docs/zh-CN/claimCRU18" : 'https://wiki-maxwell.crust.network/docs/en/claimCRU18'} target="_blank">[Maxwell Wiki]</a> 
              </span>
            </p>
          </Banner>
          <Card withBottomMargin>
            {/* <h3>{t<string>(`1. Select your {{chain}} account and enter`, {
                replace: {
                  chain: systemChain
                }
              })} <a href='https://etherscan.io/token/0x32a7C02e79c4ea1008dD6564b35F131428673c41'>{t('ERC20 CRU')}</a> {t<string>('transfer tx hash')} </h3> */}
            <InputAddress
              defaultValue={accountId}
              help={t<string>('The account you want to claim to.')}
              isDisabled={ethereumTxHashValid}
              label={t<string>('claim to account')}
              onChange={setAccountId}
              type='account'
            />
            {/* <Dropdown
              defaultValue={accountId?.toString()}
              help={t<string>('The destination account for any payments as either a nominator or validator')}
              label={t<string>('token types')}
              onChange={setTokenType}
              options={options}
              value={tokenType}
            /> */}
            {(step === Step.Account) && (
              <Button.Group>
                <Button
                  icon='sign-in-alt'
                  isBusy={isBusy}
                  label={preclaimEthereumAddress === PRECLAIMS_LOADING
                    ? t<string>('Loading')
                    : t<string>('Continue')
                  }
                  onClick={handleAccountStep}
                />
              </Button.Group>
            )}
            <HttpStatus
              isStatusOpen={statusOpen}
              message={result}
              setStatusOpen={setStatusOpen}
              status={status}
            />
          </Card>
          {
            // We need to know the ethereuem address only for the new process
            // to be able to know the statement kind so that the users can sign it
            (step >= Step.ETHAddress && !isPreclaimed && !isOldClaimProcess) && (
              <Card withBottomMargin>
                <h3>{t<string>('2. Enter the ETH address from the sale.')}</h3>
                <Input
                  autoFocus
                  className='full'
                  help={t<string>('The the Ethereum address you used during the pre-sale (starting by "0x")')}
                  label={t<string>('Pre-sale ethereum address')}
                  onChange={onChangeEthereumAddress}
                  value={ethereumAddress || ''}
                />
                {(step === Step.ETHAddress) && (
                  <Button.Group>
                    <Button
                      icon='sign-in-alt'
                      isDisabled={!ethereumAddress}
                      label={t<string>('Continue')}
                      onClick={goToStepSign}
                    />
                  </Button.Group>
                )}
              </Card>
            )}
          {(step >= Step.Sign && !isPreclaimed) && (
            <Card>
              <h3>{t<string>('{{step}}. Sign with your ETH address', { replace: { step: isOldClaimProcess ? '2' : '3' } })}</h3>
              {!isOldClaimProcess && (
                <Statement
                  kind={statementKind}
                  systemChain={systemChain}
                />
              )}
              <div>{t<string>('Copy the following string and sign it with the Ethereum account you used during the pre-sale in the wallet of your choice, using the string as the payload, and then paste the transaction signature object below:')}</div>
              <CopyToClipboard
                onCopy={onCopy}
                text={payload}
              >
                <Payload
                  data-for='tx-payload'
                  data-tip
                >
                  {payload}
                </Payload>
              </CopyToClipboard>
              <Tooltip
                place='right'
                text={didCopy ? t<string>('copied') : t<string>('click to copy')}
                trigger='tx-payload'
              />
              <div>{t<string>('Paste the signed message into the field below. The placeholder text is there as a hint to what the message should look like:')}</div>
              <Signature
                onChange={onChangeSignature}
                placeholder={`{\n  "address": "0x ...",\n  "msg": "${prefix}:...",\n  "sig": "0x ...",\n  "version": "3",\n  "signer": "..."\n}`}
                rows={10}
              />
              {(step === Step.Sign) && (
                <Button.Group>
                  <Button
                    icon='sign-in-alt'
                    isDisabled={!accountId || !signature}
                    label={t<string>('Confirm')}
                    onClick={goToStepClaim}
                  />
                </Button.Group>
              )}
            </Card>
          )}
        </Columar.Column>
        <Columar.Column>
          {(step >= Step.Claim) && (
            <PreClaimDisplay
              accountId={accountId}
              ethereumAddress={ethereumAddress}
              // tokenType={tokenType}
              ethereumSignature={signature}
              isOldClaimProcess={isOldClaimProcess}
              // onSuccess={goToStepAccount}
              statementKind={statementKind}
              ethereumTxHash={ethereumTxHash}
            />
          )}
        </Columar.Column>
      </Columar>
    </main>
  );
}
Example #23
Source File: index.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Claims(): React.ReactElement<Props> {
  const [didCopy, setDidCopy] = useState(false);
  const [ethereumAddress, setEthereumAddress] = useState<string | undefined | null>(null);
  const [signature, setSignature] = useState<EcdsaSignature | null>(null);
  const [step, setStep] = useState<Step>(Step.Account);
  const [accountId, setAccountId] = useState<string | null>(null);
  const { api, systemChain } = useApi();
  const { t } = useTranslation();
  const [statusOpen, setStatusOpen] = useState<boolean>(false);
  const [result, setResult] = useState<string>('');
  const [status, setStatus] = useState<string>('');
  const [ethereumTxHashValid, setEthereumTxHashValid] = useState<boolean>(false);
  const [isBusy, setIsBusy] = useState<boolean>(false);
  const [isValid, setIsValid] = useState(false);
  const [ethereumTxHash, setEthereumTxHash] = useState<string | undefined | null>(null);

  // This preclaimEthereumAddress holds the result of `api.query.claims.preclaims`:
  // - an `EthereumAddress` when there's a preclaim
  // - null if no preclaim
  // - `PRECLAIMS_LOADING` if we're fetching the results
  const [preclaimEthereumAddress, setPreclaimEthereumAddress] = useState<string | null | undefined | typeof PRECLAIMS_LOADING>(PRECLAIMS_LOADING);
  const isPreclaimed = !!preclaimEthereumAddress && preclaimEthereumAddress !== PRECLAIMS_LOADING;
  const claimLimit = useCall<BalanceOf>(api.query.claims.claimLimit);

  // Everytime we change account, reset everything, and check if the accountId
  // has a preclaim.
  useEffect(() => {
    if (!accountId) {
      return;
    }

    setStep(Step.Account);
    setEthereumAddress(null);
    setEthereumTxHash(null);
    setPreclaimEthereumAddress(PRECLAIMS_LOADING);

    if (!api.query.claims || !api.query.claims.preclaims) {
      return setPreclaimEthereumAddress(null);
    }

    api.query.claims
      .preclaims<Option<EthereumAddress>>(accountId)
      .then((preclaim): void => {
        const address = preclaim.unwrapOr(null)?.toString();

        setEthereumAddress(address);
        setPreclaimEthereumAddress(address);
      })
      .catch((): void => setPreclaimEthereumAddress(null));
  }, [accountId, api.query.claims, api.query.claims.preclaims]);

  // Old claim process used `api.tx.claims.claim`, and didn't have attest
  const isOldClaimProcess = !api.tx.claims.claimAttest;

  useEffect(() => {
    if (didCopy) {
      setTimeout((): void => {
        setDidCopy(false);
      }, 1000);
    }
  }, [didCopy]);

  const goToStepAccount = useCallback(() => {
    setStep(Step.Account);
    setEthereumTxHash("");
    setEthereumTxHashValid(false);
  }, []);

  const goToStepSign = useCallback(() => {
    setStep(Step.Sign);
  }, []);

  const goToStepClaim = useCallback(() => {
    setStep(Step.Claim);
  }, []);

  const handleAccountStep = useCallback(async () => {
    setIsBusy(true);

    api.query.claims
      .claimed<Option<BalanceOf>>(ethereumTxHash?.toString())
      .then(async (claim): Promise<void> => {
        const isClaimed = JSON.parse(JSON.stringify(claim));

        if (isClaimed) {
          setResult('AlreadyClaimed');
          setStatus('error');
          setStatusOpen(true);
        } else {
          const result = await httpPost("https://claim.crust.network/claim/" + ethereumTxHash);
          setIsBusy(false);
          setResult(result.statusText);
          setStatus(result.status);

          if (result.code == 200) {
            setEthereumTxHashValid(true);
            goToStepSign();
          } else {
            api.query.claims
              .claims<Option<BalanceOf>>(ethereumTxHash?.toString())
              .then(async (claim): Promise<void> => {
                const claimOpt = JSON.parse(JSON.stringify(claim));
                if (claimOpt) {
                  setResult('MintClaimSuccess');
                  setStatus('success');
                  setEthereumTxHashValid(true);
                  goToStepSign();
                } else {
                  setResult('MintError, Please try again');
                  setStatus('error');
                }
              })
              .catch((): void => setIsBusy(false));
          }
          setStatusOpen(true);
        }
      })
      .catch((): void => setIsBusy(false))
      .finally(() =>  setIsBusy(false));
  }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess, ethereumTxHash]);

  const onChangeEthereumTxHash = useCallback((hex: string) => {
    let [isValid, value] = convertInput(hex);

    isValid = isValid && (
      length !== -1
        ? value.length === 32
        : value.length !== 0
    );
    setIsValid(isValid);
    setEthereumTxHash(hex.trim());
  }, [ethereumTxHash]);

  function convertInput(value: string): [boolean, Uint8Array] {
    if (value === '0x') {
      return [true, new Uint8Array([])];
    } else if (value.startsWith('0x')) {
      try {
        return [true, hexToU8a(value)];
      } catch (error) {
        return [false, new Uint8Array([])];
      }
    }

    // maybe it is an ss58?
    try {
      return [true, decodeAddress(value)];
    } catch (error) {
      // we continue
    }

    return isAscii(value)
      ? [true, stringToU8a(value)]
      : [value === '0x', new Uint8Array([])];
  }

  // Depending on the account, decide which step to show.
  // const handleAccountStep = useCallback(() => {
  //   if (isPreclaimed) {
  //     goToStepClaim();
  //   } else if (ethereumAddress || isOldClaimProcess) {
  //     goToStepSign();
  //   } else {
  //     setStep(Step.ETHAddress);
  //   }
  // }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess]);

  const onChangeSignature = useCallback((event: React.SyntheticEvent<Element>) => {
    const { value: signatureJson } = event.target as HTMLInputElement;

    const { ethereumAddress, signature } = recoverFromJSON(signatureJson);

    setEthereumAddress(ethereumAddress?.toString());
    setSignature(signature);
  }, []);

  const onChangeEthereumAddress = useCallback((value: string) => {
    // FIXME We surely need a better check than just a trim

    setEthereumAddress(value.trim());
  }, []);

  const onCopy = useCallback(() => {
    setDidCopy(true);
  }, []);

  // If it's 1/ not preclaimed and 2/ not the old claiming process, fetch the
  // statement kind to sign.
  const statementKind = useCall<StatementKind | null>(!isPreclaimed && !isOldClaimProcess && !!ethereumAddress && api.query.claims.signing, [ethereumAddress], transformStatement);

  const statementSentence = getStatement(systemChain, statementKind)?.sentence || '';

  const prefix = u8aToString(api.consts.claims.prefix.toU8a(true));
  const payload = accountId
    ? `${prefix}${u8aToHex(decodeAddress(accountId), -1, false)}${statementSentence}${ethereumTxHash?.substring(2)}`
    : '';

  return (
    <main>
      {!isOldClaimProcess && <Warning />}
      <h2>
        {t<string>('Claim your {{token}} tokens', {
          replace: {
            token: TokenUnit.abbr
          }
        })}
      </h2>
      <Columar>
        <Columar.Column>
          <Card withBottomMargin>
            <h3>{t<string>('0. Burn your ')}<a href='https://etherscan.io/token/0x32a7C02e79c4ea1008dD6564b35F131428673c41'>{t('ERC20 CRU')}</a>{t<string>(', transfer to address ')} <a href='https://etherscan.io/address/0x0000000000000000000000000000000000000001' target="_blank">0x0000000000000000000000000000000000000001</a></h3>
            <Banner type='warning'>
              <p>{t<string>('Please make sure you have the authority to make signature with the private key of the wallet account, using an exchange account to sent a transfer (withdrawal) transaction will be invalidated and cause asset loss, you are responsible for the consequences')}</p>
            </Banner>
            <img style={{'marginLeft': 'auto', 'marginRight': 'auto', 'display': 'block', "width": "150px" }} src={burnPng as string} />
          </Card>
          {(<Card withBottomMargin>
            <h3>{t<string>(`1. Select your {{chain}} account and enter`, {
              replace: {
                chain: systemChain
              }
            })} <a href='https://etherscan.io/token/0x32a7C02e79c4ea1008dD6564b35F131428673c41'>{t('ERC20 CRU')}</a>
             {t<string>(' transfer tx hash')}<span>
               {t<string>(`, If your claim amount is greater than the claim limit `)}
               <span style={{ 'color': '#ff8812', 'textDecoration': 'underline', 'fontStyle': 'italic' }}>({formatBalance(claimLimit, { withUnit: 'CRU' })})</span>
               {t<string>(', please wait for the limit update')}
               </span>
            </h3>
            <InputAddress
              defaultValue={accountId}
              help={t<string>('The account you want to claim to.')}
              isDisabled={ethereumTxHashValid}
              label={t<string>('claim to account')}
              onChange={setAccountId}
              type='account'
            />
            <Input
              autoFocus
              className='full'
              help={t<string>('The Ethereum CRU transfer tx hash (starting by "0x")')}
              isDisabled={ethereumTxHashValid}
              isError={!isValid}
              label={t<string>('Ethereum tx hash')}
              onChange={onChangeEthereumTxHash}
              placeholder={t<string>('0x prefixed hex, e.g. 0x1234 or ascii data')}
              value={ethereumTxHash || ''}
            />
            {(step === Step.Account) && (<Button.Group>
              <Button
                icon='sign-in-alt'
                isBusy={isBusy}
                isDisabled={preclaimEthereumAddress === PRECLAIMS_LOADING || ethereumTxHash === null || ethereumTxHash === '' || !isValid}
                label={preclaimEthereumAddress === PRECLAIMS_LOADING
                  ? t<string>('Loading')
                  : t<string>('Continue')
                }
                onClick={handleAccountStep}
              />
            </Button.Group>)}
            <HttpStatus
              isStatusOpen={statusOpen}
              message={result}
              setStatusOpen={setStatusOpen}
              status={status}
            />
          </Card>)}
          {
            // We need to know the ethereuem address only for the new process
            // to be able to know the statement kind so that the users can sign it
            (step >= Step.ETHAddress && !isPreclaimed && !isOldClaimProcess) && (
              <Card withBottomMargin>
                <h3>{t<string>('2. Enter the ETH address from the sale.')}</h3>
                <Input
                  autoFocus
                  className='full'
                  help={t<string>('The the Ethereum address you burnt your ERC20 CRU in step 0 (starting by "0x")')}
                  label={t<string>('Pre-sale ethereum address')}
                  onChange={onChangeEthereumAddress}
                  value={ethereumAddress || ''}
                />
                {(step === Step.ETHAddress) && (
                  <Button.Group>
                    <Button
                      icon='sign-in-alt'
                      isDisabled={!ethereumAddress}
                      label={t<string>('Continue')}
                      onClick={goToStepSign}
                    />
                  </Button.Group>
                )}
              </Card>
            )}
            {(step >= Step.Sign && !isPreclaimed) && (
              <Card>
                <h3>{t<string>('{{step}}. Sign with your ETH address', { replace: { step: isOldClaimProcess ? '2' : '3' } })}</h3>
                {!isOldClaimProcess && (
                  <Statement
                    kind={statementKind}
                    systemChain={systemChain}
                  />
                )}
                <div>{t<string>('Copy the following string and sign it with the Ethereum account you burnt your ERC20 CRU in step 0, using the string as the payload, and then paste the transaction signature object below:')}</div>
                <CopyToClipboard
                  onCopy={onCopy}
                  text={payload}
                >
                  <Payload
                    data-for='tx-payload'
                    data-tip
                  >
                    {payload}
                  </Payload>
                </CopyToClipboard>
                <Tooltip
                  place='right'
                  text={didCopy ? t<string>('copied') : t<string>('click to copy')}
                  trigger='tx-payload'
                />
                <div>{t<string>('Paste the signed message into the field below. The placeholder text is there as a hint to what the message should look like:')}</div>
                <Signature
                  onChange={onChangeSignature}
                  placeholder={`{\n  "address": "0x ...",\n  "msg": "0x ...",\n  "sig": "...",\n  "version": "3",\n  "signer": "..."\n}`}
                  rows={10}
                />
                {(step === Step.Sign) && (
                  <Button.Group>
                    <Button
                      icon='sign-in-alt'
                      isDisabled={!accountId || !signature}
                      label={t<string>('Confirm claim')}
                      onClick={goToStepClaim}
                    />
                  </Button.Group>
                )}
              </Card>
            )}
        </Columar.Column>
        <Columar.Column>
            {(step >= Step.Claim) && (
              isPreclaimed
                ? <AttestDisplay
                  accountId={accountId}
                  ethereumAddress={ethereumAddress}
                  onSuccess={goToStepAccount}
                  statementKind={statementKind}
                  systemChain={systemChain}
                />
                : <ClaimDisplay
                  accountId={accountId}
                  ethereumAddress={ethereumAddress}
                  ethereumSignature={signature}
                  isOldClaimProcess={isOldClaimProcess}
                  onSuccess={goToStepAccount}
                  statementKind={statementKind}
                  ethereumTxHash={ethereumTxHash}
                />
            )}
        </Columar.Column>
      </Columar>
    </main>
  );
}
Example #24
Source File: edit_identity_modal.ts    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
EditIdentityModal: m.Component<IAttrs, IState> = {
  oninit: (vnode) => {
    app.runWhenReady(async () => {
      vnode.state.identity = await (app.chain as Substrate).identities.load(vnode.attrs.account);
      m.redraw();
    });
  },
  oncreate: (vnode: m.VnodeDOM<IAttrs, IState>) => {
    if (vnode.state.identity?.info) {
      const {
        additional,
        display,
        legal,
        web,
        riot,
        email,
        // pgpFingerprint,
        image,
        twitter
      } = vnode.state.identity?.info;

      // do not display SHA values, only raw strings
      const d2s = (d: Data) => u8aToString(d.toU8a()).replace(/[^\x20-\x7E]/g, '');
      $(vnode.dom).find('input[name=display]').val(d2s(display));
      $(vnode.dom).find('input[name=legal]').val(d2s(legal));
      $(vnode.dom).find('input[name=web]').val(d2s(web));
      $(vnode.dom).find('input[name=riot]').val(d2s(riot));
      $(vnode.dom).find('input[name=email]').val(d2s(email));
      // if (pgpFingerprint.isSome) {
      //   $(vnode.dom).find('input[name=pgp]').val(pgpFingerprint.unwrap().toString());
      // }
      $(vnode.dom).find('input[name=twitter]').val(d2s(twitter));
    }
  },
  view: (vnode: m.VnodeDOM<IAttrs, IState>) => {
    const updateIdentity = async () => {
      const data = {
        display: `${$(vnode.dom).find('input[name=display]').val()}`.trim(),
        legal: `${$(vnode.dom).find('input[name=legal]').val()}`.trim(),
        web: `${$(vnode.dom).find('input[name=web]').val()}`.trim(),
        riot: `${$(vnode.dom).find('input[name=riot]').val()}`.trim(),
        email: `${$(vnode.dom).find('input[name=email]').val()}`.trim(),
        // pgpFingerprint: `${$(vnode.dom).find('input[name=pgp]').val()}`.trim(),
        twitter: `${$(vnode.dom).find('input[name=twitter]').val()}`.trim(),
        image: null,
      };

      vnode.state.saving = true;
      const idData: IdentityInfoProps = {
        display: { [data.display ? 'raw' : 'none']: data.display ? data.display : null },
        email: { [data.email ? 'raw' : 'none']: data.email ? data.email : null },
        image: { [data.image ? 'sha256' : 'none']: data.image ? data.image : null },
        legal: { [data.legal ? 'raw' : 'none']: data.legal ? data.legal : null },
        riot: { [data.riot ? 'raw' : 'none']: data.riot ? data.riot : null },
        web: { [data.web ? 'raw' : 'none']: data.web ? data.web : null },
        twitter: { [data.twitter ? 'raw' : 'none']: data.twitter ? data.twitter : null },
        // pgpFingerprint: data.pgpFingerprint ? data.pgpFingerprint : null,
        additional: [],
      };

      try {
        await createTXModal(
          (app.chain as Substrate).identities.setIdentityTx(app.user.activeAccount as SubstrateAccount, idData)
        );
      } catch (error) {
        if (typeof error === 'string') {
          notifyError(error);
        } else if (error.txType === 'setIdentity') {
          notifyError('Sending transaction failed');
        } else {
          notifyError('Unknown error');
        }
      }

      // force creation and update of the user's on-chain identity, guaranteeing that the identity
      // component has immediate access to the new identity.
      await (app.chain as Substrate).identities.load(app.user.activeAccount as SubstrateAccount);

      // temporarily mark the user's profile as invalid, since they've potentially updated their
      // display name. this ensures that any identity display will fall back to the loaded identity.
      const profile = app.profiles.getProfile(app.chain.id, app.user.activeAccount.address);
      if (profile) {
        profile.invalidateName();
      }
      vnode.state.saving = false;
      m.redraw();
    };

    const getInput = (inputLabel, inputName, description, prefixAt = false) => {
      return m(FormGroup, [
        m(FormLabel, {
          for: inputName,
        }, inputLabel),
        m(Input, {
          name: inputName,
          id: inputName,
          placeholder: description,
          autocomplete: 'off',
          contentLeft: prefixAt ? m(Icon, { name: Icons.AT_SIGN }) : null,
        }),
      ]);
    };

    return m('.EditIdentityModal', [
      m('.compact-modal-title', [
        m('h3', 'Set on-chain identity')
      ]),
      m(Form, { class: 'form' }, [
        getInput('Display Name', 'display', 'A reasonable display name for the controller of the account'),
        getInput('Legal Name', 'legal', 'Full legal name in the local jurisdiction of the entity'),
        getInput('Website', 'web', 'Website for the controller of the account, https:// automatically prepended'),
        getInput('Riot/Matrix', 'riot', 'Riot/Matrix handle held by the controller of the account'),
        getInput('Email', 'email', 'Email address of the controller of the account'),
        // getInput('PGP', 'pgp', 'PGP/GPG public key of the controller of the account'),
        getInput('Twitter', 'twitter', 'Twitter identity of the controller of the account', true),
        m('.form-bottom', [
          m('.buttons', [
            m(Button, {
              intent: 'primary',
              disabled: vnode.state.saving || !app.chain?.loaded,
              rounded: true,
              onclick: (e) => {
                e.preventDefault();
                updateIdentity().then(() => $(vnode.dom).trigger('modalexit'));
              },
              label: 'Set identity'
            }),
            m(Button, {
              rounded: true,
              onclick: (e) => {
                e.preventDefault();
                $(vnode.dom).trigger('modalexit');
              },
              label: 'Cancel'
            }),
          ]),
          m('.clear'),
        ])
      ])
    ]);
  }
}