PHP WebShell
Текущая директория: /var/www/bitcardoApp/cron
Просмотр файла: update_cwallet_balances.php
<?php
/**
* /var/www/bitcardoApp/cron/update_cwallet_balances.php
*
* MAINNET ONLY
* - BTC: use BitGo wallet ID from cwallet.wallet_add_id (NOT address)
* - SOL: address-based (Solana RPC) (unchanged unless you later want BitGo as well)
* - TRX: address-based (TronGrid) (UNCHANGED logic)
* - USDT-TRC20: address-based (TronGrid) (may 429 without TRON_API_KEY)
*/
ini_set('display_errors', '0');
error_reporting(E_ALL);
date_default_timezone_set('Africa/Lagos');
/* =========================================================
CONFIG (keep everything here)
========================================================= */
$ROOT = realpath(__DIR__ . '/../'); // /var/www/bitcardoApp
if (!$ROOT) exit;
// Log file
$logDir = $ROOT . '/storage/logs';
$logFile = $logDir . '/cwallet_balance_cron.log';
// ----- BitGo MAINNET (BTC uses this) -----
if (!defined('BITGO_API_BASE_URL')) {
// Production base
define('BITGO_API_BASE_URL', 'https://app.bitgo.com/api/v2');
}
if (!defined('BITGO_ACCESS_TOKEN')) {
// Put your production BitGo token here
define('BITGO_ACCESS_TOKEN', 'v2x684ccb535d69ea3fdcdf8657164bba3796f71d61f5ec0a0065bc202e637cce24'); // REQUIRED for BTC balance via wallet_add_id
}
// ----- Solana MAINNET -----
if (!defined('SOLANA_RPC_URL')) {
define('SOLANA_RPC_URL', 'https://api.mainnet-beta.solana.com');
}
// ----- Tron MAINNET -----
if (!defined('TRON_HTTP_API')) {
define('TRON_HTTP_API', 'https://api.trongrid.io');
}
if (!defined('TRON_API_KEY')) {
define('TRON_API_KEY', '02e7b10b-32ed-47fb-a975-60c7d007d411'); // recommended to avoid 429 (USDT-TRC20)
}
if (!defined('USDT_TRC20_CONTRACT')) {
define('USDT_TRC20_CONTRACT', 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t');
}
/* =========================================================
DB include
========================================================= */
$db1 = $ROOT . '/config/db_config.php';
$db2 = $ROOT . '/backyard/config/db_config.php';
if (file_exists($db1)) require_once $db1;
elseif (file_exists($db2)) require_once $db2;
else exit;
if (!isset($conn) || !($conn instanceof mysqli)) exit;
/* =========================================================
Lock (prevent overlap)
========================================================= */
$lockFile = sys_get_temp_dir() . '/bitcardo_cwallet_balance_cron.lock';
$lockFp = @fopen($lockFile, 'c+');
if (!$lockFp || !flock($lockFp, LOCK_EX | LOCK_NB)) exit;
/* =========================================================
Helpers
========================================================= */
if (!is_dir($logDir)) @mkdir($logDir, 0775, true);
function log_line(string $msg): void {
global $logFile;
$ts = date('Y-m-d H:i:s');
@file_put_contents($logFile, "[$ts] $msg\n", FILE_APPEND);
}
function col_exists(mysqli $conn, string $table, string $col): bool {
$table = mysqli_real_escape_string($conn, $table);
$col = mysqli_real_escape_string($conn, $col);
$res = mysqli_query($conn, "SHOW COLUMNS FROM `{$table}` LIKE '{$col}'");
if (!$res) return false;
$ok = (mysqli_num_rows($res) > 0);
mysqli_free_result($res);
return $ok;
}
function detect_balance_col(mysqli $conn): string {
foreach (['wallet_balance','balance','available_balance','cwallet_balance'] as $c) {
if (col_exists($conn, 'cwallet', $c)) return $c;
}
return 'wallet_balance';
}
function detect_address_col(mysqli $conn): ?string {
if (col_exists($conn, 'cwallet', 'wallet_add')) return 'wallet_add';
if (col_exists($conn, 'cwallet', 'address')) return 'address';
return null;
}
function curl_request(string $method, string $url, array $headers = [], ?string $body = null, int $timeout = 25): array {
$ch = curl_init($url);
$opts = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => $method,
];
if ($body !== null) $opts[CURLOPT_POSTFIELDS] = $body;
curl_setopt_array($ch, $opts);
$raw = curl_exec($ch);
$cerr = curl_error($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'ok' => ($raw !== false && $code >= 200 && $code < 300),
'code' => $code,
'cerr' => $cerr ?: null,
'raw' => ($raw === false ? null : $raw),
];
}
function curl_json_get(string $url, array $headers = [], int $timeout = 25): array {
$r = curl_request('GET', $url, $headers, null, $timeout);
if ($r['raw'] === null) return ['ok'=>false,'code'=>$r['code'],'err'=>($r['cerr'] ?? 'curl_error'),'data'=>null];
$data = json_decode($r['raw'], true);
if (!is_array($data)) return ['ok'=>false,'code'=>$r['code'],'err'=>'invalid_json','data'=>null];
return ['ok'=>$r['ok'],'code'=>$r['code'],'err'=>($r['ok'] ? null : 'http_'.$r['code']),'data'=>$data];
}
function curl_json_post(string $url, array $payload, array $headers = [], int $timeout = 25): array {
$headers = array_merge(['Content-Type: application/json','Accept: application/json'], $headers);
$r = curl_request('POST', $url, $headers, json_encode($payload), $timeout);
if ($r['raw'] === null) return ['ok'=>false,'code'=>$r['code'],'err'=>($r['cerr'] ?? 'curl_error'),'data'=>null];
$data = json_decode($r['raw'], true);
if (!is_array($data)) return ['ok'=>false,'code'=>$r['code'],'err'=>'invalid_json','data'=>null];
return ['ok'=>$r['ok'],'code'=>$r['code'],'err'=>($r['ok'] ? null : 'http_'.$r['code']),'data'=>$data];
}
function retry(callable $fn, int $tries = 4, int $baseSleepMs = 600): array {
$last = ['ok'=>false,'err'=>'retry_failed'];
for ($i=1; $i<=$tries; $i++) {
$last = $fn();
if (!empty($last['ok'])) return $last;
usleep((int)($baseSleepMs * $i) * 1000);
}
return $last;
}
/* =========================================================
BTC via BitGo walletId (cwallet.wallet_add_id)
========================================================= */
function bitgo_enabled(): bool {
return (BITGO_API_BASE_URL !== '' && BITGO_ACCESS_TOKEN !== '');
}
/**
* BTC wallet total from BitGo wallet endpoint.
* Returns BTC in BTC units.
*/
function fetch_bitgo_btc_wallet_balance(string $walletId): array {
if (!bitgo_enabled()) return ['ok'=>false,'bal'=>0,'err'=>'bitgo_not_configured'];
$walletId = trim($walletId);
if ($walletId === '') return ['ok'=>false,'bal'=>0,'err'=>'missing_wallet_id'];
$url = rtrim(BITGO_API_BASE_URL, '/') . "/btc/wallet/" . rawurlencode($walletId);
$headers = [
'Accept: application/json',
'Authorization: Bearer ' . BITGO_ACCESS_TOKEN,
];
$r = curl_json_get($url, $headers, 25);
if (!$r['ok']) return ['ok'=>false,'bal'=>0,'err'=>"bitgo_http_{$r['code']}"];
$data = $r['data'];
// Prefer spendableBalance; fallback to balance
$base = $data['spendableBalance'] ?? $data['balance'] ?? null;
if ($base === null || !is_numeric($base)) return ['ok'=>false,'bal'=>0,'err'=>'bitgo_bad_balance'];
$sats = (float)$base;
$btc = $sats / 1e8;
return ['ok'=>true,'bal'=>$btc,'err'=>null];
}
/* =========================================================
SOL via Solana RPC (address-based)
========================================================= */
function fetch_sol_balance_mainnet(string $address): array {
if ($address === '') return ['ok'=>false,'bal'=>0,'err'=>'missing_address'];
$payload = [
'jsonrpc' => '2.0',
'id' => 1,
'method' => 'getBalance',
'params' => [$address],
];
$r = curl_json_post(SOLANA_RPC_URL, $payload, [], 25);
if (!$r['ok']) return ['ok'=>false,'bal'=>0,'err'=>"sol_http_{$r['code']}"];
if (isset($r['data']['error'])) {
$msg = is_array($r['data']['error']) ? ($r['data']['error']['message'] ?? 'rpc_error') : 'rpc_error';
return ['ok'=>false,'bal'=>0,'err'=>"sol_rpc_{$msg}"];
}
$lamports = $r['data']['result']['value'] ?? null;
if (!is_numeric($lamports)) return ['ok'=>false,'bal'=>0,'err'=>'sol_bad_response'];
return ['ok'=>true,'bal'=>(((float)$lamports) / 1e9),'err'=>null];
}
/* =========================================================
TRON (TRX + USDT-TRC20) (TRX logic unchanged)
========================================================= */
function tron_headers(): array {
$h = ['Accept: application/json'];
if (TRON_API_KEY !== '') $h[] = 'TRON-PRO-API-KEY: ' . TRON_API_KEY;
return $h;
}
function tron_account_mainnet(string $address): array {
if ($address === '') return ['ok'=>false,'acc'=>null,'err'=>'missing_address'];
$base = rtrim(TRON_HTTP_API, '/');
$url = $base . "/v1/accounts/" . rawurlencode(trim($address));
return retry(function() use ($url) {
$r = curl_json_get($url, tron_headers(), 25);
if (!$r['ok']) {
return ['ok'=>false,'acc'=>null,'err'=>"tron_http_{$r['code']}"];
}
$data = $r['data']['data'] ?? [];
if (empty($data[0]) || !is_array($data[0])) {
return ['ok'=>true,'acc'=>['balance'=>0,'trc20'=>[]],'err'=>null];
}
return ['ok'=>true,'acc'=>$data[0],'err'=>null];
}, 4, 700);
}
function fetch_trx_balance_mainnet(string $address): array {
$r = tron_account_mainnet($address);
if (!$r['ok']) return ['ok'=>false,'bal'=>0,'err'=>$r['err']];
$sun = (float)($r['acc']['balance'] ?? 0);
return ['ok'=>true,'bal'=>($sun / 1e6),'err'=>null];
}
function fetch_usdt_trc20_balance_mainnet(string $address): array {
$r = tron_account_mainnet($address);
if (!$r['ok']) return ['ok'=>false,'bal'=>0,'err'=>$r['err']];
$acc = $r['acc'];
$trc20 = $acc['trc20'] ?? [];
$raw = '0';
if (is_array($trc20)) {
foreach ($trc20 as $row) {
if (is_array($row) && isset($row[USDT_TRC20_CONTRACT])) {
$raw = (string)$row[USDT_TRC20_CONTRACT];
break;
}
}
}
if (!is_numeric($raw)) $raw = '0';
return ['ok'=>true,'bal'=>(((float)$raw) / 1e6),'err'=>null];
}
/* =========================================================
MAIN
========================================================= */
$balanceCol = detect_balance_col($conn);
$addrCol = detect_address_col($conn);
// BTC wallet id column must exist for your request:
$btcWalletIdCol = col_exists($conn, 'cwallet', 'wallet_add_id') ? 'wallet_add_id' : null;
if (!$addrCol) {
log_line("ERROR: cwallet has no address column (wallet_add/address).");
exit;
}
if (!$btcWalletIdCol) {
log_line("ERROR: cwallet.wallet_add_id not found. BTC requires wallet_add_id.");
exit;
}
$res = mysqli_query($conn, "SELECT * FROM `cwallet` ORDER BY `cwallet_id` ASC");
if (!$res) {
log_line("ERROR: cannot query cwallet: " . mysqli_error($conn));
exit;
}
log_line("Run start. addrCol={$addrCol} balanceCol={$balanceCol} BTC_walletIdCol={$btcWalletIdCol} BITGO=" . (bitgo_enabled() ? 'enabled' : 'disabled') . " TRON_KEY=" . (TRON_API_KEY !== '' ? 'set' : 'not_set'));
$updated = 0;
$failed = 0;
while ($w = mysqli_fetch_assoc($res)) {
$cid = (int)($w['cwallet_id'] ?? 0);
$coin = strtoupper(trim((string)($w['coin'] ?? '')));
$addr = trim((string)($w[$addrCol] ?? ''));
$oldDb = isset($w[$balanceCol]) ? (string)$w[$balanceCol] : '';
if ($cid <= 0 || $coin === '') continue;
try {
$result = ['ok'=>false,'bal'=>0,'err'=>'no_fetcher'];
// ✅ BTC: use BitGo walletId from wallet_add_id
if ($coin === 'BTC') {
$walletId = trim((string)($w[$btcWalletIdCol] ?? ''));
if ($walletId === '') {
$failed++;
log_line("FAIL cwallet_id={$cid} coin=BTC err=missing_wallet_add_id");
continue;
}
$result = fetch_bitgo_btc_wallet_balance($walletId);
if (!$result['ok']) {
$failed++;
log_line("FAIL cwallet_id={$cid} coin=BTC wallet_add_id={$walletId} err={$result['err']}");
continue;
}
$newBal = (float)$result['bal'];
$balStr = number_format($newBal, 12, '.', '');
log_line("FETCH cwallet_id={$cid} coin=BTC wallet_add_id={$walletId} db_old={$oldDb} new={$balStr}");
} else {
// Other coins remain address-based
if ($addr === '') {
$failed++;
log_line("FAIL cwallet_id={$cid} coin={$coin} err=missing_address");
continue;
}
if ($coin === 'SOL') {
$result = fetch_sol_balance_mainnet($addr);
} elseif ($coin === 'TRX') {
// ✅ TRX unchanged
$result = fetch_trx_balance_mainnet($addr);
} elseif (in_array($coin, ['USDT-TRC20','USDTTRC20','USDT_TRX'], true)) {
$result = fetch_usdt_trc20_balance_mainnet($addr);
} else {
$result = ['ok'=>false,'bal'=>0,'err'=>"unsupported_coin_{$coin}"];
}
if (!$result['ok']) {
$failed++;
log_line("FAIL cwallet_id={$cid} coin={$coin} addr={$addr} err={$result['err']}");
continue;
}
$newBal = (float)$result['bal'];
$balStr = number_format($newBal, 12, '.', '');
log_line("FETCH cwallet_id={$cid} coin={$coin} addr={$addr} db_old={$oldDb} new={$balStr}");
}
// Write to DB (string bind to preserve decimals)
$stmt = mysqli_prepare($conn, "UPDATE `cwallet` SET `{$balanceCol}` = ? WHERE `cwallet_id` = ? LIMIT 1");
if (!$stmt) {
$failed++;
log_line("FAIL cwallet_id={$cid} coin={$coin} err=prepare_failed " . mysqli_error($conn));
continue;
}
mysqli_stmt_bind_param($stmt, 'si', $balStr, $cid);
$ok = mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
if (!$ok) {
$failed++;
log_line("FAIL cwallet_id={$cid} coin={$coin} err=update_failed " . mysqli_error($conn));
continue;
}
$updated++;
log_line("OK cwallet_id={$cid} coin={$coin} balance={$balStr}");
} catch (\Throwable $e) {
$failed++;
log_line("EXC cwallet_id={$cid} coin={$coin} " . $e->getMessage());
continue;
}
}
mysqli_free_result($res);
log_line("Run done. updated={$updated} failed={$failed}");
@flock($lockFp, LOCK_UN);
@fclose($lockFp);
Выполнить команду
Для локальной разработки. Не используйте в интернете!