cosmos-multisig-ui/lib/displayHelpers.ts
Abel Fernández bd28c5de88
Use 5 chars for hash
Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com>
2023-04-13 16:29:55 +02:00

165 lines
5.2 KiB
TypeScript
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 { Coin } from "@cosmjs/amino";
import { sha512 } from "@cosmjs/crypto";
import { fromBase64, fromBech32, toBase64, toBech32 } from "@cosmjs/encoding";
import { Decimal } from "@cosmjs/math";
import { ChainInfo } from "../types";
function ellideMiddle(str: string, maxOutLen: number): string {
if (str.length <= maxOutLen) {
return str;
}
const ellide = "…";
const frontLen = Math.ceil((maxOutLen - ellide.length) / 2);
const tailLen = Math.floor((maxOutLen - ellide.length) / 2);
return str.slice(0, frontLen) + ellide + str.slice(str.length - tailLen, str.length);
}
// NARROW NO-BREAK SPACE (U+202F)
const thinSpace = "\u202F";
/**
* Takes a Coin (e.g. `{"amount": "1234", "denom": "uatom"}`) and converts it into a user-readable string.
*
* The chainInfo provided displayDenom/displayDenomExponent
* will be used if the denom matches the denom stored in the chainInfo object.
*
* A leading "u" is interpreted as µ (micro) and uxyz will be converted to XYZ
* for displaying.
*
* @param {object} coin (e.g. `{"amount": "1234", "denom": "uatom"}`)
* @param {object} chainInfo Provides information about a chain (e.g. node, prefix, denomination), object structure defined in '../context/AppReducer'.
* @return {string} The abbreviated string.
*/
const printableCoin = (coin: Coin, chainInfo: ChainInfo) => {
// null, undefined and this sort of things
if (!coin) return "";
// The display denom from configuration
if (coin.denom === chainInfo.denom) {
const exponent = Number(chainInfo.displayDenomExponent);
const value = Decimal.fromAtomics(coin.amount ?? "0", exponent).toString();
const ticker = chainInfo.displayDenom;
return value + thinSpace + ticker;
}
// Auto-convert leading "u"s
if (coin.denom.startsWith("u")) {
const value = Decimal.fromAtomics(coin.amount ?? "0", 6).toString();
const ticker = coin.denom.slice(1).toUpperCase();
return value + thinSpace + ticker;
}
// Ellide IBC tokens
if (coin.denom.startsWith("ibc/")) {
const value = coin.amount;
const hash = coin.denom.slice(4);
const ellidedHash = ellideMiddle(hash, 11);
const ticker = `ibc/${ellidedHash}`;
return value + thinSpace + ticker;
}
// Fallback to plain coin display
return coin.amount + thinSpace + coin.denom;
};
const printableCoins = (coins: Coin[], chainInfo: ChainInfo) => {
if (coins.length !== 1) {
throw new Error("Implementation only supports exactly one coin entry.");
}
return printableCoin(coins[0], chainInfo);
};
/**
* Generates an example address for the configured blockchain.
*
* `index` can be set to a small integer in order to get different addresses.
*/
function exampleAddress(index: number, chainAddressPrefix: string): string {
let data = fromBech32("cosmos1vqpjljwsynsn58dugz0w8ut7kun7t8ls2qkmsq").data;
for (let i = 0; i < index; ++i) {
data = sha512(data).slice(0, data.length); // hash one time and trim to original length
}
return toBech32(chainAddressPrefix, data);
}
/**
* Generates an example address for the configured blockchain.
*
* `index` can be set to a small integer in order to get different addresses.
*/
function exampleValidatorAddress(index: number, chainAddressPrefix: string): string {
let data = fromBech32("cosmosvaloper10v6wvdenee8r9l6wlsphcgur2ltl8ztkfrvj9a").data;
for (let i = 0; i < index; ++i) {
data = sha512(data).slice(0, data.length); // hash one time and trim to original length
}
const validatorPrefix = chainAddressPrefix + "valoper";
return toBech32(validatorPrefix, data);
}
/**
* Generates an example pubkey (secp256k1, compressed).
*
* `index` can be set to a small integer in order to get different addresses.
*
* Note: the keys are not necessarily valid (as in points on the chain) and should only be used
* as dummy data.
*/
function examplePubkey(index: number): string {
let data = fromBase64("Akd/qKMWdZXyiMnSu6aFLpQEGDO0ijyal9mXUIcVaPNX");
for (let i = 0; i < index; ++i) {
data = sha512(data).slice(0, data.length); // hash one time and trim to original length
data[0] = index % 2 ? 0x02 : 0x03; // pubkeys have to start with 0x02 or 0x03
}
return toBase64(data);
}
/**
* Returns an error message for invalid addresses.
*
* Returns null of there is no error.
*/
const checkAddress = (input: string, chainAddressPrefix: string) => {
if (!input) return "Empty";
let data;
let prefix;
try {
({ data, prefix } = fromBech32(input));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
return error.toString();
}
if (!prefix.startsWith(chainAddressPrefix)) {
return `Expected address prefix '${chainAddressPrefix}' but got '${prefix}'`;
}
if (data.length !== 20) {
return "Invalid address length in bech32 data. Must be 20 bytes.";
}
return null;
};
/**
* Returns a link to a transaction in an explorer if an explorer is configured
* for transactions. Returns null otherwise.
*/
const explorerLinkTx = (link: string, hash: string) => {
if (link && link.includes("${txHash}")) {
return link.replace("${txHash}", hash);
}
return null;
};
export {
ellideMiddle,
printableCoin,
printableCoins,
exampleAddress,
exampleValidatorAddress,
examplePubkey,
checkAddress,
explorerLinkTx,
};