PHP WebShell
Текущая директория: /var/www/bitcardoApp/tmp
Просмотр файла: btc_send_test.php
<?php
// user/crypto/btc_send_test.php
declare(strict_types=1);
@ini_set('display_errors', '1');
error_reporting(E_ALL);
if (session_status() === PHP_SESSION_NONE) session_start();
require_once __DIR__ . '/../config/db_config.php';
require_once __DIR__ . '/../config/bitgo_config.php';
/**
* BTC ONLY. One-file sender (form + submit handler).
* - Uses BitGo Express: BITGO_API_BASE_URL
* - Uses cwallet (BTC) for wallet_add_id and encrypted_phrase (passphrase)
* - Records to transactions with user_id = 1
* - No mkdir, no extra files, no queue/worker
*/
const USER_ID_FIXED = 1;
// You can change this default if CoinGecko fails.
// Using a sensible fallback avoids “could not fetch” blocking the form.
const DEFAULT_BTC_USD_RATE = 88657.0; // fallback (update anytime)
// very small “low” fees; still allow user to override on the form
const DEFAULT_FEE_RATE_SAT_PER_KVB = 1000; // ~1 sat/vB (approx; BitGo uses sat/kvB)
const DEFAULT_MAX_FEE_SATS = 5000; // cap fee in sats
function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }
function db_one_cwallet_btc(mysqli $conn): array {
$stmt = $conn->prepare("SELECT cwallet_id, coin, wallet_add_id, wallet_add, wallet_balance, encrypted_phrase FROM cwallet WHERE UPPER(coin)='BTC' LIMIT 1");
if (!$stmt) throw new Exception('DB error: cannot query cwallet BTC');
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
$stmt->close();
if (!$row) throw new Exception('Central BTC wallet not found in cwallet table');
if (empty($row['wallet_add_id'])) throw new Exception('cwallet.wallet_add_id is missing for BTC');
if (!array_key_exists('encrypted_phrase', $row) || $row['encrypted_phrase'] === null || $row['encrypted_phrase'] === '') {
throw new Exception('cwallet.encrypted_phrase (wallet passphrase) is missing for BTC');
}
return $row;
}
function coingecko_btc_usd_rate(array &$diag): ?float {
$url = 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd';
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 8,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
// A UA reduces 403s on some hosts
'User-Agent: BitcardoBTC/1.0 (+https://bitcardo)'
],
]);
$body = curl_exec($ch);
$http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = (string)curl_error($ch);
curl_close($ch);
$diag[] = "CoinGecko http={$http} err=" . ($err ?: 'none');
if ($http !== 200 || !$body) return null;
$j = json_decode($body, true);
if (!is_array($j)) return null;
$v = $j['bitcoin']['usd'] ?? null;
if (!is_numeric($v)) return null;
$rate = (float)$v;
if ($rate <= 0) return null;
return $rate;
}
function get_btc_usd_rate(array &$diag): float {
// session cache 60s
$now = time();
$cached = $_SESSION['btc_rate_cache'] ?? null;
if (is_array($cached) && isset($cached['rate'], $cached['ts']) && ($now - (int)$cached['ts']) < 60) {
return (float)$cached['rate'];
}
$rate = coingecko_btc_usd_rate($diag);
if ($rate !== null) {
$_SESSION['btc_rate_cache'] = ['rate' => $rate, 'ts' => $now];
return $rate;
}
// fallback to last cached (even if older)
if (is_array($cached) && isset($cached['rate'])) {
$diag[] = "Using cached BTC rate (stale)";
return (float)$cached['rate'];
}
// final fallback
$diag[] = "Using DEFAULT_BTC_USD_RATE fallback";
return (float)DEFAULT_BTC_USD_RATE;
}
function bitgo_post_json(string $url, array $payload, array &$diag): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . BITGO_ACCESS_TOKEN,
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 45,
]);
$resp = curl_exec($ch);
$http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = (string)curl_error($ch);
curl_close($ch);
$diag[] = "BitGo POST http={$http} curl=" . ($err ?: 'none');
return [$http, (string)$resp, $err];
}
function insert_tx_initiated(mysqli $conn, int $userId, string $walletId, string $fromAddr, string $toAddr, float $amountBtc, string $providerMetaJson, string $noteJson): int {
// wallet_id is the USER wallet id (as you required)
$stmt = $conn->prepare("
INSERT INTO transactions
(coin, user_id, wallet_id, sender_address, receiver_address, amount, type, status, confirmation, note, provider, provider_meta, created_at, updated_at)
VALUES
('BTC', ?, ?, ?, ?, ?, 'send', 'initiated', 0, ?, 'bitgo', ?, NOW(), NOW())
");
if (!$stmt) throw new Exception('DB error: cannot insert transaction');
$stmt->bind_param('isssdss', $userId, $walletId, $fromAddr, $toAddr, $amountBtc, $noteJson, $providerMetaJson);
$stmt->execute();
$id = (int)$conn->insert_id;
$stmt->close();
return $id;
}
function update_tx_result(mysqli $conn, int $transId, string $status, ?string $txid, string $providerMetaJson): void {
$stmt = $conn->prepare("UPDATE transactions SET status=?, txid=?, provider_meta=?, updated_at=NOW() WHERE trans_id=? LIMIT 1");
if (!$stmt) return;
$txid2 = $txid ?? '';
$stmt->bind_param('sssi', $status, $txid2, $providerMetaJson, $transId);
$stmt->execute();
$stmt->close();
}
// ---- Page state
$diag = [];
$error = '';
$success = '';
$result = null;
// Load BTC cwallet once (so the page fails clearly if missing)
try {
if (!isset($conn) || !($conn instanceof mysqli)) throw new Exception('DB connection missing ($conn)');
$cw = db_one_cwallet_btc($conn);
} catch (Throwable $e) {
$cw = null;
$error = $e->getMessage();
}
// Handle submit
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $cw) {
try {
// inputs
$to = trim((string)($_POST['to'] ?? ''));
$usd = (float)($_POST['usd'] ?? 0);
$feeRate = (int)($_POST['fee_rate'] ?? DEFAULT_FEE_RATE_SAT_PER_KVB);
$maxFee = (int)($_POST['max_fee'] ?? DEFAULT_MAX_FEE_SATS);
if ($to === '') throw new Exception('Recipient address is required');
if (!($usd > 0)) throw new Exception('Amount (USD) must be greater than 0');
// rate
$rate = get_btc_usd_rate($diag);
if (!($rate > 0)) throw new Exception('BTC/USD rate unavailable');
// convert USD -> sats
$btc = $usd / $rate;
$sats = (int)round($btc * 100000000);
// avoid dust
if ($sats < 1000) {
throw new Exception('Amount too small after conversion (min ~1000 sats). Increase USD amount.');
}
// clamp fee settings to “low but not insane”
if ($feeRate < 250) $feeRate = 250;
if ($feeRate > 500000) $feeRate = 500000;
if ($maxFee < 500) $maxFee = 500;
if ($maxFee > 200000) $maxFee = 200000;
// Use USER wallet id for the transaction record (your rule)
// For this one-file tester, we’ll fetch the user’s BTC wallet_id from user_wallets.
$uid = USER_ID_FIXED;
$stmt = $conn->prepare("SELECT wallet_id, wallet_add, balance FROM user_wallets WHERE user_id=? AND UPPER(coin)='BTC' LIMIT 1");
if (!$stmt) throw new Exception('DB error: cannot read user BTC wallet');
$stmt->bind_param('i', $uid);
$stmt->execute();
$uw = $stmt->get_result()->fetch_assoc();
$stmt->close();
if (!$uw) throw new Exception('User BTC wallet not found for user_id=1');
$userWalletId = (string)$uw['wallet_id'];
$userAddr = (string)($uw['wallet_add'] ?? '');
// Build BitGo sendcoins request
$walletId = (string)$cw['wallet_add_id'];
$pass = (string)$cw['encrypted_phrase'];
// IMPORTANT: BitGo Express expects "address" (not "toAddress") for sendcoins.
$payload = [
'address' => $to,
'amount' => $sats,
'walletPassphrase' => $pass,
'maxFee' => $maxFee,
'feeRate' => $feeRate,
];
$sendUrl = rtrim(BITGO_API_BASE_URL, '/') . '/btc/wallet/' . $walletId . '/sendcoins';
// Record an initiated transaction first (so you always have a record)
$providerMeta = [
'coin_upper' => 'BTC',
'bitgo_wallet_id' => $walletId,
'to_address' => $to,
'amount_usd' => $usd,
'btc_usd_rate' => $rate,
'amount_sats' => $sats,
'fee_rate' => $feeRate,
'max_fee_sats' => $maxFee,
'api_url' => $sendUrl,
'created_at' => date('Y-m-d H:i:s'),
];
$note = [
'ui_amount_usd' => round($usd, 2),
'btc_usd_rate' => round($rate, 2),
'amount_sats' => (string)$sats,
'fee_rate' => (string)$feeRate,
'max_fee_sats' => (string)$maxFee,
];
$transId = insert_tx_initiated(
$conn,
$uid,
$userWalletId,
$userAddr,
$to,
(float)($sats / 100000000),
json_encode($providerMeta, JSON_UNESCAPED_SLASHES),
json_encode($note, JSON_UNESCAPED_SLASHES)
);
// Call BitGo Express
[$http, $resp, $curlErr] = bitgo_post_json($sendUrl, $payload, $diag);
$providerMeta['last_attempt'] = [
'http' => $http,
'curl' => $curlErr ?: null,
'resp' => $resp,
'at' => date('Y-m-d H:i:s')
];
$txid = null;
if ($http >= 200 && $http < 300 && $resp) {
$j = json_decode($resp, true);
if (is_array($j)) {
$txid = $j['transfer']['txid'] ?? ($j['txid'] ?? null);
}
}
if ($txid) {
update_tx_result($conn, $transId, 'success', (string)$txid, json_encode($providerMeta, JSON_UNESCAPED_SLASHES));
$success = "BTC sent successfully. trans_id={$transId} txid={$txid}";
} else {
// keep full response in provider_meta; mark failed for now
update_tx_result($conn, $transId, 'failed', null, json_encode($providerMeta, JSON_UNESCAPED_SLASHES));
$error = "BTC send failed. trans_id={$transId}. Check provider_meta for details.";
$result = ['http' => $http, 'resp' => $resp];
}
} catch (Throwable $e) {
$error = $e->getMessage();
}
}
// Display rate (best-effort) for the form
$displayRate = null;
if ($cw) {
try {
$displayRate = get_btc_usd_rate($diag);
} catch (Throwable $e) {
$displayRate = DEFAULT_BTC_USD_RATE;
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BTC Sender Test (Mainnet)</title>
<style>
body { font-family: Arial, sans-serif; background:#f7f7f7; margin:0; padding:20px; }
.wrap { max-width: 760px; margin: 0 auto; background:#fff; padding:18px; border-radius:10px; box-shadow:0 2px 12px rgba(0,0,0,.06); }
.row { display:flex; gap:12px; flex-wrap:wrap; }
.col { flex:1; min-width:240px; }
label { display:block; font-size:12px; color:#444; margin:10px 0 6px; }
input { width:100%; padding:10px; border:1px solid #ddd; border-radius:8px; font-size:14px; }
button { margin-top:14px; padding:10px 14px; border:0; border-radius:8px; background:#0473aa; color:#fff; font-weight:600; cursor:pointer; }
button:disabled { opacity:.6; cursor:not-allowed; }
.alert { padding:10px; border-radius:8px; margin:12px 0; }
.err { background:#ffe8e8; color:#8b0000; border:1px solid #ffcccc; }
.ok { background:#e9fff1; color:#0b6b2a; border:1px solid #bff0cf; }
.meta { font-size:12px; color:#666; }
pre { background:#111; color:#d7ffd7; padding:12px; border-radius:10px; overflow:auto; }
</style>
</head>
<body>
<div class="wrap">
<h2 style="margin:0 0 6px;">BTC Sender Test (Mainnet)</h2>
<div class="meta">
Uses BitGo Express: <b><?= h(BITGO_API_BASE_URL) ?></b><br>
User record: <b>user_id=1</b> (transactions are written with the user wallet_id)
</div>
<?php if ($error): ?>
<div class="alert err"><?= h($error) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert ok"><?= h($success) ?></div>
<?php endif; ?>
<?php if (!$cw): ?>
<div class="alert err">BTC cwallet is not ready. Fix the error above first.</div>
<?php else: ?>
<div class="alert" style="background:#f2f8ff; border:1px solid #d7e9ff; color:#124;">
BTC/USD rate (best effort): <b><?= number_format((float)$displayRate, 2) ?></b>
(fallback is <?= number_format(DEFAULT_BTC_USD_RATE, 2) ?>)
</div>
<form method="POST" autocomplete="off">
<label>Recipient BTC Address</label>
<input name="to" value="<?= h((string)($_POST['to'] ?? '')) ?>" placeholder="bc1..." required>
<label>Amount in USD (e.g., 1.00)</label>
<input name="usd" type="number" step="0.01" min="0.01" value="<?= h((string)($_POST['usd'] ?? '1.00')) ?>" required>
<div class="row">
<div class="col">
<label>feeRate (sat/kvB) — low default</label>
<input name="fee_rate" type="number" step="1" min="250" value="<?= h((string)($_POST['fee_rate'] ?? (string)DEFAULT_FEE_RATE_SAT_PER_KVB)) ?>">
</div>
<div class="col">
<label>maxFee (sats) — cap fee</label>
<input name="max_fee" type="number" step="1" min="500" value="<?= h((string)($_POST['max_fee'] ?? (string)DEFAULT_MAX_FEE_SATS)) ?>">
</div>
</div>
<button type="submit">Send BTC Now</button>
</form>
<?php endif; ?>
<?php if (!empty($diag)): ?>
<h3 style="margin:16px 0 8px;">Diagnostics</h3>
<pre><?= h(json_encode(['diag'=>$diag, 'result'=>$result], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)) ?></pre>
<?php endif; ?>
</div>
</body>
</html>
Выполнить команду
Для локальной разработки. Не используйте в интернете!