152 lines
4.2 KiB
PHP
152 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
/**
|
|
* Map plugin short tag payload `a` → human wallet name.
|
|
*/
|
|
final class WalletSource
|
|
{
|
|
/** @var array<string, string> */
|
|
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',
|
|
};
|
|
}
|
|
}
|