Files
dchain/client-app/lib/utils.ts
vsecoder 7e7393e4f8 chore: initial commit for v0.0.1
DChain single-node blockchain + React Native messenger client.

Core:
- PBFT consensus with multi-sig validator admission + equivocation slashing
- BadgerDB + schema migration scaffold (CurrentSchemaVersion=0)
- libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1)
- Native Go contracts (username_registry) alongside WASM (wazero)
- WebSocket gateway with topic-based fanout + Ed25519-nonce auth
- Relay mailbox with NaCl envelope encryption (X25519 + Ed25519)
- Prometheus /metrics, per-IP rate limit, body-size cap

Deployment:
- Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus
- 3-node dev compose (docker-compose.yml) with mocked internet topology
- 3-validator prod compose (deploy/prod/) for federation
- Auto-update from Gitea via /api/update-check + systemd timer
- Build-time version injection (ldflags → node --version)
- UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER)

Client (client-app/):
- Expo / React Native / NativeWind
- E2E NaCl encryption, typing indicator, contact requests
- Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch

Documentation:
- README.md, CHANGELOG.md, CONTEXT.md
- deploy/single/README.md with 6 operator scenarios
- deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design
- docs/contracts/*.md per contract
2026-04-17 14:16:44 +03:00

36 lines
1.3 KiB
TypeScript

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/** Format µT amount to human-readable string */
export function formatAmount(microTokens: number | undefined | null): string {
if (microTokens == null) return '—';
if (microTokens >= 1_000_000) return `${(microTokens / 1_000_000).toFixed(2)} T`;
if (microTokens >= 1_000) return `${(microTokens / 1_000).toFixed(1)} mT`;
return `${microTokens} µT`;
}
/** Format unix seconds to relative time */
export function relativeTime(unixSeconds: number | undefined | null): string {
if (!unixSeconds) return '';
const diff = Date.now() / 1000 - unixSeconds;
if (diff < 60) return 'just now';
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
return new Date(unixSeconds * 1000).toLocaleDateString();
}
/** Format unix seconds to HH:MM */
export function formatTime(unixSeconds: number | undefined | null): string {
if (!unixSeconds) return '';
return new Date(unixSeconds * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
/** Generate a random nonce string */
export function randomId(): string {
return Math.random().toString(36).slice(2) + Date.now().toString(36);
}