From 39bcc4df1f11c7bb22a4c06a196c2f6e1678b025 Mon Sep 17 00:00:00 2001 From: Theraxia Date: Tue, 2 Jun 2026 10:43:53 +0000 Subject: [PATCH] feat: implement task functionality with supporting updates and validations --- src/routes/account.js | 13 ++-- src/routes/asset.js | 7 +- src/utils/formatBalance.js | 33 +++++++++ tests/utils.formatBalance.test.js | 108 ++++++++++++++++++++++++++++++ verify-formatBalance.js | 49 ++++++++++++++ 5 files changed, 201 insertions(+), 9 deletions(-) create mode 100644 src/utils/formatBalance.js create mode 100644 tests/utils.formatBalance.test.js create mode 100644 verify-formatBalance.js diff --git a/src/routes/account.js b/src/routes/account.js index e27e71d..5525479 100644 --- a/src/routes/account.js +++ b/src/routes/account.js @@ -3,6 +3,7 @@ const router = express.Router(); const { server, fetchAccountCreation } = require("../config/stellar"); const { success } = require("../utils/response"); const { getAssetMetadataFromToml } = require("../utils/tomlResolver"); +const { formatBalance } = require("../utils/formatBalance"); const { Asset } = require("@stellar/stellar-sdk"); const { validateAccountId, @@ -29,19 +30,19 @@ function formatAccountBalances(account) { assetCode: b.asset_code, assetIssuer: b.asset_issuer, assetType: b.asset_type, - balance: b.balance, + balance: formatBalance(b.balance), limit: b.limit, - buyingLiabilities: b.buying_liabilities, - sellingLiabilities: b.selling_liabilities, + buyingLiabilities: formatBalance(b.buying_liabilities), + sellingLiabilities: formatBalance(b.selling_liabilities), isAuthorized: b.is_authorized, isClawbackEnabled: b.is_clawback_enabled, })); return { xlm: { - balance: xlmBalance ? xlmBalance.balance : "0.0000000", - buyingLiabilities: xlmBalance ? xlmBalance.buying_liabilities : "0", - sellingLiabilities: xlmBalance ? xlmBalance.selling_liabilities : "0", + balance: formatBalance(xlmBalance ? xlmBalance.balance : "0.0000000"), + buyingLiabilities: formatBalance(xlmBalance ? xlmBalance.buying_liabilities : "0"), + sellingLiabilities: formatBalance(xlmBalance ? xlmBalance.selling_liabilities : "0"), }, assets, }; diff --git a/src/routes/asset.js b/src/routes/asset.js index 30f5178..237b8d4 100644 --- a/src/routes/asset.js +++ b/src/routes/asset.js @@ -3,6 +3,7 @@ const router = express.Router(); const { Asset } = require("@stellar/stellar-sdk"); const { server } = require("../config/stellar"); const { success } = require("../utils/response"); +const { formatBalance } = require("../utils/formatBalance"); const { assetHoldersRateLimiter } = require("../middleware/rateLimiter"); const { validateAccountId, @@ -22,10 +23,10 @@ function formatAssetHolder(account, assetCode, issuer) { return { accountId: account.id || account.account_id, - balance: balance ? balance.balance : "0.0000000", + balance: formatBalance(balance ? balance.balance : "0.0000000"), limit: balance ? balance.limit : null, - buyingLiabilities: balance ? balance.buying_liabilities : "0.0000000", - sellingLiabilities: balance ? balance.selling_liabilities : "0.0000000", + buyingLiabilities: formatBalance(balance ? balance.buying_liabilities : "0.0000000"), + sellingLiabilities: formatBalance(balance ? balance.selling_liabilities : "0.0000000"), isAuthorized: balance ? balance.is_authorized : null, isAuthorizedToMaintainLiabilities: balance ? balance.is_authorized_to_maintain_liabilities diff --git a/src/utils/formatBalance.js b/src/utils/formatBalance.js new file mode 100644 index 0000000..a3d57c8 --- /dev/null +++ b/src/utils/formatBalance.js @@ -0,0 +1,33 @@ +/** + * Formats a raw Stellar balance string into a human-friendly display string + * with thousand separators (commas). + * + * @param {string} balance - The raw balance string (e.g., "10000.1234567") + * @returns {string} The formatted balance string with thousand separators (e.g., "10,000.1234567") + * + * @example + * formatBalance("10000.1234567") // "10,000.1234567" + * formatBalance("0.0000100") // "0.0000100" + * formatBalance("1234567.89") // "1,234,567.89" + * formatBalance("100") // "100" + */ +function formatBalance(balance) { + if (!balance || typeof balance !== "string") { + return balance; + } + + // Split the balance into integer and decimal parts + const parts = balance.split("."); + + // Add thousand separators to the integer part + const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); + + // Reconstruct: if there's a decimal part, add it back; otherwise just return the integer part + if (parts.length > 1) { + return integerPart + "." + parts[1]; + } + + return integerPart; +} + +module.exports = { formatBalance }; diff --git a/tests/utils.formatBalance.test.js b/tests/utils.formatBalance.test.js new file mode 100644 index 0000000..2255120 --- /dev/null +++ b/tests/utils.formatBalance.test.js @@ -0,0 +1,108 @@ +const { formatBalance } = require("../src/utils/formatBalance"); + +describe("formatBalance", () => { + describe("Basic formatting with thousand separators", () => { + it('should format "10000.1234567" as "10,000.1234567"', () => { + expect(formatBalance("10000.1234567")).toBe("10,000.1234567"); + }); + + it('should format "1234567.89" as "1,234,567.89"', () => { + expect(formatBalance("1234567.89")).toBe("1,234,567.89"); + }); + + it('should format "100" as "100" (no formatting needed)', () => { + expect(formatBalance("100")).toBe("100"); + }); + + it('should format "999" as "999" (under 1000)', () => { + expect(formatBalance("999")).toBe("999"); + }); + + it('should format "1000" as "1,000"', () => { + expect(formatBalance("1000")).toBe("1,000"); + }); + }); + + describe("Small balance handling (no trailing zero stripping)", () => { + it('should return "0.0000100" without stripping trailing zeros', () => { + expect(formatBalance("0.0000100")).toBe("0.0000100"); + }); + + it('should return "0.0000001" without stripping trailing zeros', () => { + expect(formatBalance("0.0000001")).toBe("0.0000001"); + }); + + it('should format "0.10" as "0.10"', () => { + expect(formatBalance("0.10")).toBe("0.10"); + }); + + it('should return "0" as "0"', () => { + expect(formatBalance("0")).toBe("0"); + }); + }); + + describe("Large balance formatting", () => { + it('should format "1000000.5555" as "1,000,000.5555"', () => { + expect(formatBalance("1000000.5555")).toBe("1,000,000.5555"); + }); + + it('should format "1000000000.1" as "1,000,000,000.1"', () => { + expect(formatBalance("1000000000.1")).toBe("1,000,000,000.1"); + }); + + it('should format "999999999.999999" as "999,999,999.999999"', () => { + expect(formatBalance("999999999.999999")).toBe("999,999,999.999999"); + }); + }); + + describe("Edge cases", () => { + it("should handle null gracefully", () => { + expect(formatBalance(null)).toBe(null); + }); + + it("should handle undefined gracefully", () => { + expect(formatBalance(undefined)).toBe(undefined); + }); + + it("should handle empty string", () => { + expect(formatBalance("")).toBe(""); + }); + + it("should handle non-string input", () => { + const num = 123; + expect(formatBalance(num)).toBe(num); + }); + }); + + describe("Stellar-specific balance values", () => { + it('should format XLM default balance "0.0000000" as "0.0000000"', () => { + expect(formatBalance("0.0000000")).toBe("0.0000000"); + }); + + it('should format typical XLM balance "50000.0000000" as "50,000.0000000"', () => { + expect(formatBalance("50000.0000000")).toBe("50,000.0000000"); + }); + + it('should format typical asset balance "1234567.123456" as "1,234,567.123456"', () => { + expect(formatBalance("1234567.123456")).toBe("1,234,567.123456"); + }); + + it('should format liability value "10000" as "10,000"', () => { + expect(formatBalance("10000")).toBe("10,000"); + }); + }); + + describe("Decimal precision preservation", () => { + it("should preserve all decimal places", () => { + expect(formatBalance("1000.123456789")).toBe("1,000.123456789"); + }); + + it("should preserve many decimal places for small amounts", () => { + expect(formatBalance("0.0000000000000001")).toBe("0.0000000000000001"); + }); + + it("should format integer without decimal point", () => { + expect(formatBalance("50000")).toBe("50,000"); + }); + }); +}); diff --git a/verify-formatBalance.js b/verify-formatBalance.js new file mode 100644 index 0000000..c01072d --- /dev/null +++ b/verify-formatBalance.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node +const { formatBalance } = require("./src/utils/formatBalance"); + +console.log("Testing formatBalance utility...\n"); + +const testCases = [ + { input: "10000.1234567", expected: "10,000.1234567", description: "Basic formatting with decimals" }, + { input: "0.0000100", expected: "0.0000100", description: "Small balance - no trailing zero stripping" }, + { input: "1234567.89", expected: "1,234,567.89", description: "Large balance" }, + { input: "100", expected: "100", description: "Small number under 1000" }, + { input: "1000", expected: "1,000", description: "Exactly 1000" }, + { input: "50000.0000000", expected: "50,000.0000000", description: "XLM balance" }, + { input: "0", expected: "0", description: "Zero" }, + { input: "999999999.999999", expected: "999,999,999.999999", description: "Large balance with decimals" }, + { input: null, expected: null, description: "Null input" }, + { input: undefined, expected: undefined, description: "Undefined input" }, +]; + +let passed = 0; +let failed = 0; + +testCases.forEach(({ input, expected, description }) => { + const result = formatBalance(input); + const isPass = result === expected; + + if (isPass) { + console.log(`✓ ${description}`); + console.log(` Input: ${JSON.stringify(input)} → Output: ${JSON.stringify(result)}`); + passed++; + } else { + console.log(`✗ ${description}`); + console.log(` Input: ${JSON.stringify(input)}`); + console.log(` Expected: ${JSON.stringify(expected)}`); + console.log(` Got: ${JSON.stringify(result)}`); + failed++; + } + console.log(); +}); + +console.log(`\n========================================`); +console.log(`Results: ${passed} passed, ${failed} failed`); +console.log(`========================================\n`); + +if (failed > 0) { + process.exit(1); +} + +console.log("✅ All tests passed!"); +process.exit(0);