false, 'error' => 'HOT_WALLET_MNEMONIC is not configured']; } $index = (int) config('coruna.hot_wallet.index', 0); $asset = strtoupper(trim($asset)); $driver = $this->chains->resolve($chain); if (! $driver->isValidAddress($to)) { return ['ok' => false, 'error' => 'Invalid recipient address']; } $this->assertAmountWithinLimit($amount, $asset); $from = $driver->deriveAddress($mnemonic, $index); $txid = match ($asset) { 'TRX' => $driver->sendNative($mnemonic, $index, $to, $amount), 'USDT' => $driver->sendToken( $mnemonic, $index, $to, $amount, (string) config('coruna.tron.usdt_contract'), ), default => throw new RuntimeException("Unsupported asset: {$asset}"), }; return ['ok' => true, 'txid' => $txid, 'from' => $from]; } catch (\Throwable $e) { return ['ok' => false, 'error' => $e->getMessage()]; } } private function assertAmountWithinLimit(string $amount, string $asset): void { if (! preg_match('/^\d+(\.\d{1,6})?$/', $amount) || bccomp($amount, '0') <= 0) { throw new RuntimeException('Invalid amount'); } $maxKey = $asset === 'TRX' ? 'coruna.transfer.max_trx' : 'coruna.transfer.max_usdt'; $max = (string) config($maxKey, '0'); if ($max !== '' && $max !== '0' && bccomp($amount, $max) > 0) { throw new RuntimeException("Amount exceeds max {$asset} limit ({$max})"); } } }