feat: bnb
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user