goldrat/goldrat.php
2025-10-13 09:04:00 +00:00

326 lines
12 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// start.php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/helper.php';
require __DIR__ . '/config.php';
require __DIR__ . '/version.php';
use Workerman\Worker;
use Elliptic\EC;
use kornrunner\Keccak;
use Workerman\Timer;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Medoo\Medoo;
// CLI 参数解析(可选)
#$options = getopt('', ['workers::', 'repeat::', 'numeric-only::', 'logfile::']);
#echo($options['repeat']);
$workerCount = isset($options['workers']) ? (int)$options['workers'] : cpu_count()*4;
$minRepeat = isset($options['repeat']) ? (int)$options['repeat'] : 4; // 默认至少 4 个相同
$requireNumeric = isset($options['numeric-only']) ? (bool)$options['numeric-only'] : false;
$logFile = isset($options['logfile']) ? $options['logfile'] : (__DIR__ . '/found.log');
$detailInfo = isset($options['detailInfo']) ? (bool)$options['detailInfo'] : false;
$staticSecond = isset($options['staticSecond']) ? (int)$options['staticSecond'] : 10;
$apiKey=isset($options['apikey']) ? $options['apikey'] : 'none';
echo "Configuration: VerboseOpt={$detailInfo}, Progress:{$workerCount}, SameNumbers={$minRepeat}, NumericOnly=" . ($requireNumeric ? 'true' : 'false') . "\n";
if(!isset($options['redis'])||!isset($options['redis']['ip'])||!isset($options['redis']['port'])){
exit("🚨Lack of Redis Configuration, now exit……\n");
}
if(!isset($options['db'])){
exit("🚨Lack of Database Configuration, now exit……\n");
}else{
$dbConfig=$options['db'];
}
if($apiKey=='none'){
exit("🚨Lack of EtherScan API Key, now exit……\n You can apply for an APIKey through https://etherscan.io/apidashboard \n");
}
$Rat = new Worker();
$GLOBALS['mainid']=posix_getpid(); #父进程PID
$Rat->name="Glod Rat";
$Rat->count = max(1, $workerCount);
$GDS = new GlobalData\Server('127.0.0.1', 2207);
$Rat->onWorkerStart = function($Rat) use ($minRepeat, $requireNumeric,$detailInfo,$options) {
global $gd;
$gd = new \GlobalData\Client('127.0.0.1:2207');
$gd->count=0;
$pid = posix_getpid();
$cid=$pid-$GLOBALS['mainid'];
if($detailInfo){
echo "[Rat $cid]启动 PID={$pid}\n";
}
$redis = new Redis();
$redis->connect($options['redis']['ip'], $options['redis']['port']);
if($detailInfo){
echo "[Rat $cid] Connecting Redis……\n";
}
if(isset($options['redis']['password'])&&$options['redis']['password']!=false){
if($detailInfo){
echo "[Rat $cid] Redis Authing……\n";
}
$redis->auth($options['redis']['password']);
}
if($redis->ping(1)&&$detailInfo){
echo "[Rat $cid] Redis Successfully Connected\n";
}
$ec = new EC('secp256k1');
$start_time = microtime(true);
$staticCount=0;
// 无限循环,持续生成地址并检查
while (true) {
// 生成随机私钥32 字节)
try {
$privBin = random_bytes(32);
} catch (Exception $e) {
// random_bytes 失败(极少见),跳过一次循环
echo "\033[33m[Rat $cid] PID={$pid} random_bytes failed: " . $e->getMessage() . ",thus jumped this loop\n
This is an expected but extremely rare error. It should not affect normal operation. \n
However, if this error persists, please be vigilant.\n
This Rat will sleep for 1000 ms\033[0m\n";
usleep(1000);
continue;
}
$privHex = bin2hex($privBin);
// 由私钥生成公钥(未压缩)
$keyPair = $ec->keyFromPrivate($privHex);
$pubHex = $keyPair->getPublic(false, 'hex'); // '04' + x + y
$pubHexNoPrefix = (strpos($pubHex, '04') === 0) ? substr($pubHex, 2) : $pubHex;
// keccak256 公钥并取地址
$hash = Keccak::hash(hex2bin($pubHexNoPrefix), 256); // 返回 hex
$address = '0x' . substr($hash, -40);
$staticCount=$staticCount+1;
if (microtime(true) - $start_time >= 5) {
if($detailInfo){
echo "[Rat $cid] digged $staticCount address in past 5s \n";
}
$gd->count=$gd->count+$staticCount;
$staticCount=0;
$start_time = microtime(true);
}
// 检查末尾连续相同字符(只检查尾部连续段)
$addrRaw = strtolower(substr($address, 2)); // 纯 hex40 chars
$len = strlen($addrRaw);
$lastChar = $addrRaw[$len - 1];
$count = 1;
for ($i = $len - 2; $i >= 0; $i--) {
if ($addrRaw[$i] === $lastChar) $count++;
else break;
}
// 如果满足重复数量,并且(可选)最后字符为数字
if ($count >= $minRepeat) {
if ($requireNumeric && !ctype_digit($lastChar)) {
// 不满足数字要求,继续
} else {
// 匹配成功 —— 调用 scanwallet 并记录(但不退出,继续寻找)
try {
if($detailInfo){
echo "[Rat $cid] PID={$pid} Found Address {$address} (ended with {$count} x '{$lastChar}')\n";
}
$gd->scount=$gd->scount+1;
$result = [
'pid' => $pid,
'private_key' => $privHex,
'timestamp' => time(),
'count'=> $count,
'char'=> $lastChar,
];
$redis->sadd('goldRat_set', $address);
$redis->hset('goldRat_hash', $address, json_encode($result));
} catch (Throwable $t) {
if($detailInfo){
echo "\033[31m ❌[Rat $cid] PID={$pid} ERROR OCCURRED : " . $t->getMessage() . "\033[0m\n";
}
}
}
}
// 若想降低 CPU 占用可在这里加短暂 sleep但会降低查找速度
// usleep(0); // 等同于 yield
}
if($detailInfo){
echo "\033[31m ⚠[Rat $cid] PID={$pid} Unexceptedly Exited The Loop \033[0m\n";
}
};
$Rat->onWorkerStop = function($Rat) {
$pid = posix_getpid();
$cid=$pid-$GLOBALS['mainid'];
echo "[Rat $cid] PID={$pid} 已终止\n";
};
$Static = new Worker();
$Static->name='Static&Timer Service';
$Static->onWorkerStart = function($Static)use($staticSecond,$detailInfo,$apiKey) {
$GLOBALS['starttime']=microtime(true);
$GLOBALS['lc']=0;
$GLOBALS['lsc']=0;
$gda = new \GlobalData\Client('127.0.0.1:2207');
$gda->second_token=5;
$gda->minute_token=60;
$gda->lastToken='--';
$gda->lastTokenTime='will shown after 1min';
Timer::add(1, function()use($staticSecond,$detailInfo,$gda,$apiKey){
$gda->second_token= $gda->second_token+5;
});
Timer::add(60, function()use($staticSecond,$detailInfo,$gda,$apiKey){
if($detailInfo){
echo "Go to ask EtherScan for API useage";
}
$client = new Client([
'base_uri' => 'https://api.etherscan.io/v2/',
'timeout' => 10.0,
]);
$params = [
'query' => [
'module' => 'getapilimit',
'action' => 'getapilimit',
'apikey' => $apiKey
]
];
try {
$response = $client->get('api', $params);
$statusCode = $response->getStatusCode();
if ($statusCode == 200) {
$data = json_decode($response->getBody(), true);
if ($data['status'] === '1') {
$lastminute=secondsUntilUtcDayEnd()*60;
$addToken=$data['result']['creditsAvailable']/60;
$gda->lastToken=$data['result']['creditsAvailable'];
$gda->lastTokenTime=$data['result']['intervalExpiryTimespan'];
if($detailInfo){
echo "✅[Timmer]EtherScan Got the credit successfully\n";
}
} else {
if($detailInfo){
echo "❌[Timmer]EtherScan Get Credit Failed\n";
print_r($data);
}
$addToken=0;
}
}else{
if($detailInfo){
if($detailInfo){
echo "\033[31m❌[Timmer]EtherScan Http Request Failed: HTTP" . $statusCode . "\033[0m\n";
}
}
$addToken=0;
}
} catch (RequestException $e) {
echo "\033[31m❌[Timmer]EtherScan Http Request Failed: " . $e->getMessage() . "\033[0m\n";
$addToken=0;
}
$gda->minute_token= $gda->minute_token+$addToken;
});
Timer::add($staticSecond, function()use($staticSecond,$detailInfo,$gda){
$staticCount=$gda->count;
$avgCount=($staticCount-$GLOBALS['lc'])/$staticSecond;
$successCount=$gda->scount;
$creditAvailable=$gda->lastToken;
$resetCountdown=$gda->lastTokenTime;
$avgsCount=($successCount-$GLOBALS['lsc']);
$totalSpeed= ($successCount / (microtime(true)-$GLOBALS['starttime'])) * 3600;
$running=formatSeconds(microtime(true)-$GLOBALS['starttime']);
if(!$detailInfo){
echo "\033[H"; // 光标回到左上角
echo "\033[J";
echo " \033[33m
.88888. dP dP 888888ba dP
d8' `88 88 88 88 `8b 88
88 .d8888b. 88 .d888b88 a88aaaa8P' .d8888b. d8888P
88 YP88 88' `88 88 88' `88 88 `8b. 88' `88 88
Y8. .88 88. .88 88 88. .88 88 88 88. .88 88
`88888' `88888P' dP `88888P8 dP dP `88888P8 dP \033[0m
====================================================
|| Gold Rat v".GR_VERSION." Build ".GR_BUILD." ||
||Dig out wallet address with the beautiful number||
|| As well as a CPU Benchmark tool ||
====================================================
";
}
echo "[STATIC] in last $staticSecond s:
Avg: $avgCount adds/s, Total: $staticCount adds
Succeed: $avgsCount adds, Total: $successCount adds
Total Diging Speed: $totalSpeed adds/hour
Total Runing Time: $running
----------------API CREDITS INFO--------------------
EtherScan.io API
Available: $creditAvailable Credits
Reset Countdown: $resetCountdown
====================================================
Enter Ctrl+C to Exit.
————————————————————————————————————————————————————
©Gold Rat by LayFi.de
\n";
$GLOBALS['lc']=$staticCount;
$GLOBALS['lsc']=$successCount;
});
};
require __DIR__ . '/scanner.php';
require __DIR__ . '/writeToDB.php';
$Scanner=new Worker();
$Scanner->name='Scanner';
$Scanner->count=1;
$Scanner->onWorkerStart = function($Scanner)use($detailInfo,$options,$apiKey,$dbConfig){
usleep(1000);#延迟1秒启动防止与Rat争抢redis并发
$pid = posix_getpid();
if($detailInfo){
echo "[Scanner] PID={$pid} Started\n";
}
$redis = new Redis();
$redis->connect($options['redis']['ip'], $options['redis']['port']);
if($detailInfo){
echo "[Scanner] Connecting Redis……\n";
}
if(isset($options['redis']['password'])&&$options['redis']['password']!=false){
if($detailInfo){
echo "[Scanner] Redis Authing……\n";
}
$redis->auth($options['redis']['password']);
}
if($redis->ping(1)&&$detailInfo){
echo "[Scanner] Redis Successfully Connected\n";
}
$setKey = 'goldRat_set';
$hashKey = 'goldRat_hash';
while(true){
$address = $redis->spop($setKey);
if ($address === false) {
usleep(1000); #休眠1s
}else{
$json = $redis->hget($hashKey, $address);
if($detailInfo){
echo "[Scanner] Take out and start scanning: $address\n";
}
if(scanwallet($address,$apiKey,$detailInfo)){
$active='true';
echo "\033[32m🎉[Scanner] $address found active!\033[0m\n";
writeToDB($address,$dbConfig,$json,true);
}else{
$active='false';
if($detailInfo){
echo "[Scanner] $address found nothing \n";
}
writeToDB($address,$dbConfig,$json,false);
}
$redis->hdel($hashKey, $address);
}
}
};
Worker::runAll();