// ==UserScript==
// @name Stake.com Visual Balance & Live Stats Modifier
// @namespace http://tampermonkey.net/
// @version 9.6
// @description Adds a persistent fake BTC balance, live stat tracking with a working graph, and an integrated settings UI to visually simulate gameplay on Stake.com.
// @author XaRTeCK (Enhanced by Gemini)
// @match *://stake.com/*
// @match *://rgs.twist-rgs.com/*
// @connect stake.com
// @connect rgs.twist-rgs.com
// @license CC-BY-NC-ND-4.0
// @grant none
// @run-at document-start
// @downloadURL https://update.greasyfork.icu/scripts/549858/Stakecom%20Visual%20Balance%20%20Live%20Stats%20Modifier.user.js
// @updateURL https://update.greasyfork.icu/scripts/549858/Stakecom%20Visual%20Balance%20%20Live%20Stats%20Modifier.meta.js
// ==/UserScript==
(function() {
'use strict';
const BALANCE_STORAGE_KEY = 'stake_fake_btc_balance_v4';
const STATS_STORAGE_KEY = 'stake_fake_stats_v3';
const DEFAULT_BTC_VALUE = 0.1;
let currentFakeBet = { amount: 0, currency: null };
let fakeStats = getFakeStats();
function getFakeBtcValue() {
const savedBalance = localStorage.getItem(BALANCE_STORAGE_KEY);
return savedBalance ? parseFloat(savedBalance) : DEFAULT_BTC_VALUE;
}
// ENHANCED: This function now forces a live UI update without a page refresh.
function setFakeBtcValue(amount) {
const numericAmount = parseFloat(amount);
if (!isNaN(numericAmount) && numericAmount >= 0) {
// 1. Save the new balance to local storage
localStorage.setItem(BALANCE_STORAGE_KEY, numericAmount.toString());
// 2. Update the footer input if it exists for visual consistency
const footerInput = document.getElementById('fake-balance-input-btc');
if (footerInput) {
footerInput.value = numericAmount.toFixed(4);
}
// 3. Force a refresh of the balance display in the UI by simulating a currency switch.
const balanceToggle = document.querySelector('[data-testid="balance-toggle"] button');
if (balanceToggle) {
// First click opens the currency dropdown
balanceToggle.click();
// A small delay ensures the UI has time to react before we click again to close it.
// This double action forces the balance component to re-render with the new value.
setTimeout(() => balanceToggle.click(), 50);
}
}
}
function getFakeStats() {
const savedStats = localStorage.getItem(STATS_STORAGE_KEY);
const defaultStats = { profit: 0, wagered: 0, wins: 0, losses: 0, profitHistory: [0] };
if (savedStats) {
const parsed = JSON.parse(savedStats);
if (!Array.isArray(parsed.profitHistory) || parsed.profitHistory.length === 0) {
parsed.profitHistory = [0];
}
return { ...defaultStats, ...parsed };
}
return defaultStats;
}
function saveFakeStats(stats) {
localStorage.setItem(STATS_STORAGE_KEY, JSON.stringify(stats));
}
function resetFakeStats() {
fakeStats = { profit: 0, wagered: 0, wins: 0, losses: 0, profitHistory: [0] };
saveFakeStats(fakeStats);
updateLiveStatsDisplay();
}
function showBalanceSetupPopup() {
const popupHTML = `
`;
footerElement.insertAdjacentHTML('afterbegin', settingsHTML);
const input = document.getElementById('fake-balance-input-btc');
const savedMessage = document.getElementById('fake-balance-saved');
const resetButton = document.getElementById('reset-stats-button-footer');
let timeoutId;
input.addEventListener('input', (event) => {
setFakeBtcValue(event.target.value);
savedMessage.textContent = 'Saved!';
savedMessage.style.opacity = '1';
clearTimeout(timeoutId);
timeoutId = setTimeout(() => { savedMessage.style.opacity = '0'; }, 1500);
});
resetButton.addEventListener('click', () => {
if (confirm('Are you sure you want to reset your visual stats (Profit, Wagered, Wins, Losses, and Graph)?')) {
resetFakeStats();
savedMessage.textContent = 'Stats Reset!';
savedMessage.style.opacity = '1';
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
savedMessage.style.opacity = '0';
}, 1500);
}
});
}
function updateStatsAndHistory(betAmount, payout) {
fakeStats.wagered += betAmount;
if (payout > 0) {
fakeStats.wins++;
fakeStats.profit += payout - betAmount;
} else {
fakeStats.losses++;
fakeStats.profit -= betAmount;
}
fakeStats.profitHistory.push(fakeStats.profit);
saveFakeStats(fakeStats);
updateLiveStatsDisplay();
}
const originalFetch = window.fetch;
window.fetch = async function(url, options) {
const FAKE_BTC_BALANCE = getFakeBtcValue();
const FAKE_PROVIDER_BALANCE = FAKE_BTC_BALANCE * 100_000_000;
const requestUrl = new URL(url.toString(), window.location.origin);
const host = requestUrl.hostname;
const path = requestUrl.pathname;
if (host.includes('rgs.twist-rgs.com') && path.includes('/wallet/authenticate')) {
const response = await originalFetch(url, options);
const data = await response.clone().json();
if (data.balance) data.balance.amount = FAKE_PROVIDER_BALANCE;
return new Response(JSON.stringify(data), { status: 200, headers: response.headers });
}
if (host.includes('stake.com') && path.includes('/_api/graphql') && options?.body) {
let requestBody;
try { requestBody = JSON.parse(options.body); } catch (e) { return originalFetch(url, options); }
let modifiedOptions = options;
if (requestBody.operationName === 'UserBalances') {
const response = await originalFetch(url, options);
const data = await response.clone().json();
const btcBalance = data?.data?.user?.balances.find(b => b.available.currency === 'btc');
if (btcBalance) btcBalance.available.amount = FAKE_BTC_BALANCE;
return new Response(JSON.stringify(data), { status: response.status, headers: response.headers });
}
if (requestBody.query?.includes('mutation') && requestBody.variables?.amount > 0) {
currentFakeBet = { amount: requestBody.variables.amount, currency: requestBody.variables.currency };
const modifiedBody = JSON.parse(JSON.stringify(requestBody));
modifiedBody.variables.amount = 0;
modifiedOptions = { ...options, body: JSON.stringify(modifiedBody) };
}
const response = await originalFetch(url, modifiedOptions);
const responseClone = response.clone();
try {
const data = await response.json();
if (data.data) {
const gameDataKey = Object.keys(data.data).find(key => data.data[key] && typeof data.data[key] === 'object' && 'amount' in data.data[key]);
if (gameDataKey && currentFakeBet.amount > 0) {
const gameData = data.data[gameDataKey];
gameData.amount = currentFakeBet.amount;
gameData.payout = (gameData.payoutMultiplier || 0) * currentFakeBet.amount;
updateStatsAndHistory(currentFakeBet.amount, gameData.payout);
if (!gameData.active) currentFakeBet = { amount: 0, currency: null };
return new Response(JSON.stringify(data), { status: 200, headers: response.headers });
}
}
return responseClone;
} catch (e) { return responseClone; }
}
if (host.includes('stake.com') && path.startsWith('/_api/casino/')) {
let modifiedOptions = options;
if (/\/(bet|roll|bonus)$/.test(path) && options?.body) {
try {
const originalRequestBody = JSON.parse(options.body);
const modifiedBody = { ...originalRequestBody };
let totalAmount = 0;
if (path.includes('/roulette/bet')) {
['colors', 'parities', 'dozens', 'numbers', 'columns', 'halves'].forEach(key => {
if (Array.isArray(modifiedBody[key])) modifiedBody[key].forEach(bet => { totalAmount += bet.amount; bet.amount = 0; });
});
} else if (originalRequestBody.amount > 0) {
totalAmount = originalRequestBody.amount;
modifiedBody.amount = 0;
}
if (totalAmount > 0) {
currentFakeBet = { amount: totalAmount, currency: originalRequestBody.currency };
modifiedOptions = { ...options, body: JSON.stringify(modifiedBody) };
}
} catch (e) {}
}
const response = await originalFetch(url, modifiedOptions);
const responseClone = response.clone();
try {
const data = await response.json();
const gameDataKey = Object.keys(data).find(key => data[key] && typeof data[key] === 'object' && 'amount' in data[key]);
if (gameDataKey && currentFakeBet.amount > 0 && data[gameDataKey].currency === currentFakeBet.currency) {
const gameData = data[gameDataKey];
gameData.amount = currentFakeBet.amount;
gameData.payout = (gameData.payoutMultiplier || 0) * currentFakeBet.amount;
updateStatsAndHistory(currentFakeBet.amount, gameData.payout);
if (gameData.state?.rounds) gameData.state.rounds.forEach(r => { if ('amount' in r) r.amount = currentFakeBet.amount; });
if (!gameData.active) currentFakeBet = { amount: 0, currency: null };
return new Response(JSON.stringify(data), { status: 200, headers: response.headers });
}
return responseClone;
} catch (e) { return responseClone; }
}
return originalFetch(url, options);
};
window.addEventListener('DOMContentLoaded', () => {
showBalanceSetupPopup();
const mainObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
if (node.matches('footer[data-testid="footer"]') && !document.getElementById('fake-balance-settings')) {
injectBalanceSettings(node);
}
if (node.matches('div.draggable') && node.querySelector('[data-testid="bets-stats-profit"]')) {
setTimeout(() => updateLiveStatsDisplay(), 100);
}
const resetButton = node.matches('[data-testid="draggable-stats-reset"]') ? node : node.querySelector('[data-testid="draggable-stats-reset"]');
if (resetButton && !resetButton.dataset.scriptListenerAttached) {
resetButton.dataset.scriptListenerAttached = 'true';
resetButton.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
if (confirm('Are you sure you want to reset your visual stats? This will clear the graph and all tracked data.')) {
resetFakeStats();
}
}, true);
}
}
}
});
mainObserver.observe(document.body, { childList: true, subtree: true });
});
})();