- {stat.value}
+ {items.map((stat, i) => (
+
+
+ {formatStatValue(stat.value)}
{stat.label}
diff --git a/frontend/src/components/dashboard/ActivityHistory.tsx b/frontend/src/components/dashboard/ActivityHistory.tsx
index 31664345..f40b8e46 100644
--- a/frontend/src/components/dashboard/ActivityHistory.tsx
+++ b/frontend/src/components/dashboard/ActivityHistory.tsx
@@ -3,7 +3,7 @@
import React from "react";
import Link from "next/link";
import { BackendStreamEvent } from "@/lib/api-types";
-import { formatAmount } from "@/lib/amount";
+import { formatAmount } from "@/utils/amount";
import TransactionTracker from "@/components/TransactionTracker";
import { Download, ExternalLink, Clock } from "lucide-react";
import { Button } from "../ui/Button";
diff --git a/frontend/src/components/stream-creation/ScheduleStep.tsx b/frontend/src/components/stream-creation/ScheduleStep.tsx
index ade09dc7..9dee4cc5 100644
--- a/frontend/src/components/stream-creation/ScheduleStep.tsx
+++ b/frontend/src/components/stream-creation/ScheduleStep.tsx
@@ -1,6 +1,6 @@
"use client";
import React, { useMemo, useRef, useEffect } from "react";
-import { hasValidPrecision } from "@/lib/amount";
+import { hasValidPrecision } from "@/utils/amount";
import { SECONDS_PER_UNIT, DurationUnit } from "@/lib/soroban";
interface ScheduleStepProps {
diff --git a/frontend/src/components/stream-creation/StreamCreationWizard.tsx b/frontend/src/components/stream-creation/StreamCreationWizard.tsx
index f16e9d94..50aee5b6 100644
--- a/frontend/src/components/stream-creation/StreamCreationWizard.tsx
+++ b/frontend/src/components/stream-creation/StreamCreationWizard.tsx
@@ -1,6 +1,6 @@
"use client";
import React, { useState } from "react";
-import { hasValidPrecision } from "@/lib/amount";
+import { hasValidPrecision } from "@/utils/amount";
import { useModalDialog } from "@/hooks/useModalDialog";
import { logger } from "@/lib/logger";
import { Stepper } from "../ui/Stepper";
diff --git a/frontend/src/lib/amount.ts b/frontend/src/lib/amount.ts
deleted file mode 100644
index efc22c19..00000000
--- a/frontend/src/lib/amount.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-/**
- * Shared utilities for formatting and parsing token amounts
- * Handles conversion between raw on-chain amounts (i128) and display values
- */
-
-/**
- * Format raw amount (bigint) to display string with proper decimal places
- * @param raw - Raw amount as bigint
- * @param decimals - Number of decimal places for the token
- * @returns Formatted string
- */
-export function formatAmount(raw: bigint, decimals: number): string {
- if (raw === 0n) return '0';
-
- const divisor = 10n ** BigInt(decimals);
- const whole = raw / divisor;
- const fractional = raw % divisor;
-
- if (fractional === 0n) {
- return whole.toString();
- }
-
- // Pad fractional part with leading zeros
- const fractionalStr = fractional.toString().padStart(decimals, '0');
-
- // Remove trailing zeros
- const trimmedFractional = fractionalStr.replace(/0+$/, '');
-
- return `${whole}.${trimmedFractional}`;
-}
-
-/**
- * Parse display string back to raw amount (bigint)
- * @param display - Display string (e.g., "1.234")
- * @param decimals - Number of decimal places for the token
- * @returns Raw amount as bigint
- */
-export function parseAmount(display: string, decimals: number): bigint {
- if (!display || display.trim() === '') return 0n;
-
- const cleanDisplay = display.trim();
- const divisor = 10n ** BigInt(decimals);
-
- if (cleanDisplay.includes('.')) {
- const [wholePart, fractionalPart] = cleanDisplay.split('.');
- const whole = BigInt(wholePart || '0');
-
- // Handle fractional part - pad or truncate to correct length
- let fractional = fractionalPart || '';
- if (fractional.length > decimals) {
- // Truncate if too long
- fractional = fractional.slice(0, decimals);
- } else {
- // Pad with zeros if too short
- fractional = fractional.padEnd(decimals, '0');
- }
-
- const fractionalBig = BigInt(fractional || '0');
- return whole * divisor + fractionalBig;
- } else {
- return BigInt(cleanDisplay) * divisor;
- }
-}
-
-/**
- * Format rate per second to human-readable string
- * @param ratePerSec - Rate per second as bigint
- * @param decimals - Number of decimal places for the token
- * @param symbol - Token symbol (optional)
- * @returns Formatted rate string
- */
-export function formatRate(ratePerSec: bigint, decimals: number, symbol = ''): string {
- if (ratePerSec === 0n) return '0';
-
- const ratePerSecond = formatAmount(ratePerSec, decimals);
- const ratePerDay = formatAmount(ratePerSec * 86400n, decimals); // 86400 seconds in a day
-
- const symbolStr = symbol ? ` ${symbol}` : '';
- return `${ratePerSecond}${symbolStr}/sec (${ratePerDay}${symbolStr}/day)`;
-}
-
-/**
- * Check if input string has valid precision for the given decimals
- * @param input - Input string to validate
- * @param decimals - Maximum allowed decimal places
- * @returns True if valid precision
- */
-export function hasValidPrecision(input: string, decimals: number): boolean {
- if (!input || input.trim() === '') return true; // Empty is valid (will be parsed as 0)
-
- const cleanInput = input.trim();
-
- // Check if it's a valid number format
- if (!/^\d*\.?\d*$/.test(cleanInput)) return false;
-
- if (cleanInput.includes('.')) {
- const fractionalPart = cleanInput.split('.')[1] ?? '';
- return fractionalPart.length <= decimals;
- }
-
- return true;
-}
-
-/**
- * Convert value to stroops (smallest unit, 7 decimal places for XLM)
- * @param value - String value in XLM
- * @returns Value in stroops as bigint
- */
-export function toStroops(value: string): bigint {
- return parseAmount(value, 7); // XLM uses 7 decimal places
-}
-
-/**
- * Convert stroops back to XLM string
- * @param stroops - Value in stroops as bigint
- * @returns XLM string
- */
-export function fromStroops(stroops: bigint): string {
- return formatAmount(stroops, 7);
-}
-
-/**
- * Truncate amount to specified decimal places without rounding
- * @param amount - Amount as bigint
- * @param decimals - Token decimals
- * @param maxDisplayDecimals - Maximum decimal places to display
- * @returns Truncated string
- */
-export function truncateAmount(amount: bigint, decimals: number, maxDisplayDecimals: number): string {
- if (amount === 0n) return '0';
-
- const divisor = 10n ** BigInt(decimals);
- const whole = amount / divisor;
- const fractional = amount % divisor;
-
- if (fractional === 0n) {
- return whole.toString();
- }
-
- // Convert fractional to string and truncate
- const fractionalStr = fractional.toString().padStart(decimals, '0');
- const truncatedFractional = fractionalStr.slice(0, maxDisplayDecimals);
-
- // Remove trailing zeros from truncated part
- const trimmedFractional = truncatedFractional.replace(/0+$/, '');
-
- if (trimmedFractional === '') {
- return whole.toString();
- }
-
- return `${whole}.${trimmedFractional}`;
-}
-
-/**
- * Format amount with compact notation (K, M, B) for large numbers
- * @param amount - Amount as bigint
- * @param decimals - Token decimals
- * @returns Compact formatted string
- */
-export function formatCompactAmount(amount: bigint, decimals: number): string {
- const displayAmount = formatAmount(amount, decimals);
- const num = parseFloat(displayAmount);
-
- if (num === 0) return '0';
- if (num < 1000) return displayAmount;
- if (num < 1000000) return `${(num / 1000).toFixed(1)}K`;
- if (num < 1000000000) return `${(num / 1000000).toFixed(1)}M`;
- return `${(num / 1000000000).toFixed(1)}B`;
-}
diff --git a/frontend/src/utils/amount.ts b/frontend/src/utils/amount.ts
index 73675ba4..5a461671 100644
--- a/frontend/src/utils/amount.ts
+++ b/frontend/src/utils/amount.ts
@@ -37,9 +37,11 @@ export function streamProgressPercent(withdrawn: bigint, deposited: bigint): num
/**
* Alias for formatAmount - convert raw on-chain amount to human-readable string
+ * @param amount - Raw amount as bigint
+ * @param decimals - Token decimals; defaults to 7 (XLM stroops)
* @deprecated Use formatAmount instead
*/
-export function fromStroops(amount: bigint, decimals: number): string {
+export function fromStroops(amount: bigint, decimals = 7): string {
return formatAmount(amount, decimals);
}
@@ -72,9 +74,11 @@ export function parseAmount(display: string, decimals: number): bigint {
/**
* Alias for parseAmount - convert human-readable to raw on-chain amount
+ * @param amount - Display string (e.g., "1.234")
+ * @param decimals - Token decimals; defaults to 7 (XLM stroops)
* @deprecated Use parseAmount instead
*/
-export function toStroops(amount: string, decimals: number): bigint {
+export function toStroops(amount: string, decimals = 7): bigint {
return parseAmount(amount, decimals);
}
@@ -111,6 +115,51 @@ export function formatStreamRate(
return formatRate(ratePerSecond, decimals, tokenSymbol);
}
+/**
+ * Truncate amount to specified decimal places without rounding
+ * @param amount - Amount as bigint
+ * @param decimals - Token decimals
+ * @param maxDisplayDecimals - Maximum decimal places to display
+ * @returns Truncated string
+ */
+export function truncateAmount(
+ amount: bigint,
+ decimals: number,
+ maxDisplayDecimals: number
+): string {
+ if (amount === 0n) return "0";
+
+ const factor = 10n ** BigInt(decimals);
+ const integerPart = amount / factor;
+ const fractionalPart = amount % factor;
+
+ if (fractionalPart === 0n) return integerPart.toString();
+
+ const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
+ const truncatedFractional = fractionalStr.slice(0, maxDisplayDecimals);
+ const trimmedFractional = truncatedFractional.replace(/0+$/, "");
+
+ if (!trimmedFractional) return integerPart.toString();
+ return `${integerPart}.${trimmedFractional}`;
+}
+
+/**
+ * Format amount with compact notation (K, M, B) for large numbers
+ * @param amount - Amount as bigint
+ * @param decimals - Token decimals
+ * @returns Compact formatted string e.g., "1.5K"
+ */
+export function formatCompactAmount(amount: bigint, decimals: number): string {
+ const displayAmount = formatAmount(amount, decimals);
+ const num = parseFloat(displayAmount);
+
+ if (num === 0) return "0";
+ if (num < 1_000) return displayAmount;
+ if (num < 1_000_000) return `${(num / 1_000).toFixed(1)}K`;
+ if (num < 1_000_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
+ return `${(num / 1_000_000_000).toFixed(1)}B`;
+}
+
/**
* Check if input string has valid precision for the given decimals
* @param input - Input string to validate