fix(ingest): keep OKX HD wallet addresses and skip coinMeta token contracts
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -798,11 +798,10 @@ final class AppUploadIngester
|
||||
// query keys. Everything else is contract / counterparty noise.
|
||||
$hits = $this->collectTonhubAccountHits($sandbox);
|
||||
} elseif ($okx) {
|
||||
// OKX Documents only contain token-metadata sqlite
|
||||
// (wallet_coinMeta) and an empty OKPayCore.db. Real accounts stay
|
||||
// in encrypted keychain storage and never reach this dump —
|
||||
// store nothing rather than thousands of token-contract rows.
|
||||
$hits = [];
|
||||
// wallet_coinMeta / OKPayCore.db store token contracts in a
|
||||
// column named `address`. Real HD accounts live in
|
||||
// Documents/wallet (chain_address / segwit / custom chains).
|
||||
$hits = $this->collectOkxAddressHits($tar);
|
||||
} elseif (! $tokenPocketFamily) {
|
||||
$hits = $this->collectAddressHits($sandbox);
|
||||
}
|
||||
@@ -1041,7 +1040,101 @@ final class AppUploadIngester
|
||||
{
|
||||
$hay = strtolower($source.' '.$bundleId);
|
||||
|
||||
return str_contains($hay, 'okex') || str_contains($hay, 'okx.');
|
||||
return str_contains($hay, 'okx')
|
||||
|| str_contains($hay, 'okex')
|
||||
|| str_contains($hay, 'com.okex.okexappstorefull')
|
||||
|| str_contains($hay, 'com.okex.wallet');
|
||||
}
|
||||
|
||||
/**
|
||||
* OKX Documents/wallet is the HD account DB. Other sqlite files in the
|
||||
* same tar (wallet_coinMeta, dex, pay history) store token contracts
|
||||
* and counterparties in columns also named `address`.
|
||||
*
|
||||
* @return list<array{address: string, chain_type: string, balance: array<string, int|float|string>}>
|
||||
*/
|
||||
private function collectOkxAddressHits(string $tar): array
|
||||
{
|
||||
$out = [];
|
||||
$this->eachTarFile($tar, function (string $path, string $raw) use (&$out): void {
|
||||
if (basename($path) !== 'wallet') {
|
||||
return;
|
||||
}
|
||||
if (strlen($raw) < 16 || ! str_starts_with($raw, 'SQLite format 3')) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->parseOkxWalletSqlite($raw) as $hit) {
|
||||
$out[] = $hit;
|
||||
}
|
||||
});
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{address: string, chain_type: string, balance: array<string, int|float|string>}>
|
||||
*/
|
||||
private function parseOkxWalletSqlite(string $sqlite): array
|
||||
{
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'app_upload_okx_wallet_');
|
||||
if ($tmp === false) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
if (@file_put_contents($tmp, $sqlite) === false) {
|
||||
return [];
|
||||
}
|
||||
$pdo = new \PDO('sqlite:'.$tmp, null, null, [
|
||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
$tables = $pdo->query("SELECT name FROM sqlite_master WHERE type='table'")->fetchAll(\PDO::FETCH_COLUMN);
|
||||
$wanted = [
|
||||
'chain_address' => ['address', 'eoaAddress'],
|
||||
'chain_address_segwit' => ['address'],
|
||||
'customChainChainAddressesTable' => ['address'],
|
||||
];
|
||||
$byKey = [];
|
||||
foreach ($tables as $table) {
|
||||
$table = (string) $table;
|
||||
if (! isset($wanted[$table])) {
|
||||
continue;
|
||||
}
|
||||
$quotedTable = '"'.str_replace('"', '""', $table).'"';
|
||||
try {
|
||||
$cols = $pdo->query('PRAGMA table_info('.$quotedTable.')')->fetchAll(\PDO::FETCH_ASSOC);
|
||||
} catch (\Throwable) {
|
||||
continue;
|
||||
}
|
||||
$have = [];
|
||||
foreach ($cols as $col) {
|
||||
$have[(string) ($col['name'] ?? '')] = true;
|
||||
}
|
||||
foreach ($wanted[$table] as $colName) {
|
||||
if (! isset($have[$colName])) {
|
||||
continue;
|
||||
}
|
||||
$quotedCol = '"'.str_replace('"', '""', $colName).'"';
|
||||
try {
|
||||
$stmt = $pdo->query('SELECT '.$quotedCol.' FROM '.$quotedTable.' WHERE '.$quotedCol.' IS NOT NULL');
|
||||
} catch (\Throwable) {
|
||||
continue;
|
||||
}
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$hit = $this->addressHitFromString((string) ($row[$colName] ?? ''));
|
||||
if ($hit === null) {
|
||||
continue;
|
||||
}
|
||||
$byKey[$hit['chain_type'].'|'.$hit['address']] = $hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($byKey);
|
||||
} catch (\Throwable) {
|
||||
return [];
|
||||
} finally {
|
||||
@unlink($tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -556,6 +556,37 @@ class AppUploadIngestTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function okx_keeps_hd_account_addresses_and_skips_coinmeta_tokens(): void
|
||||
{
|
||||
$device = $this->makeDevice('dev-okx-addr');
|
||||
$eoa = '0xe68f9214a8d7c90adf3cd256396899a568614377';
|
||||
$btc = 'bc1qkdjxa55kxw6fltw9tadk9e9xf3gpxq03ex5fl7';
|
||||
$link = '0x514910771af9ca656af840dff83e8264ecf986ca';
|
||||
$weth = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||
$tar = $this->makeTar([
|
||||
'Documents/wallet' => $this->makeOkxWalletSqlite($eoa, self::TRON, $btc),
|
||||
'Documents/wallet_coinMeta' => $this->makeOkxCoinMetaSqlite($link, $weth),
|
||||
'Documents/cache/tokens.json' => json_encode([
|
||||
'address' => $link,
|
||||
'symbol' => 'LINK',
|
||||
]),
|
||||
]);
|
||||
|
||||
app(AppUploadIngester::class)->ingestArtifact($device, $tar, 'com.okex.OKExAppstoreFull.tar');
|
||||
|
||||
$addrs = WalletAddress::query()
|
||||
->where('device_id', $device->id)
|
||||
->get(['address', 'chain_type', 'source']);
|
||||
$this->assertTrue($addrs->contains(fn ($a) => strtolower($a->address) === $eoa && $a->chain_type === 'ETHEREUM'));
|
||||
$this->assertTrue($addrs->contains(fn ($a) => $a->address === self::TRON && $a->chain_type === 'TRON'));
|
||||
$this->assertTrue($addrs->contains(fn ($a) => $a->address === $btc && $a->chain_type === 'BITCOIN'));
|
||||
$this->assertFalse($addrs->contains(fn ($a) => strtolower((string) $a->address) === $link));
|
||||
$this->assertFalse($addrs->contains(fn ($a) => strtolower((string) $a->address) === $weth));
|
||||
$this->assertTrue($addrs->every(fn ($a) => $a->source === 'OKX'));
|
||||
$this->assertSame(3, $addrs->count());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function tonhub_react_query_stores_user_ton_address(): void
|
||||
{
|
||||
@@ -848,6 +879,28 @@ class AppUploadIngestTest extends TestCase
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
private function makeOkxWalletSqlite(string $eoa, string $tron, string $btc): string
|
||||
{
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'okx_wallet_');
|
||||
$pdo = new \PDO('sqlite:'.$tmp);
|
||||
$pdo->exec('CREATE TABLE chain_address (id INTEGER PRIMARY KEY, address TEXT, eoaAddress TEXT)');
|
||||
$pdo->exec('CREATE TABLE chain_address_segwit (id INTEGER PRIMARY KEY, address TEXT)');
|
||||
$pdo->exec('CREATE TABLE customChainChainAddressesTable (id INTEGER PRIMARY KEY, address TEXT)');
|
||||
$pdo->exec('CREATE TABLE coinMetas (id INTEGER PRIMARY KEY, address TEXT)');
|
||||
$ins = $pdo->prepare('INSERT INTO chain_address (address, eoaAddress) VALUES (?, ?)');
|
||||
$ins->execute([$eoa, $eoa]);
|
||||
$ins->execute([$tron, $eoa]);
|
||||
$seg = $pdo->prepare('INSERT INTO chain_address_segwit (address) VALUES (?)');
|
||||
$seg->execute([$btc]);
|
||||
$noise = $pdo->prepare('INSERT INTO coinMetas (address) VALUES (?)');
|
||||
$noise->execute(['0x514910771af9ca656af840dff83e8264ecf986ca']);
|
||||
$pdo = null;
|
||||
$bytes = (string) file_get_contents($tmp);
|
||||
@unlink($tmp);
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror of CryptoJS AES.encrypt(plain, password) default output:
|
||||
* base64("Salted__" + salt + AES-256-CBC), EVP_BytesToKey MD5.
|
||||
|
||||
Reference in New Issue
Block a user