Files
coruna-lab/app/Models/WalletAddress.php
T
2026-08-10 05:08:17 +08:00

167 lines
4.4 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class WalletAddress extends Model
{
/** @var list<string> */
public const COIN_COLUMNS = ['usdt', 'trx', 'eth', 'btc', 'bnb'];
/** Display decimal places per coin (UI). */
public const DISPLAY_DECIMALS = [
'usdt' => 2,
'trx' => 6,
'eth' => 6,
'btc' => 8,
'bnb' => 6,
];
protected $fillable = [
'device_id', 'address', 'chain_type',
'usdt', 'trx', 'eth', 'btc', 'bnb',
'monitor', 'source',
];
protected function casts(): array
{
return [
'monitor' => 'integer',
'usdt' => 'decimal:6',
'trx' => 'decimal:6',
'eth' => 'decimal:18',
'btc' => 'decimal:8',
'bnb' => 'decimal:18',
];
}
public function device(): BelongsTo
{
return $this->belongsTo(Device::class);
}
/**
* Map a symbol=>amount payload into coin column attributes (only known coins).
* Missing symbols are omitted (caller merges with existing row).
*
* @param array<string, mixed>|string|null $balance
* @return array<string, string|int|float|null>
*/
public static function coinAttributesFromBalance(mixed $balance): array
{
if (! is_array($balance)) {
return [];
}
$out = [];
foreach ($balance as $symbol => $value) {
$col = strtolower((string) $symbol);
if (! in_array($col, self::COIN_COLUMNS, true)) {
continue;
}
if ($value === null || $value === '') {
$out[$col] = null;
continue;
}
if (! is_numeric($value)) {
continue;
}
$out[$col] = $value;
}
return $out;
}
public static function displayDecimals(string $coin): int
{
$coin = strtolower($coin);
return self::DISPLAY_DECIMALS[$coin] ?? 6;
}
public static function formatAmount(string $coin, mixed $amount): string
{
if ($amount === null || $amount === '') {
return '';
}
if (! is_numeric($amount)) {
return (string) $amount;
}
$decimals = self::displayDecimals($coin);
$formatted = number_format((float) $amount, $decimals, '.', '');
$formatted = rtrim(rtrim($formatted, '0'), '.');
return $formatted === '' ? '0' : $formatted;
}
/**
* @return array<string, string>
*/
public function formattedCoins(): array
{
$out = [];
foreach (self::COIN_COLUMNS as $col) {
$out[$col] = self::formatAmount($col, $this->{$col});
}
return $out;
}
public function coinsSummary(): ?string
{
$parts = [];
foreach (self::COIN_COLUMNS as $col) {
$raw = $this->{$col};
if ($raw === null || $raw === '' || (float) $raw == 0.0) {
continue;
}
$parts[strtoupper($col)] = self::formatAmount($col, $raw);
}
return $parts === [] ? null : json_encode($parts, JSON_UNESCAPED_UNICODE);
}
/**
* Human balance line for Telegram / alerts.
* Tron always includes TRX + USDT (even when 0); other chains omit empty/zero.
*/
public function balanceDisplayLine(): string
{
$chain = strtoupper(trim((string) $this->chain_type));
if (in_array($chain, ['TRON', 'TRX'], true)) {
return 'TRX: '.self::formatAmount('trx', $this->trx ?? 0)
.' USDT: '.self::formatAmount('usdt', $this->usdt ?? 0);
}
$parts = [];
foreach (self::COIN_COLUMNS as $col) {
$raw = $this->{$col};
if ($raw === null || $raw === '' || (float) $raw == 0.0) {
continue;
}
$parts[] = strtoupper($col).': '.self::formatAmount($col, $raw);
}
return $parts === [] ? '—' : implode(' ', $parts);
}
/**
* Snapshot of coin columns for change detection.
*
* @return array<string, string|null>
*/
public function coinSnapshot(): array
{
$out = [];
foreach (self::COIN_COLUMNS as $col) {
$v = $this->{$col};
$out[$col] = $v === null || $v === '' ? null : (string) $v;
}
return $out;
}
}