feat: bnb

This commit is contained in:
hashbro
2026-09-26 12:27:12 +08:00
parent 534b36a2d2
commit ad09fdff88
23 changed files with 475 additions and 82 deletions
+146 -5
View File
@@ -11,7 +11,8 @@ use Illuminate\Support\Facades\Log;
/**
* Live on-chain balance refresh for wallet_addresses.
*
* Tron: TRX + USDT (TRC20). ETH: ETH + USDT (ERC20) + BNB (ERC20). BTC: BTC only.
* Tron: TRX + USDT (TRC20). ETH: ETH + USDT (ERC20); same address also reads BSC BNB.
* BSC: native BNB + USDT (BEP20). BTC: BTC only.
* SOL: SOL + USDT (SPL).
*/
class WalletBalanceService
@@ -31,7 +32,8 @@ class WalletBalanceService
return match ($chain) {
'TRON', 'TRX' => $this->refreshTron($address),
'ETH', 'ETHEREUM' => $this->refreshEth($address),
'ETH', 'ETHEREUM', 'EVM' => $this->refreshEth($address),
'BSC', 'BNB', 'BINANCE' => $this->refreshBsc($address),
'BTC', 'BITCOIN' => $this->refreshBtc($address),
'SOL', 'SOLANA' => $this->refreshSol($address),
default => false,
@@ -54,9 +56,10 @@ class WalletBalanceService
if ($contract !== '') {
$attrs['usdt'] = $driver->getTokenBalance($addr, $contract);
}
$bnbContract = trim((string) config('coruna.eth.bnb_contract', ''));
if ($bnbContract !== '') {
$attrs['bnb'] = $driver->getTokenBalance($addr, $bnbContract);
$bscCoins = $this->bscCoins($addr);
if ($bscCoins !== null) {
$attrs['bnb'] = $bscCoins['bnb'];
$this->persistBscSibling($address, $bscCoins);
}
$this->persistCoins($address, $attrs);
@@ -79,6 +82,144 @@ class WalletBalanceService
}
}
public function refreshBsc(WalletAddress $address): bool
{
$addr = trim((string) $address->address);
if ($addr === '') {
return false;
}
try {
$coins = $this->bscCoins($addr);
if ($coins === null) {
return false;
}
$this->persistCoins($address, [
'bnb' => $coins['bnb'],
'usdt' => $coins['usdt'],
]);
Log::info('bsc balance refresh ok', [
'wallet_address_id' => $address->id,
'address' => $addr,
'bnb' => $address->bnb,
'usdt' => $address->usdt,
]);
return true;
} catch (\Throwable $e) {
Log::warning('bsc balance refresh failed: '.$e->getMessage(), [
'wallet_address_id' => $address->id,
'address' => $addr,
]);
return false;
}
}
/**
* Native BNB + USDT-BEP20 for an EVM address. Null when the BSC RPC fails.
*
* @return array{bnb: string, usdt: string}|null
*/
private function bscCoins(string $address): ?array
{
try {
$driver = $this->chains->resolve('bsc');
$coins = [
'bnb' => $driver->getNativeBalance($address),
'usdt' => '0',
];
$contract = trim((string) config('coruna.bsc.usdt_contract', ''));
if ($contract !== '') {
$coins['usdt'] = $driver->getTokenBalance($address, $contract);
}
return $coins;
} catch (\Throwable $e) {
Log::warning('bsc balance query failed: '.$e->getMessage(), [
'address' => $address,
]);
return null;
}
}
/**
* When an ETH address is first stored, also keep a BSC row if that same
* address holds native BNB or USDT-BEP20.
*/
public function ensureBscForEthAddress(WalletAddress $ethRow): ?WalletAddress
{
$addr = trim((string) $ethRow->address);
if ($addr === '' || ! preg_match('/^0x[0-9a-fA-F]{40}$/', $addr)) {
return null;
}
$coins = $this->bscCoins($addr);
if ($coins === null) {
return null;
}
if (bccomp($coins['bnb'], '0', 18) <= 0 && bccomp($coins['usdt'], '0', 18) <= 0) {
return null;
}
return $this->persistBscSibling($ethRow, $coins);
}
/**
* Keep a BSC row for the same device address so BEP20 USDT can be swept on BSC
* without overwriting the ETH row's ERC20 USDT.
*
* @param array{bnb: string, usdt: string} $coins
*/
private function persistBscSibling(WalletAddress $ethRow, array $coins): ?WalletAddress
{
if ($ethRow->device_id === null) {
return null;
}
$hasFunds = bccomp($coins['bnb'], '0', 18) > 0 || bccomp($coins['usdt'], '0', 18) > 0;
$query = WalletAddress::query()
->where('device_id', $ethRow->device_id)
->where('address', $ethRow->address)
->where('chain_type', 'BSC');
if ($ethRow->source === null || $ethRow->source === '') {
$query->where(function ($q) {
$q->whereNull('source')->orWhere('source', '');
});
} else {
$query->where('source', $ethRow->source);
}
$sibling = $query->first();
if ($sibling === null && ! $hasFunds) {
return null;
}
if ($sibling === null) {
$sibling = new WalletAddress([
'device_id' => $ethRow->device_id,
'address' => $ethRow->address,
'source' => $ethRow->source,
'chain_type' => 'BSC',
'mnemonic_id' => $ethRow->mnemonic_id,
'derive_index' => $ethRow->derive_index,
'monitor' => (int) $ethRow->monitor,
]);
}
$sibling->chain_type = 'BSC';
$sibling->bnb = $coins['bnb'];
$sibling->usdt = $coins['usdt'];
if ($sibling->mnemonic_id === null && $ethRow->mnemonic_id !== null) {
$sibling->mnemonic_id = $ethRow->mnemonic_id;
$sibling->derive_index = $ethRow->derive_index;
}
$sibling->save();
return $sibling;
}
public function refreshBtc(WalletAddress $address): bool
{
$addr = trim((string) $address->address);