diff --git a/client/src/helpers/stardust/transactionsHelper.spec.ts b/client/src/helpers/stardust/convertToBigEndian.spec.ts similarity index 53% rename from client/src/helpers/stardust/transactionsHelper.spec.ts rename to client/src/helpers/stardust/convertToBigEndian.spec.ts index 311fac658..f704277e5 100644 --- a/client/src/helpers/stardust/transactionsHelper.spec.ts +++ b/client/src/helpers/stardust/convertToBigEndian.spec.ts @@ -1,23 +1,23 @@ -import { TransactionsHelper } from "./transactionsHelper"; +import { Converter } from "./convertUtils"; -describe("Transaction helper", () => { +describe("convertToBigEndian", () => { it("should convert little endian encoded hex to big endian", () => { const outputIndex = "0200"; - expect(TransactionsHelper.convertToBigEndian(outputIndex)).toBe("0002"); + expect(Converter.convertToBigEndian(outputIndex)).toBe("0002"); }); it("should convert to big endian when the index length is 6", () => { const outputIndex = "9F8601"; - expect(TransactionsHelper.convertToBigEndian(outputIndex)).toBe("01869F"); + expect(Converter.convertToBigEndian(outputIndex)).toBe("01869F"); }); it("should convert to big endian when the index length is 8", () => { const outputIndex = "9F860101"; - expect(TransactionsHelper.convertToBigEndian(outputIndex)).toBe("0101869F"); + expect(Converter.convertToBigEndian(outputIndex)).toBe("0101869F"); }); it("should convert to big endian if the index has odd length", () => { const outputIndex = "9F86010"; - expect(TransactionsHelper.convertToBigEndian(outputIndex)).toBe("1060F809"); + expect(Converter.convertToBigEndian(outputIndex)).toBe("1060F809"); }); }); diff --git a/client/src/helpers/stardust/convertUtils.ts b/client/src/helpers/stardust/convertUtils.ts index c086f459f..650aa8eaf 100644 --- a/client/src/helpers/stardust/convertUtils.ts +++ b/client/src/helpers/stardust/convertUtils.ts @@ -241,6 +241,29 @@ export class Converter { return Base64.decode(base64); } + /** + * Convert little endian to big endian. + * @param index Output index in little endian format. + * @returns Output index in big endian format. + */ + public static convertToBigEndian(index: string) { + const bigEndian = []; + let hexLength = index.length; + + if (hexLength % 2 !== 0) { + index = "0".concat(index); + hexLength = index.length; + } + + while (hexLength >= 0) { + const slicedBits = index.slice(hexLength - 2, hexLength); + bigEndian.push(slicedBits); + hexLength -= 2; + } + + return bigEndian.join(""); + } + /** * Build the static lookup tables. * @internal diff --git a/client/src/helpers/stardust/transactionsHelper.ts b/client/src/helpers/stardust/transactionsHelper.ts index b5f03283b..04d07098c 100644 --- a/client/src/helpers/stardust/transactionsHelper.ts +++ b/client/src/helpers/stardust/transactionsHelper.ts @@ -10,6 +10,7 @@ import { TransactionPayload, TreasuryOutput, Unlock, UnlockCondition, UnlockConditionType, UnlockType, Utils, UTXOInput } from "@iota/sdk-wasm/web"; import bigInt from "big-integer"; +import { Converter } from "./convertUtils"; import { HexHelper } from "./hexHelper"; import { IBech32AddressDetails } from "../../models/api/IBech32AddressDetails"; import { IInput } from "../../models/api/stardust/IInput"; @@ -187,36 +188,13 @@ export class TransactionsHelper { items.sort((a: any, b: any) => { const firstIndex: string = a.id ? a.id.slice(-4) : a.outputId.slice(-4); const secondIndex: string = b.id ? b.id.slice(-4) : b.outputId.slice(-4); - const firstFormattedIndex = this.convertToBigEndian(firstIndex); - const secondFormattedIndex = this.convertToBigEndian(secondIndex); + const firstFormattedIndex = Converter.convertToBigEndian(firstIndex); + const secondFormattedIndex = Converter.convertToBigEndian(secondIndex); return Number.parseInt(firstFormattedIndex, 16) - Number.parseInt(secondFormattedIndex, 16); }); } - /** - * Convert little endian to big endian. - * @param index Output index in little endian format. - * @returns Output index in big endian format. - */ - public static convertToBigEndian(index: string) { - const bigEndian = []; - let hexLength = index.length; - - if (hexLength % 2 !== 0) { - index = "0".concat(index); - hexLength = index.length; - } - - while (hexLength >= 0) { - const slicedBits = index.slice(hexLength - 2, hexLength); - bigEndian.push(slicedBits); - hexLength -= 2; - } - - return bigEndian.join(""); - } - /** * Compute BLAKE2b-256 hash for alias. * @param aliasId Alias id. diff --git a/client/src/helpers/stardust/valueFormatHelper.spec.ts b/client/src/helpers/stardust/valueFormatHelper.spec.ts new file mode 100644 index 000000000..f8f7ab2d0 --- /dev/null +++ b/client/src/helpers/stardust/valueFormatHelper.spec.ts @@ -0,0 +1,65 @@ +import { formatAmount } from "./valueFormatHelper"; + +const tokenInfo = { + "name": "IOTA", + "tickerSymbol": "IOTA", + "unit": "IOTA", + "subunit": "micro", + "decimals": 6, + "useMetricPrefix": false +}; + +describe("formatAmount", () => { + it("should format 1 subunit properly", () => { + expect(formatAmount(1, tokenInfo)).toBe("0.000001 IOTA"); + }); + + it("should format 10 subunit properly", () => { + expect(formatAmount(10, tokenInfo)).toBe("0.00001 IOTA"); + }); + + it("should format 100 subunit properly", () => { + expect(formatAmount(100, tokenInfo)).toBe("0.0001 IOTA"); + }); + + it("should format 1000 subunit properly", () => { + expect(formatAmount(1000, tokenInfo)).toBe("0.001 IOTA"); + }); + + it("should format 10000 subunit properly", () => { + expect(formatAmount(10000, tokenInfo)).toBe("0.01 IOTA"); + }); + + it("should format 100000 subunit properly", () => { + expect(formatAmount(100000, tokenInfo)).toBe("0.1 IOTA"); + }); + + it("should format 1 unit properly", () => { + expect(formatAmount(1000000, tokenInfo)).toBe("1 IOTA"); + }); + + it("should format 1 unit with fraction properly", () => { + expect(formatAmount(1234567, tokenInfo)).toBe("1.23 IOTA"); + }); + + it("should handle edge case from issue 'explorer/issues/822'", () => { + expect(formatAmount(1140000, tokenInfo)).toBe("1.14 IOTA"); + }); + + it("should honour precision 3", () => { + expect(formatAmount(9999, tokenInfo, false, 3)).toBe("0.009 IOTA"); + }); + + it("should honour precision 4", () => { + expect(formatAmount(9999, tokenInfo, false, 4)).toBe("0.0099 IOTA"); + }); + + it("should honour precision 0", () => { + expect(formatAmount(1450896407249092, tokenInfo, false, 0)).toBe("1450896407 IOTA"); + }); + + it("should format big values properly", () => { + expect(formatAmount(1450896407249092, tokenInfo)).toBe("1450896407.24 IOTA"); + }); +}); + diff --git a/client/src/helpers/stardust/valueFormatHelper.tsx b/client/src/helpers/stardust/valueFormatHelper.tsx index 83a99c4c5..aafbbda6c 100644 --- a/client/src/helpers/stardust/valueFormatHelper.tsx +++ b/client/src/helpers/stardust/valueFormatHelper.tsx @@ -2,6 +2,7 @@ import { UnitsHelper } from "@iota/iota.js"; import { INodeInfoBaseToken } from "@iota/sdk-wasm/web"; import React from "react"; import Tooltip from "../../app/components/Tooltip"; + /** * The id of the Genesis block. */ @@ -26,12 +27,11 @@ export function formatAmount( } const baseTokenValue = value / Math.pow(10, tokenInfo.decimals); - // useMetricPrefix is broken cause it passes a float value to formatBest - const amount = tokenInfo.useMetricPrefix - ? UnitsHelper.formatBest(baseTokenValue) - : `${toFixedNoRound(baseTokenValue, decimalPlaces)} `; + const formattedAmount = toFixedNoRound(baseTokenValue, decimalPlaces); - return `${amount}${tokenInfo.unit}`; + // useMetricPrefix is broken cause it passes a float value to formatBest + const amount = tokenInfo.useMetricPrefix ? UnitsHelper.formatBest(baseTokenValue) : `${formattedAmount} `; + return `${amount}${tokenInfo.unit}`; } /** @@ -51,9 +51,25 @@ export function formatNumberWithCommas( * @param precision The decimal places to show. * @returns The formatted amount. */ -function toFixedNoRound(value: number, precision: number = 2) { - const factor = Math.pow(10, precision); - return Math.floor(value * factor) / factor; +function toFixedNoRound(value: number, precision: number = 2): string { + const valueString = `${value}`; + const [integer, fraction] = valueString.split("."); + if (!fraction) { + return valueString; + } + + if (!precision) { + return integer; + } + + const truncatedFraction = fraction.slice(0, precision); + + // avoid 0.00 case + if (!Number(truncatedFraction)) { + return `${integer}.${fraction}`; + } + + return `${integer}.${truncatedFraction}`; } /**