PHP WebShell
Текущая директория: /var/www/bitcardoApp/user/crypto
Просмотр файла: swap.php
<?php
ob_start();
require_once "../../config/db_config.php";
if (!isset($_SESSION)) { session_start(); }
if (!isset($_SESSION['user_id'])) { header("Location: ../../auth/login.php"); exit(); }
$user_id = (int)$_SESSION['user_id'];
function get_setting(mysqli $conn, string $key, $default = null) {
$sql = "SELECT setting_value FROM site_settings WHERE setting_key=? LIMIT 1";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $key);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
$stmt->close();
return $row['setting_value'] ?? $default;
}
function table_has_column(mysqli $conn, string $table, string $column): bool {
$sql = "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?
LIMIT 1";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ss", $table, $column);
$stmt->execute();
$res = $stmt->get_result();
$ok = $res && $res->num_rows > 0;
$stmt->close();
return $ok;
}
function norm_coin(string $coin): string {
$coin = strtoupper(trim($coin));
$map = [
'USDT-TRC20' => 'USDT',
'TRON' => 'TRX',
];
return $map[$coin] ?? $coin;
}
function usd_price(mysqli $conn, string $coin): float {
$coin = norm_coin($coin);
$stmt = $conn->prepare("SELECT rate FROM online_coin_rates WHERE UPPER(coin)=? LIMIT 1");
$stmt->bind_param("s", $coin);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
$stmt->close();
$usd = (float)($row['rate'] ?? 0.0);
if ($usd > 0) return $usd;
if (table_has_column($conn,'coin_rates','usd_price')) {
$stmt = $conn->prepare("SELECT usd_price FROM coin_rates WHERE UPPER(coin)=? LIMIT 1");
$stmt->bind_param("s", $coin);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
$stmt->close();
return (float)($row['usd_price'] ?? 0.0);
}
return 0.0;
}
function min_swap(mysqli $conn, string $coin): float {
$coin = norm_coin($coin);
$stmt = $conn->prepare("SELECT COALESCE(min_swap,0) AS m FROM coin_rates WHERE UPPER(coin)=? LIMIT 1");
$stmt->bind_param("s", $coin);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
$stmt->close();
return (float)($row['m'] ?? 0.0);
}
/* ---------- AJAX quote ---------- */
if (isset($_GET['ajax']) && $_GET['ajax'] === 'quote') {
ini_set('display_errors', '0');
while (ob_get_level() > 0) { ob_end_clean(); }
header('Content-Type: application/json; charset=utf-8');
$from = strtoupper($_GET['from'] ?? '');
$to = strtoupper($_GET['to'] ?? '');
$amt = (float)($_GET['amount'] ?? 0);
if (!$from || !$to || $amt <= 0) { echo json_encode(['error'=>'Invalid input']); exit; }
$factor = (float) get_setting($conn, 'swap_coin_to_usdt_factor', '0.8000');
if ($factor < 0.10) $factor = 0.10;
if ($factor > 1.00) $factor = 1.00;
$min_cfg = min_swap($conn, $from);
if ($min_cfg > 0) {
if ($from === 'NGN') {
if ($amt < $min_cfg) { echo json_encode(['error'=>"Min ₦".number_format($min_cfg,0)]); exit; }
} else {
if ($amt < $min_cfg) { echo json_encode(['error'=>"Min $".number_format($min_cfg,2)]); exit; }
}
}
$to_is_usdt = in_array($to, ['USDT','USDT-TRC20'], true);
// COIN->USDT: user enters USD, payout USDT = USD * factor
if ($from !== 'NGN' && $to_is_usdt) {
$to_amount = $amt * $factor;
echo json_encode(['rate'=>$factor, 'to_amount'=>round($to_amount, 8)]);
exit;
}
// COIN->COIN: USD -> to coin
if ($from !== 'NGN' && $to !== 'NGN') {
$p = usd_price($conn, $to);
if ($p <= 0) { echo json_encode(['error'=>"Missing USD price for {$to}. Run cron."]); exit; }
$to_amount = $amt / $p;
echo json_encode(['rate'=>($to_amount/$amt), 'to_amount'=>round($to_amount, 8)]);
exit;
}
// NGN->COIN
if ($from === 'NGN' && $to !== 'NGN') {
$to_n = norm_coin($to);
$stmt = $conn->prepare("SELECT buy_rate FROM coin_rates WHERE UPPER(coin)=? LIMIT 1");
$stmt->bind_param("s", $to_n);
$stmt->execute();
$r = $stmt->get_result()->fetch_assoc();
$stmt->close();
$ngn_per_usd = (float)($r['buy_rate'] ?? 0);
if ($ngn_per_usd <= 0) { echo json_encode(['error'=>"Missing NGN/$ buy rate for {$to}"]); exit; }
$usd = $amt / $ngn_per_usd;
$p = usd_price($conn, $to);
if ($p <= 0) { echo json_encode(['error'=>"Missing USD price for {$to}. Run cron."]); exit; }
$to_amount = $usd / $p;
echo json_encode(['rate'=>($to_amount/$amt), 'to_amount'=>round($to_amount, 8)]);
exit;
}
// COIN->NGN
if ($from !== 'NGN' && $to === 'NGN') {
$from_n = norm_coin($from);
$stmt = $conn->prepare("SELECT sell_rate FROM coin_rates WHERE UPPER(coin)=? LIMIT 1");
$stmt->bind_param("s", $from_n);
$stmt->execute();
$r = $stmt->get_result()->fetch_assoc();
$stmt->close();
$ngn_per_usd = (float)($r['sell_rate'] ?? 0);
if ($ngn_per_usd <= 0) { echo json_encode(['error'=>"Missing NGN/$ sell rate for {$from}"]); exit; }
$to_amount = $amt * $ngn_per_usd;
echo json_encode(['rate'=>($to_amount/$amt), 'to_amount'=>round($to_amount, 2)]);
exit;
}
echo json_encode(['error'=>'Invalid pair']);
exit;
}
/* ---------- Page data ---------- */
$hasWalletStatus = table_has_column($conn, 'user_wallets', 'wallet_status');
$activeClause = $hasWalletStatus ? " AND (LOWER(uw.wallet_status)='active' OR uw.wallet_status IS NULL) " : "";
/* You send */
$sendCoins = [];
$balances = [];
$min_swaps = [];
$usdPrices = [];
$stmt = $conn->prepare("SELECT uw.coin, uw.balance FROM user_wallets uw WHERE uw.user_id=? $activeClause AND uw.balance>0");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$rs = $stmt->get_result();
while ($row = $rs->fetch_assoc()) {
$c = strtoupper($row['coin']);
$sendCoins[] = $c;
$balances[$c] = (float)$row['balance'];
$min_swaps[$c] = min_swap($conn, $c);
$usdPrices[$c] = ($c === 'NGN') ? 0.0 : usd_price($conn, $c);
}
$stmt->close();
/* You get wallets */
$activeToCoins = [];
$stmt = $conn->prepare("SELECT DISTINCT UPPER(uw.coin) AS coin FROM user_wallets uw WHERE uw.user_id=? $activeClause");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$rt = $stmt->get_result();
while ($row = $rt->fetch_assoc()) { $activeToCoins[] = $row['coin']; }
$stmt->close();
/* allowed directions */
$directions = [];
$d = $conn->query("SELECT from_coin,to_coin FROM swap_directions WHERE is_active=1");
while ($row = $d->fetch_assoc()) {
$f = strtoupper($row['from_coin']);
$t = strtoupper($row['to_coin']);
$directions[$f][] = $t;
}
$quotePath = $_SERVER['PHP_SELF'];
?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<div class="py-2">
<form action="../../models/crypto/swap.php" method="POST">
<div class="mb-3">
<label class="form-label ms-2 d-flex justify-content-between align-items-center">
<span>You send</span>
<span id="balanceMsg" class="text-danger small d-none"></span>
</label>
<div class="input-group">
<input type="number" name="fromAmount" id="amountFrom" class="form-control" placeholder="100.00" step="0.01" required>
<button class="btn btn-white-outline btn-sm pyx-0 border" type="button" id="useMaxBtn">Max</button>
<select id="currencyFrom" name="currencyFrom" class="form-select" style="max-width:140px;" required>
<?php foreach($sendCoins as $i=>$coin): ?>
<option value="<?=$coin?>" <?=$i===0?'selected':''?>><?=$coin?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<span class="small text-muted ms-1 mt-1" id="entryHint"></span> |
<span class="small text-muted ms-1 mt-1" id="minSwapText"></span>
</div>
</div>
<div class="mb-3 mt-3">
<label class="form-label ms-2">You Get</label>
<div class="input-group">
<input type="number" id="amountTo" name="toAmount" class="form-control" placeholder="0.00" readonly>
<select id="currencyTo" name="currencyTo" class="form-select" style="max-width:140px;" required></select>
</div>
</div>
<div class="d-grid mt-4">
<button class="btn btn-primary" id="convertBtn">Convert</button>
</div>
<div style="font-size: small; text-align: center;">
This action is instant. Wallets are updated immediately.<br>
<a href="#" class="text-decoration-none text-secondary"><small>Terms & Condtions Apply</small></a>
</div>
</form>
<script>
const balances = <?= json_encode($balances) ?>;
const minSwaps = <?= json_encode($min_swaps) ?>;
const usdPrices = <?= json_encode($usdPrices) ?>;
const activeToCoins = new Set(<?= json_encode($activeToCoins) ?>);
const allowedDirs = <?= json_encode($directions) ?>;
const quotePath = <?= json_encode($quotePath) ?>;
function variants(c){
c = (c||'').toUpperCase();
const out = new Set([c]);
if (c === 'USDT') out.add('USDT-TRC20');
if (c === 'USDT-TRC20') out.add('USDT');
if (c === 'TRON') out.add('TRX');
if (c === 'TRX') out.add('TRON');
return Array.from(out);
}
function fmtMoney(n, d){
return Number(n).toLocaleString(undefined, { minimumFractionDigits:d, maximumFractionDigits:d });
}
function setInsufficient(msgText){
const btn = document.getElementById('convertBtn');
const msg = document.getElementById('balanceMsg');
const out = document.getElementById('amountTo');
btn.disabled = true;
out.value = '';
msg.textContent = msgText || 'Insufficient balance';
msg.classList.remove('d-none');
}
function clearBalanceMsg(){
const msg = document.getElementById('balanceMsg');
msg.classList.add('d-none');
msg.textContent = '';
}
function maxSpendableUsd(from){
// NGN returns NGN balance, crypto returns USD equivalent of coin balance
const bal = parseFloat(balances[from] || 0);
if (from === 'NGN') return bal;
const p = parseFloat(usdPrices[from] || 0);
if (bal <= 0 || p <= 0) return 0;
return bal * p;
}
function updateEntryModeUI(){
const from = document.getElementById('currencyFrom').value;
const input = document.getElementById('amountFrom');
const hint = document.getElementById('entryHint');
if (from === 'NGN') {
input.step = '0.01';
input.placeholder = '1000.00';
hint.textContent = 'Enter exact amount in ₦ (Naira).';
} else {
input.step = '0.01';
input.placeholder = '100.00';
hint.textContent = 'Enter USD value (e.g., 100).';
}
}
function updateMinText(){
const from = document.getElementById('currencyFrom').value;
const m = parseFloat(minSwaps[from] || 0);
const el = document.getElementById('minSwapText');
if (!m || m <= 0) { el.textContent = ''; return; }
el.textContent = (from === 'NGN') ? `Min ₦${fmtMoney(m,0)}` : `Min $${fmtMoney(m,2)}`;
}
function updateCurrencyTo(){
const from = document.getElementById('currencyFrom').value;
const toSel = document.getElementById('currencyTo');
toSel.innerHTML = '';
const fromVars = variants(from);
const allowedSet = new Set();
fromVars.forEach(fv => {
(allowedDirs[fv] || []).forEach(tc => allowedSet.add(tc));
});
const filtered = Array.from(allowedSet).filter(tc => {
const tcVars = variants(tc);
return tcVars.some(v => activeToCoins.has(v));
});
if (!filtered.length) {
const o = document.createElement('option');
o.value = '';
o.text = 'Not available';
o.disabled = true;
o.selected = true;
toSel.appendChild(o);
} else {
filtered.forEach(c => {
const o = document.createElement('option');
o.value = c;
o.text = c;
toSel.appendChild(o);
});
toSel.value = filtered[0];
}
updateEntryModeUI();
updateMinText();
getSwapQuote();
}
function getSwapQuote(){
const from = document.getElementById('currencyFrom').value;
const to = document.getElementById('currencyTo').value;
const amount = parseFloat(document.getElementById('amountFrom').value);
const btn = document.getElementById('convertBtn');
const out = document.getElementById('amountTo');
if (!from || !to || !isFinite(amount) || amount <= 0) {
out.value = '';
btn.disabled = true;
clearBalanceMsg();
return;
}
// Balance enforcement:
// - NGN: amount <= NGN balance
// - Crypto: USD amount <= (coin_balance * coin_usd_price)
const maxVal = maxSpendableUsd(from);
if (maxVal <= 0) {
// if crypto price missing, prevent swaps to avoid accidental negative
if (from !== 'NGN') return setInsufficient('Price unavailable. Try again later.');
return setInsufficient('Insufficient balance');
}
if (amount > maxVal + 1e-9) {
return setInsufficient('Insufficient balance');
}
clearBalanceMsg();
btn.disabled = false;
const url = `${quotePath}?ajax=quote&from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&amount=${encodeURIComponent(amount)}`;
fetch(url, { headers: { 'Accept': 'application/json' } })
.then(async (r) => {
const text = await r.text();
try { return JSON.parse(text); } catch (e) { throw new Error(text.slice(0, 300)); }
})
.then(d => {
if (d.error) {
setInsufficient(d.error);
return;
}
out.value = d.to_amount ?? '0.00';
})
.catch(() => {
setInsufficient('Quote failed');
});
}
document.getElementById('amountFrom').addEventListener('input', getSwapQuote);
document.getElementById('currencyFrom').addEventListener('change', updateCurrencyTo);
document.getElementById('currencyTo').addEventListener('change', getSwapQuote);
document.addEventListener('DOMContentLoaded', () => setTimeout(updateCurrencyTo, 0));
// Max: NGN => balance; Crypto => max USD = coinBal * usdPrice
document.getElementById('useMaxBtn').addEventListener('click', () => {
const from = document.getElementById('currencyFrom').value;
const input = document.getElementById('amountFrom');
const maxVal = maxSpendableUsd(from);
input.value = (from === 'NGN') ? maxVal.toFixed(2) : maxVal.toFixed(2);
getSwapQuote();
});
</script>
</div>
Выполнить команду
Для локальной разработки. Не используйте в интернете!