cyb/src/utils/utils.ts

/* eslint-disable no-await-in-loop */

import { Sha256 } from '@cosmjs/crypto';
import { fromBase64, fromUtf8, toBech32 } from '@cosmjs/encoding';
import { Pool } from '@cybercongress/cyber-js/build/codec/tendermint/liquidity/v1beta1/liquidity';
import bech32 from 'bech32';
import BigNumber from 'bignumber.js';
import { BECH32_PREFIX, BECH32_PREFIX_VAL_CONS } from 'src/constants/config';
import { localStorageKeys } from 'src/constants/localStorageKeys';
import { Option } from 'src/types';
import { ObjKeyValue } from 'src/types/data';
import { AccountValue } from 'src/types/defaultAccount';
import { toHex } from 'src/utils/encoding';
import cyberBostrom from '../image/large-green.png';
import customNetwork from '../image/large-orange-circle.png';
import cyberSpace from '../image/large-purple-circle.png';
const DEFAULT_DECIMAL_DIGITS = 3;
const DEFAULT_CURRENCY = 'GoL';

const roundNumber = (num, scale) => {
  if (!`${num}`.includes('e')) {
    return +`${Math.floor(`${num}e+${scale}`)}e-${scale}`;
  }
  const arr = `${num}`.split('e');
  let sig = '';
  if (+arr[1] + scale > 0) {
    sig = '+';
  }
  const i = `${+arr[0]}e${sig}${+arr[1] + scale}`;
  const j = Math.floor(i);
  const k = +`${j}e-${scale}`;
  return k;
};

function numberWithCommas(x) {
  const parts = x.split('.');
  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
  return parts.join('.');
}

const formatNumber = (number: number | string, toFixed?: number): string => {
  let formatted = number;

  if (toFixed) {
    formatted = roundNumber(formatted, toFixed);
    formatted = formatted.toFixed(toFixed + 1);
  }

  if (typeof number === 'string') {
    return numberWithCommas(formatted);
  }

  return formatted
    .toLocaleString('en')
    .replace(/(\.\d{0,})0+MATH_PLACEHOLDER_41')
    .replace(/,/g, ' ');
};

const PREFIXES = [
  {
    prefix: 'T',
    power: 10 ** 12,
  },
  {
    prefix: 'G',
    power: 10 ** 9,
  },
  {
    prefix: 'M',
    power: 10 ** 6,
  },
  {
    prefix: 'K',
    power: 10 ** 3,
  },
];

export function formatCurrency(
  value,
  currency = DEFAULT_CURRENCY,
  decimalDigits = DEFAULT_DECIMAL_DIGITS,
  prefixCustom = PREFIXES
) {
  const { prefix = '', power = 1 } = prefixCustom.find((obj) => value >= obj.power) || {};

  return `${roundNumber(
    Number(value) / power,
    decimalDigits
  )} ${prefix}${currency.toLocaleUpperCase()}`;
}

const getDecimal = (number, _toFixed) => {
  const nstring = number.toString();
  const narray = nstring.split('.');
  const result = narray.length > 1 ? narray[1] : '000';
  return result;
};

const asyncForEach = async (array, callback) => {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
};

const fromBech32 = (operatorAddr, prefix = BECH32_PREFIX) => {
  const address = bech32.decode(operatorAddr);
  return bech32.encode(prefix, address.words);
};

export const consensusPubkey = (pubKey: string) => {
  const ed25519PubkeyRaw = fromBase64(pubKey);
  const addressData = sha256(ed25519PubkeyRaw).slice(0, 20);
  return toBech32(BECH32_PREFIX_VAL_CONS, addressData);
};

const trimString = (address: string, first = 3, second = 8) => {
  if (address && address.length > 11) {
    return `${address.substring(0, first)}...${address.substring(address.length - second)}`;
  }
  if (address && address.length < 11) {
    return address;
  }
  return '';
};

const exponentialToDecimal = (exponential) => {
  let decimal = exponential.toString().toLowerCase();
  if (decimal.includes('e+')) {
    const exponentialSplitted = decimal.split('e+');
    let postfix = '';
    for (
      let i = 0;
      i <
      +exponentialSplitted[1] -
        (exponentialSplitted[0].includes('.') ? exponentialSplitted[0].split('.')[1].length : 0);
      i++
    ) {
      postfix += '0';
    }
    decimal = exponentialSplitted[0].replace('.', '') + postfix;
  }
  if (decimal.toLowerCase().includes('e-')) {
    const exponentialSplitted = decimal.split('e-');
    let prefix = '0.';
    for (let i = 0; i < +exponentialSplitted[1] - 1; i++) {
      prefix += '0';
    }
    decimal = prefix + exponentialSplitted[0].replace('.', '');
  }
  return decimal;
};

function dhm(t) {
  const cd = 24 * 60 * 60 * 1000;
  const ch = 60 * 60 * 1000;
  let d = Math.floor(t / cd);
  let h = Math.floor((t - d * cd) / ch);
  let m = Math.round((t - d * cd - h * ch) / 60000);
  const pad = (n, unit) => {
    return n < 10 ? `0${n}${unit}` : `${n}${unit}`;
  };
  if (m === 60) {
    h += 1;
    m = 0;
  }
  if (h === 24) {
    d += 1;
    h = 0;
  }
  return [`${d}d`, pad(h, 'h'), pad(m, 'm')].join(':');
}

const downloadObjectAsJson = (exportObj, exportName) => {
  const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(exportObj))}`;
  const downloadAnchorNode = document.createElement('a');

  downloadAnchorNode.setAttribute('href', dataStr);
  downloadAnchorNode.setAttribute('download', `${exportName}.json`);
  document.body.appendChild(downloadAnchorNode);
  downloadAnchorNode.click();
  downloadAnchorNode.remove();
};

const isMobileTablet = () => {
  let hasTouchScreen = false;
  if ('maxTouchPoints' in navigator) {
    hasTouchScreen = navigator.maxTouchPoints > 0;
  } else if ('msMaxTouchPoints' in navigator) {
    hasTouchScreen = navigator.msMaxTouchPoints > 0;
  } else {
    const mQ = window.matchMedia && matchMedia('(pointer:coarse)');
    if (mQ && mQ.media === '(pointer:coarse)') {
      hasTouchScreen = !!mQ.matches;
    } else if ('orientation' in window) {
      hasTouchScreen = true; // deprecated, but good fallback
    } else {
      // Only as a last resort, fall back to user agent sniffing
      const UA = navigator.userAgent;
      hasTouchScreen =
        /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
        /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA);
    }
  }
  return hasTouchScreen;
};

const coinDecimals = (number) => {
  return number * 10 ** -18;
};

const convertResources = (number) => {
  return Math.floor(number * 10 ** -3);
};

function timeSince(timeMS: number) {
  const seconds = Math.floor(timeMS / 1000);

  if (seconds === 0) {
    return 'now';
  }

  let interval = Math.floor(seconds / 31536000);

  if (interval > 1) {
    return `${interval} years`;
  }
  interval = Math.floor(seconds / 2592000);
  if (interval > 1) {
    return `${interval} months`;
  }
  interval = Math.floor(seconds / 86400);
  if (interval > 1) {
    return `${interval} days`;
  }
  interval = Math.floor(seconds / 3600);
  if (interval > 1) {
    return `${interval} hours`;
  }
  interval = Math.floor(seconds / 60);
  if (interval > 1) {
    return `${interval} min`;
  }
  return `${Math.floor(seconds)} sec`;
}

const reduceBalances = (data): ObjKeyValue => {
  try {
    let balances = {};
    if (Object.keys(data).length > 0) {
      balances = data.reduce(
        (obj, item) => ({
          ...obj,
          [item.denom]: parseFloat(item.amount),
        }),
        {}
      );
    }
    return balances;
  } catch (error) {
    console.log(`error reduceBalances`, error);
    return {};
  }
};

// example: oneLiner -> message.module=wasm&message.action=/cosmwasm.wasm.v1.MsgStoreCode&store_code.code_id=${codeId}
function makeTags(oneLiner) {
  return oneLiner.split('&').map((pair) => {
    if (pair.indexOf('=') === -1) {
      throw new Error('Parsing error: Equal sign missing');
    }
    const parts = pair.split('=');
    if (parts.length > 2) {
      throw new Error(
        'Parsing error: Multiple equal signs found. If you need escaping support, please create a PR.'
      );
    }
    const [key, value] = parts;
    if (!key) {
      throw new Error('Parsing error: Key must not be empty');
    }
    return { key, value };
  });
}

function parseMsgContract(msg) {
  const json = fromUtf8(msg);

  return JSON.parse(json);
}
const replaceSlash = (text) => text.replace(/\//g, '%2F');

const encodeSlash = (text) => text.replace(/%2F/g, '/');

const groupMsg = (ArrMsg, size = 2) => {
  const link = [];
  for (let i = 0; i < Math.ceil(ArrMsg.length / size); i += 1) {
    link[i] = ArrMsg.slice(i * size, i * size + size);
  }
  return link;
};

const selectNetworkImg = (network) => {
  switch (network) {
    case 'bostrom':
      return cyberBostrom;
    case 'space-pussy':
      return cyberSpace;

    default:
      return customNetwork;
  }
};

const sha256 = (data: string) => {
  return new Uint8Array(new Sha256().update(new TextEncoder().encode(data)).digest());
};

function getDenomHash(path, baseDenom) {
  const parts = path.split('/');
  parts.push(baseDenom);
  const newPath = parts.slice().join('/');
  return `ibc/${toHex(sha256(newPath)).toUpperCase()}`;
}

function convertAmount(rawAmount, precision) {
  return new BigNumber(rawAmount)
    .shiftedBy(-precision)
    .dp(precision, BigNumber.ROUND_FLOOR)
    .toNumber();
}

function convertAmountReverce(rawAmount, precision) {
  return new BigNumber(rawAmount)
    .shiftedBy(precision)
    .dp(precision, BigNumber.ROUND_FLOOR)
    .toNumber();
}

function getDisplayAmount(rawAmount: number | string, precision: number): number {
  return parseFloat(
    new BigNumber(rawAmount)
      .shiftedBy(-precision)
      .dp(precision, BigNumber.ROUND_FLOOR)
      .toFixed(precision > 0 ? 3 : 0, BigNumber.ROUND_FLOOR)
  );
}

function getDisplayAmountReverce(rawAmount, precision) {
  return new BigNumber(rawAmount)
    .shiftedBy(precision)
    .dp(precision, BigNumber.ROUND_FLOOR)
    .toFixed(precision > 0 ? 3 : 0, BigNumber.ROUND_FLOOR);
}

function isNative(denom) {
  if (denom?.includes('ibc')) {
    return false;
  }
  return true;
}

const findPoolDenomInArr = (baseDenom: string, dataPools: Pool[]): Option<Pool> => {
  const findObj = dataPools.find((item) => item.poolCoinDenom === baseDenom);
  return findObj;
};

// REFACTOR: Probably wrong timestamp
const getNowUtcTime = (): number => {
  const now = new Date();
  const utcTime = new Date(
    now.getUTCFullYear(),
    now.getUTCMonth(),
    now.getUTCDate(),
    now.getUTCHours(),
    now.getUTCMinutes(),
    now.getUTCSeconds()
  );

  return utcTime.getTime();
};

export function covertUint8ArrayToString(data: Uint8Array): string {
  return new TextDecoder().decode(data);
}

export const setEncryptedMnemonic = (encrypted: string, bech32: string) => {
  try {
    localStorage.setItem(`cyb:mnemonic:${bech32}`, encrypted);
  } catch (e) {
    console.error('Failed to save encrypted mnemonic:', e);
    throw new Error('Could not save wallet. Check browser storage settings.');
  }
};

export const getEncryptedMnemonic = (bech32: string): string | null => {
  return localStorage.getItem(`cyb:mnemonic:${bech32}`);
};

export const removeEncryptedMnemonic = (bech32: string) => {
  localStorage.removeItem(`cyb:mnemonic:${bech32}`);
};

export {
  formatNumber,
  asyncForEach,
  getDecimal,
  fromBech32,
  trimString,
  exponentialToDecimal,
  dhm,
  downloadObjectAsJson,
  isMobileTablet,
  coinDecimals,
  convertResources,
  timeSince,
  reduceBalances,
  makeTags,
  parseMsgContract,
  replaceSlash,
  encodeSlash,
  groupMsg,
  selectNetworkImg,
  getDenomHash,
  getDisplayAmount,
  getDisplayAmountReverce,
  convertAmount,
  convertAmountReverce,
  isNative,
  findPoolDenomInArr,
  getNowUtcTime,
};

export const removeAllMnemonics = () => {
  const keysToRemove: string[] = [];
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    if (key?.startsWith('cyb:mnemonic')) {
      keysToRemove.push(key);
    }
  }
  keysToRemove.forEach((key) => localStorage.removeItem(key));
  // Also remove legacy device key
  localStorage.removeItem('cyb:device-key');
};

Synonyms

soft3.js/examples/utils.ts
pussy-ts/src/utils/utils.ts
bostrom.network/src/lib/utils.ts
pussy-ts/src/services/ibc-history/utils.ts
cyb/src/services/CozoDb/utils.ts
cyb/src/features/ibc-history/utils.ts
cyb/src/containers/mint/utils.ts
cyb/src/containers/portal/utils.ts
pussy-ts/src/containers/portal/utils.ts
cyb/src/utils/search/utils.ts
pussy-ts/src/services/CozoDb/utils.ts
pussy-ts/src/utils/search/utils.ts
cyb/src/containers/warp/utils.ts
cyb/src/components/time/utils.ts
pussy-ts/src/containers/warp/utils.ts
cyb/src/pages/teleport/swap/utils.ts
pussy-ts/src/pages/teleport/swap/utils.ts
cyb/src/features/studio/utils/utils.ts
cyb/src/containers/sigma/hooks/utils.ts
cyb/src/pages/robot/Brain/utils.ts
cyb/src/services/backend/services/sync/utils.ts
pussy-ts/src/services/backend/services/sync/utils.ts
cyb/src/pages/teleport/components/Inputs/utils.ts
pussy-ts/src/services/backend/services/indexer/utils.ts
pussy-ts/src/services/backend/services/lcd/utils.ts
pussy-ts/src/pages/teleport/components/Inputs/utils.ts
cyb/src/features/studio/components/Editor/utils/utils.ts
cyb/src/pages/robot/_refactor/account/tabs/feeds/utils.ts

Neighbours