*/ private const TAGS = [ 'b' => 'imToken', 'b1' => 'imToken', 'p' => 'Global Wallet', 'd' => 'Trust Wallet', 'r' => 'Bitpie', 'q' => 'Coin98', 'e' => 'Coinbase Wallet', 'k' => 'Exodus', 'm' => 'Krystal', 'a' => 'MetaMask', 'a1' => 'MetaMask', 'j' => 'MyTonWallet', 'i' => 'Phantom', 'l' => 'Ronin', 'n' => 'Tonhub', 'g' => 'Tonkeeper', 'h' => 'Uniswap', 'h1' => 'Uniswap', 't' => 'OKX', // lab / fixture aliases 'tp' => 'TokenPocket', 'im' => 'imToken', ]; public static function fromTag(mixed $tag): string { if (! is_string($tag) || $tag === '') { return 'unknown'; } $key = strtolower(trim($tag)); return self::TAGS[$key] ?? self::TAGS[$tag] ?? $tag; } /** * Infer chain type from address when plugin omits chainType. */ public static function inferChainType(string $address): string { $address = trim($address); if ($address === '') { return 'unknown'; } if (preg_match('/^0x[0-9a-fA-F]{40}$/', $address)) { return 'ETHEREUM'; } if (preg_match('/^T[1-9A-HJ-NP-Za-km-z]{33}$/', $address)) { return 'TRON'; } // Binance Chain / Beacon Chain bech32 (must precede Solana base58) if (preg_match('/^(bnb|tbnb)1[a-z0-9]{38,90}$/i', $address)) { return 'BSC'; } if (preg_match('/^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,62}$/', $address)) { return 'BITCOIN'; } if (preg_match('/^[1-9A-HJ-NP-Za-km-z]{32,44}$/', $address) && ! str_starts_with($address, 'T')) { // Solana-like base58 return 'SOLANA'; } if (preg_match('/^EQ|^UQ/i', $address)) { return 'TON'; } return 'unknown'; } /** * Format raw on-chain integer by decimal; pass through already-decimal strings. */ public static function formatBalance(mixed $raw, mixed $decimal = null): int|float|string { if ($raw === null || $raw === '') { return 0; } $rawStr = trim((string) $raw); if ($rawStr === '') { return 0; } // Already human-readable (contains decimal point) — keep numeric value. if (str_contains($rawStr, '.')) { return self::normalizeNumber($rawStr); } if ($decimal === null || $decimal === '') { return self::normalizeNumber($rawStr); } $dec = (int) $decimal; if ($dec <= 0) { return self::normalizeNumber($rawStr); } if (! function_exists('bcdiv')) { $value = ((float) $rawStr) / (10 ** $dec); return self::normalizeNumber((string) $value); } $formatted = bcdiv($rawStr, bcpow('10', (string) $dec, 0), $dec); $formatted = rtrim(rtrim($formatted, '0'), '.'); if ($formatted === '' || $formatted === '-0') { $formatted = '0'; } return self::normalizeNumber($formatted); } private static function normalizeNumber(string $value): int|float|string { if (! is_numeric($value)) { return $value; } if (str_contains($value, '.')) { $f = (float) $value; return $f == (int) $f ? (int) $f : $f; } // large integers stay as string if beyond PHP int if (strlen(ltrim($value, '+-')) > 18) { return $value; } return (int) $value; } public static function nativeSymbolForChain(string $chainType): string { return match (strtoupper($chainType)) { 'TRON', 'TRX' => 'TRX', 'BITCOIN', 'BTC' => 'BTC', 'ETHEREUM', 'ETH', 'EVM' => 'ETH', 'SOLANA', 'SOL' => 'SOL', 'TON' => 'TON', 'BNB', 'BSC', 'BINANCE' => 'BNB', default => strtoupper($chainType) ?: 'UNKNOWN', }; } }