<?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)); // 纯 hex（40 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();
