cosmos-explorer/src/libs/utils.js
2022-05-04 10:18:02 +02:00

491 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Bech32, fromBase64, fromHex, toHex,
} from '@cosmjs/encoding'
import { sha256, stringToPath } from '@cosmjs/crypto'
// ledger
import TransportWebBLE from '@ledgerhq/hw-transport-web-ble'
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
import CosmosApp from 'ledger-cosmos-js'
import { LedgerSigner } from '@cosmjs/ledger-amino'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'
import utc from 'dayjs/plugin/utc'
import RIPEMD160 from 'ripemd160'
import localeData from 'dayjs/plugin/localeData'
import { $themeColors } from '@themeConfig'
// import { SigningStargateClient } from '@cosmjs/stargate'
import PingWalletClient from './data/signing'
dayjs.extend(localeData)
dayjs.extend(duration)
dayjs.extend(relativeTime)
dayjs.extend(utc)
export function getLocalObject(name) {
const text = localStorage.getItem(name)
if (text) {
return JSON.parse(text)
}
return null
}
export function getLocalChains() {
return getLocalObject('chains')
}
export function getLocalAccounts() {
return getLocalObject('accounts')
}
export function getLocalTxHistory() {
return getLocalObject('txHistory')
}
export function setLocalTxHistory(tx) {
const newTx = tx
const txs = getLocalTxHistory()
if (txs) {
txs.push(newTx)
return localStorage.setItem('txHistory', JSON.stringify(txs))
}
return localStorage.setItem('txHistory', JSON.stringify([newTx]))
}
export async function connectLedger(transport = 'usb') {
const trans = await transport === 'usb' ? TransportWebUSB.create() : TransportWebBLE.create()
return new CosmosApp(trans)
}
export function operatorAddressToAccount(operAddress) {
const { prefix, data } = Bech32.decode(operAddress)
if (prefix === 'iva') { // handle special cases
return Bech32.encode('iaa', data)
}
if (prefix === 'crocncl') { // handle special cases
return Bech32.encode('cro', data)
}
return Bech32.encode(prefix.replace('valoper', ''), data)
}
// TODO, not tested
export function pubkeyToAccountAddress(pubkey, prefix) {
return Bech32.encode(prefix, pubkey, 40)
}
export function addressDecode(address) {
return Bech32.decode(address)
}
export function addressEnCode(prefix, pubkey) {
return Bech32.encode(prefix, pubkey)
}
export function getUserCurrency() {
const currency = localStorage.getItem('currency')
return currency || 'usd'
}
export function setUserCurrency(currency) {
localStorage.setItem('currency', currency)
}
export function chartColors() {
const colors = [
'#8A2BE2', '#9ACD32', '#000080', '#008080', '#DC143C',
'#7FFFD4', '#B8860B', '#EEE8AA', '#FFFAFA', '#FDF5E6',
'#C0C0C0', '#E6E6FA', '#FFFAF0', '#2E8B57', '#DCDCDC',
'#FF1493', '#4682B4', '#191970', '#FF8C00', '#FFFFE0',
'#696969', '#FFFACD', '#DEB887', '#4169E1', '#9932CC',
'#B0C4DE', '#556B2F', '#FFE4E1', '#F5FFFA', '#8FBC8F',
'#B22222', '#90EE90', '#FFFF00', '#4B0082', '#DB7093',
'#F8F8FF', '#006400', '#6610f2', '#FFA500', '#7FFF00',
'#87CEFA', '#5F9EA0', '#483D8B', '#CD5C5C', '#ADFF2F',
'#2F4F4F', '#00FF7F', '#FFF5EE', '#F4A460', '#808000',
'#000000', '#00FA9A', '#000000', '#EE82EE', '#F5DEB3',
'#0000FF', '#BA55D3', '#FFF0F5', '#F5F5DC', '#0000CD',
'#FFD700', '#708090', '#6B8E23', '#800000', '#7B68EE',
'#FFA07A', '#800080', '#B0E0E6', '#00FFFF', '#00BFFF',
'#7CFC00', '#778899', '#FF7F50', '#E0FFFF', '#6495ED',
'#008B8B', '#DDA0DD', '#CD853F', '#FFFFF0', '#98FB98',
'#9400D3', '#D2691E', '#FF0000', '#008000', '#00008B',
'#C71585', '#FFB6C1', '#8B4513', '#20c997', '#FAEBD7',
'#E9967A', '#FFEFD5', '#FFE4C4', '#D8BFD8', '#A52A2A',
'#8B0000', '#32CD32', '#BDB76B', '#FF0000', '#DAA520',
'#800000', '#9370DB', '#F08080', '#FAF0E6', '#FF6347',
'#FF4500', '#FFFF00', '#808080', '#00CED1', '#FFC0CB',
'#FF00FF', '#F0FFFF', '#A9A9A9', '#F0E68C', '#1E90FF',
'#FFDAB9', '#228B22', '#F0FFF0', '#66CDAA', '#ADD8E6',
'#DA70D6', '#A0522D', '#FFE4B5', '#48D1CC', '#D2B48C',
'#FFEBCD', '#8B008B', '#3CB371', '#87CEEB', '#6A5ACD',
'#FFDEAD', '#FF69B4', '#BC8F8F', '#D3D3D3', '#00FF00',
'#FAFAD2', '#AFEEEE', '#40E0D0', '#FFF8DC', '#20B2AA',
'#00FFFF', '#FA8072', '#F0F8FF']
return Object.values($themeColors).concat(colors)
}
export function extractAccountNumberAndSequence(ret) {
let account = ret.value
if (ret.value && ret.value.base_vesting_account) { // vesting account
account = ret.value.base_vesting_account?.base_account
} else if (ret.value && ret.value.base_account) { // evmos based account
account = ret.value.base_account
}
const accountNumber = account.account_number
const sequence = account?.sequence || 0
return {
accountNumber,
sequence,
}
}
export function getUserCurrencySign() {
let s = ''
switch (getUserCurrency()) {
case 'cny':
case 'jpy':
s = '¥'
break
case 'krw':
s = '₩'
break
case 'eur':
s = '€'
break
default:
s = '$'
}
return s
}
export function consensusPubkeyToHexAddress(consensusPubkey) {
let raw = null
if (typeof consensusPubkey === 'object') {
if (consensusPubkey.type === 'tendermint/PubKeySecp256k1') {
raw = new RIPEMD160().update(Buffer.from(sha256(fromBase64(consensusPubkey.value)))).digest('hex').toUpperCase()
return raw
}
raw = sha256(fromBase64(consensusPubkey.value))
} else {
raw = sha256(fromHex(toHex(Bech32.decode(consensusPubkey).data).toUpperCase().replace('1624DE6420', '')))
}
const address = toHex(raw).slice(0, 40).toUpperCase()
return address
}
function toSignAddress(addr) {
const { data } = addressDecode(addr)
return addressEnCode('cosmos', data)
}
function getHdPath(address) {
let hdPath = "m/44'/118/0'/0/0"
Object.values(getLocalAccounts()).forEach(item => {
const curr = item.address.find(i => i.addr === address)
if (curr && curr.hdpath) {
hdPath = curr.hdpath
}
})
// return [44, 118, 0, 0, 0]
// m/0'/1/2'/2/1000000000
return stringToPath(hdPath)
}
function getLedgerAppName(coinType) {
switch (coinType) {
case 60:
return 'Ethereum'
case 529:
return 'Secret'
case 852:
return 'Desmos'
case 118:
default:
return 'Cosmos'
}
}
export async function sign(device, chainId, signerAddress, messages, fee, memo, signerData) {
let transport
let signer
const hdpath = getHdPath(signerAddress)
const coinType = Number(hdpath[1])
const ledgerName = getLedgerAppName(coinType)
switch (device) {
case 'ledgerBle':
transport = await TransportWebBLE.create()
signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
break
case 'ledgerUSB':
transport = await TransportWebUSB.create()
signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
break
case 'keplr':
default:
if (!window.getOfflineSigner || !window.keplr) {
throw new Error('Please install keplr extension')
}
await window.keplr.enable(chainId)
// signer = window.getOfflineSigner(chainId)
signer = window.getOfflineSignerOnlyAmino(chainId)
}
// if (signer) return signAmino(signer, signerAddress, messages, fee, memo, signerData)
// Ensure the address has some tokens to spend
const client = await PingWalletClient.offline(signer)
// const client = await SigningStargateClient.offline(signer)
return client.signAmino2(device.startsWith('ledger') ? toSignAddress(signerAddress) : signerAddress, messages, fee, memo, signerData)
// return signDirect(signer, signerAddress, messages, fee, memo, signerData)
}
export async function getLedgerAddress(transport = 'blu', hdPath = "m/44'/118/0'/0/0") {
const trans = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create()
// extract Cointype from from HDPath
const coinType = Number(stringToPath(hdPath)[1])
const ledgerName = getLedgerAppName(coinType)
const signer = new LedgerSigner(trans, { hdPaths: [stringToPath(hdPath)], ledgerAppName: ledgerName })
return signer.getAccounts()
}
export function toDuration(value) {
return dayjs.duration(value).humanize()
}
// unit(y M d h m s ms)
export function timeIn(time, amount, unit = 's') {
const input = dayjs(time).add(amount, unit)
return dayjs().unix() > input.unix()
}
export function toDay(time, format = 'long') {
if (format === 'long') {
return dayjs(time).format('YYYY-MM-DD HH:mm')
}
if (format === 'date') {
return dayjs(time).format('YYYY-MM-DD')
}
if (format === 'time') {
return dayjs(time).format('HH:mm:ss')
}
if (format === 'from') {
return dayjs(time).fromNow()
}
if (format === 'to') {
return dayjs(time).toNow()
}
return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
}
export function percent(num) {
return parseFloat((num * 100).toFixed(2))
}
export function abbr(string, length = 6, suffix = '...') {
if (string && string.length > length) {
return `${string.substring(0, length)}${suffix}`
}
return string
}
export function abbrRight(string, length = 6, suffix = '...') {
if (string && string.length > length) {
return `${string.substring(string.length - length)}${suffix}`
}
return string
}
export function abbrMessage(msg) {
if (Array.isArray(msg)) {
const sum = msg.map(x => abbrMessage(x)).reduce((s, c) => {
const sh = s
if (sh[c]) {
sh[c] += 1
} else {
sh[c] = 1
}
return sh
}, {})
const output = []
Object.keys(sum).forEach(k => {
output.push(sum[k] > 1 ? `${k}×${sum[k]}` : k)
})
return output.join(', ')
}
if (msg['@type']) {
return msg['@type'].substring(msg['@type'].lastIndexOf('.') + 1).replace('Msg', '')
}
if (msg.typeUrl) {
return msg.typeUrl.substring(msg.typeUrl.lastIndexOf('.') + 1).replace('Msg', '')
}
return msg.type.substring(msg.type.lastIndexOf('/') + 1).replace('Msg', '')
}
export function abbrAddress(address, length = 10) {
return address.substring(0, length).concat('...', address.substring(address.length - length))
}
export function isStringArray(value) {
let is = false
if (Array.isArray(value)) {
is = value.findIndex(x => typeof x === 'string') > -1
}
return is
}
export function isToken(value) {
let is = false
if (Array.isArray(value)) {
is = value.findIndex(x => Object.keys(x).includes('denom')) > -1
} else {
is = Object.keys(value).includes('denom')
}
return is
}
export function formatTokenDenom(tokenDenom) {
if (tokenDenom && tokenDenom.code === undefined) {
let denom = tokenDenom.denom_trace ? tokenDenom.denom_trace.base_denom : tokenDenom
const config = Object.values(getLocalChains())
config.forEach(x => {
if (x.assets) {
const asset = x.assets.find(a => (a.base === denom))
if (asset) denom = asset.symbol
}
})
return denom.length > 10 ? `${denom.substring(0, 7).toUpperCase()}..${denom.substring(denom.length - 3)}` : denom.toUpperCase()
}
return ''
}
export function getUnitAmount(amount, tokenDenom) {
const denom = tokenDenom.denom_trace ? tokenDenom.denom_trace.base_denom : tokenDenom
let exp = String(denom).startsWith('gravity') ? 18 : 6
const config = Object.values(getLocalChains())
config.forEach(x => {
if (x.assets) {
const asset = x.assets.find(a => (a.base === denom))
if (asset) exp = asset.exponent
}
})
// eslint-disable-next-line no-undef
return String(BigInt(Number(amount) * (10 ** exp)))
}
export function numberWithCommas(x) {
const parts = x.toString().split('.')
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return parts.join('.')
}
export function formatTokenAmount(tokenAmount, decimals = 2, tokenDenom = 'uatom', format = true) {
const denom = tokenDenom.denom_trace ? tokenDenom.denom_trace.base_denom : tokenDenom
let amount = 0
let exp = String(denom).startsWith('gravity') ? 18 : 6
const config = Object.values(getLocalChains())
config.forEach(x => {
if (x.assets) {
const asset = x.assets.find(a => (a.base === denom))
if (asset) exp = asset.exponent
}
})
amount = Number(Number(tokenAmount)) / (10 ** exp)
if (amount > 10) {
if (format) { return numberWithCommas(parseFloat(amount.toFixed(decimals))) }
return parseFloat(amount.toFixed(decimals))
}
return parseFloat(amount.toFixed(exp))
}
export function isTestnet() {
return (window.location.hostname.startsWith('testnet')
|| window.location.search.indexOf('testnet') > -1)
}
export function formatToken(token, IBCDenom = {}, decimals = 2, withDenom = true) {
if (token) {
const denom = IBCDenom[token.denom] || token.denom
if (withDenom) {
return `${formatTokenAmount(token.amount, decimals, denom)} ${formatTokenDenom(denom)}`
}
return formatTokenAmount(token.amount, decimals, denom)
}
return token
}
const COUNT_ABBRS = ['', 'K', 'M', 'B', 't', 'q', 's', 'S', 'o', 'n', 'd', 'U', 'D', 'T', 'Qt', 'Qd', 'Sd', 'St']
export function formatNumber(count, withAbbr = false, decimals = 2) {
const i = count === 0 ? count : Math.floor(Math.log(count) / Math.log(1000))
let result = parseFloat((count / (1000 ** i)).toFixed(decimals))
if (withAbbr && COUNT_ABBRS[i]) {
result += `${COUNT_ABBRS[i]}`
}
return result
}
export function tokenFormatter(tokens, denoms = {}) {
if (Array.isArray(tokens)) {
return tokens.map(t => formatToken(t, denoms, 2)).join(', ')
}
return formatToken(tokens, denoms, 2)
}
export function getCachedValidators(chainName) {
const locals = localStorage.getItem(`validators-${chainName}`)
return locals
}
export function isHexAddress(v) {
const re = /^[A-Z\d]{40}$/
return re.test(v)
}
export function getStakingValidatorByHex(chainName, hex) {
const locals = localStorage.getItem(`validators-${chainName}`)
if (locals) {
const val = JSON.parse(locals).find(x => consensusPubkeyToHexAddress(x.consensus_pubkey) === hex)
if (val) {
return val.description.moniker
}
}
return abbr(hex)
}
export function getStakingValidatorByAccount(chainName, addr) {
const locals = localStorage.getItem(`validators-${chainName}`)
if (locals) {
const val = JSON.parse(locals).find(x => operatorAddressToAccount(x.operator_address) === addr)
if (val) {
return val.description.moniker
}
}
return addr
}
export function getStakingValidatorOperator(chainName, addr, length = -1) {
const locals = localStorage.getItem(`validators-${chainName}`)
if (locals) {
const val = JSON.parse(locals).find(x => x.operator_address === addr)
if (val) {
return val.description.moniker
}
}
if (length > 0) {
return addr.substring(addr.length - length)
}
return addr
}
export * from 'compare-versions'
export * from './data'
export class Data {
}