feat: Solana chain

This commit is contained in:
example
2026-09-23 14:26:38 +08:00
parent f9fd356679
commit cdbfa48662
13 changed files with 749 additions and 10 deletions
+59 -6
View File
@@ -13,7 +13,7 @@ use Illuminate\Support\Facades\Log;
/**
* When a mnemonic has no linked addresses, derive BIP44 indexes 0..2 for
* TRON/ETH/BTC and persist only activated addresses (after a balance query).
* TRON/ETH/BTC/SOL and persist only activated addresses (after a balance query).
* Fully inactive mnemonics stay out of the DB and are not monitored; the
* wallets view previews one index-0 address per chain marked 未激活.
* Activated rows are persisted with monitor on.
@@ -27,6 +27,7 @@ class MnemonicWalletDiscovery
['chain' => 'tron', 'chain_type' => 'TRON'],
['chain' => 'eth', 'chain_type' => 'ETH'],
['chain' => 'btc', 'chain_type' => 'BTC'],
['chain' => 'sol', 'chain_type' => 'SOLANA'],
];
public function __construct(
@@ -63,17 +64,23 @@ class MnemonicWalletDiscovery
return 0;
}
$existing = WalletAddress::query()
$linkedChainTypes = WalletAddress::query()
->where('mnemonic_id', $mnemonic->id)
->exists();
if ($existing) {
return 0;
}
->pluck('chain_type')
->map(fn ($c) => strtoupper(trim((string) $c)))
->filter()
->unique()
->values()
->all();
$source = trim((string) ($mnemonic->source ?: ''));
$saved = [];
foreach (self::CHAINS as $spec) {
if ($this->hasLinkedChain($linkedChainTypes, $spec['chain'])) {
continue;
}
try {
$rows = $this->discoverChain($mnemonic, $phrase, $source, $spec['chain'], $spec['chain_type']);
foreach ($rows as $row) {
@@ -94,6 +101,22 @@ class MnemonicWalletDiscovery
return count($saved);
}
/**
* @param list<string> $linkedChainTypes uppercase chain_type values already linked
*/
private function hasLinkedChain(array $linkedChainTypes, string $chain): bool
{
$aliases = match ($chain) {
'tron' => ['TRON', 'TRX'],
'eth' => ['ETH', 'ETHEREUM', 'EVM', 'BNB', 'BSC', 'BINANCE'],
'btc' => ['BTC', 'BITCOIN'],
'sol' => ['SOL', 'SOLANA'],
default => [strtoupper($chain)],
};
return array_intersect($aliases, $linkedChainTypes) !== [];
}
/**
* Persisted linked rows, or ephemeral index-0 previews when none exist.
*
@@ -121,6 +144,7 @@ class MnemonicWalletDiscovery
'eth' => $coins['eth'],
'btc' => $coins['btc'],
'bnb' => $coins['bnb'],
'sol' => $coins['sol'],
'active' => true,
'persisted' => true,
];
@@ -193,6 +217,7 @@ class MnemonicWalletDiscovery
'tron' => $this->probeTron($driver, $address),
'eth' => $this->probeEth($driver, $address),
'btc' => $this->probeBtc($driver, $address),
'sol' => $this->probeSol($driver, $address),
default => ['activated' => false, 'coins' => []],
};
}
@@ -262,6 +287,32 @@ class MnemonicWalletDiscovery
];
}
/**
* @return array{activated: bool, coins: array<string, string>}
*/
private function probeSol(ChainDriver $driver, string $address): array
{
try {
$sol = $driver->getNativeBalance($address);
} catch (\Throwable) {
return ['activated' => false, 'coins' => $this->emptyCoins('sol')];
}
$usdt = $this->tokenBalance($driver, $address, (string) config('coruna.sol.usdt_contract', ''));
$activated = bccomp($sol, '0', 9) > 0 || bccomp($usdt, '0', 6) > 0;
if (! $activated) {
return ['activated' => false, 'coins' => $this->emptyCoins('sol')];
}
return [
'activated' => true,
'coins' => [
'sol' => $sol,
'usdt' => $usdt,
],
];
}
/**
* @param array<string, string> $coins
*/
@@ -327,6 +378,7 @@ class MnemonicWalletDiscovery
'eth' => null,
'btc' => null,
'bnb' => null,
'sol' => null,
'active' => false,
'persisted' => false,
];
@@ -344,6 +396,7 @@ class MnemonicWalletDiscovery
'tron' => ['trx' => '0', 'usdt' => '0'],
'eth' => ['eth' => '0', 'usdt' => '0'],
'btc' => ['btc' => '0'],
'sol' => ['sol' => '0', 'usdt' => '0'],
default => [],
};
}