Add Demo Trading tab and functional demo trade panel
// ==UserScript==
// @name Web3 OKX Demo Trade Addon
// @namespace http://tampermonkey.net/
// @version 2.4
// @description Add Demo Trading tab and functional demo trade panel
// @author mamiis
// @match https://web3.okx.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let demoMode = false;
let demoPanelVisible = false;
let currentTokenPrice = 0;
let currentSolPrice = 0;
let currentTokenSymbol = 'TOKEN';
let currentTokenName = '';
let currentTokenImage = '';
let totalPnL = 0;
let totalPnLPercent = 0;
let tokenPnL = 0;
let tokenPnLPercent = 0;
let activeTokenRealizedPnL = 0;
let showFiat = true;
let pnlSectionVisible = true; // PnL section
const DEBUG_RESET_WITH_USD_TARGET_ENABLED = true;
const DEBUG_RESET_USD_TARGET_AMOUNT = 21.61;
const DEBUG_RESET_SOL_FALLBACK_AMOUNT = 1;
// LocalStorage'dan ayarları yükle
let currentSettings = JSON.parse(localStorage.getItem('demoSettings')) || {
buySlippage: '15%',
sellSlippage: '20%',
customBuySlippage: false,
customSellSlippage: false,
customBuyAmounts: false,
customSellPercents: false,
buyAmounts: ['0.3', '0.6', '0.8', '1'],
sellPercents: ['25%', '50%', '75%', '100%'],
serviceFee: '0.68%',
buyFixedFeeSol: '0.00005',
sellFixedFeeSol: '0.00005'
};
const defaultBuyAmounts = ['0.3', '0.6', '0.8', '1'];
const defaultSellPercents = ['25%', '50%', '75%', '100%'];
if (!Array.isArray(currentSettings.buyAmounts)) {
currentSettings.buyAmounts = [...defaultBuyAmounts];
}
if (!Array.isArray(currentSettings.sellPercents)) {
currentSettings.sellPercents = [...defaultSellPercents];
}
currentSettings.buyAmounts = currentSettings.buyAmounts
.map(v => String(v || '').trim())
.filter(Boolean)
.slice(0, 4);
while (currentSettings.buyAmounts.length < 4) {
currentSettings.buyAmounts.push(defaultBuyAmounts[currentSettings.buyAmounts.length]);
}
currentSettings.sellPercents = currentSettings.sellPercents
.map(v => String(v || '').trim())
.filter(Boolean)
.slice(0, 4);
while (currentSettings.sellPercents.length < 4) {
currentSettings.sellPercents.push(defaultSellPercents[currentSettings.sellPercents.length]);
}
currentSettings.customBuySlippage = Boolean(currentSettings.customBuySlippage);
currentSettings.customSellSlippage = Boolean(currentSettings.customSellSlippage);
currentSettings.customBuyAmounts = Boolean(currentSettings.customBuyAmounts);
currentSettings.customSellPercents = Boolean(currentSettings.customSellPercents);
currentSettings.buyFixedFeeSol = normalizeFixedFeeSolText(currentSettings.buyFixedFeeSol, '0.00005');
currentSettings.sellFixedFeeSol = normalizeFixedFeeSolText(currentSettings.sellFixedFeeSol, '0.00005');
// Customize amounts state
let customizeMode = false;
let hotkeysEnabled = false;
let hotkeysSpacePressed = false;
let hotkeysListenersBound = false;
let tempBuyAmounts = [...currentSettings.buyAmounts];
let tempSellPercents = [...currentSettings.sellPercents];
let tempBuySlippage = currentSettings.buySlippage;
let tempSellSlippage = currentSettings.sellSlippage;
let tempBuyFixedFeeSol = currentSettings.buyFixedFeeSol;
let tempSellFixedFeeSol = currentSettings.sellFixedFeeSol;
let tempServiceFee = currentSettings.serviceFee;
let customizeExitAnimationPending = false;
// Sayfa değişikliklerini izlemek için observer
let pageObserver = null;
// Token bilgilerini takip etmek için yeni değişkenler
let demoPortfolio = JSON.parse(localStorage.getItem('demoPortfolio')) || {
'SOL': {
amount: 1,
avgPrice: 100,
totalInvested: 0,
totalSold: 0,
totalInvestedSol: 0,
totalSoldSol: 0,
realizedPnL: 0
}
};
let totalRealizedPnL = parseFloat(localStorage.getItem('totalRealizedPnL')) || 0;
let totalTradeVolume = parseFloat(localStorage.getItem('totalTradeVolume')) || 0;
let tradeHistory = JSON.parse(localStorage.getItem('tradeHistory')) || [];
// PnL deÄŸerleri
let boughtAmount = 0;
let soldAmount = 0;
let boughtAmountSol = 0;
let soldAmountSol = 0;
let balanceAmount = 0;
let tpnlAmount = 0;
let demoBalance = 0;
// Duplicate önleme için kontrol değişkeni
let isInitializing = false;
let lastTokenCA = '';
const DEBUG_LOGS = false;
// Debug: reset sırasında SOL bakiyesini sabit SOL yerine hedef USD karşılığına çevir.
const SOL_TOKEN_IMAGE = 'https://web3.okx.com/cdn/web3/currency/token/501-11111111111111111111111111111111-1.png/type=default_350_0?v=1734571825920';
const SOL_WALLET_ICON = 'https://web3.okx.com/cdn/wallet/logo/SOL-20220525.png';
const DEFAULT_TOKEN_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
const PANEL_UPDATE_THROTTLE_MS = 100;
const PNL_UPDATE_THROTTLE_MS = 100;
const SOL_PRICE_UPDATE_INTERVAL_MS = 300;
const SOL_API_FALLBACK_INTERVAL_MS = 300;
const SETTINGS_SYNC_INTERVAL_MS = 1200;
const TRADE_DELAY_MIN_SECONDS = 0.35;
const TRADE_DELAY_MAX_SECONDS = 0.60;
const DROPDOWN_VERTICAL_OFFSET_PX = -16;
const TRADE_FEE_MIN_SOL = 0.000001;
const TRADE_FEE_MAX_SOL = 0.01;
// Hız/gecikme oranlaması yalnızca 0.000001 - 0.0001 fee aralığında yapılır.
const TRADE_SPEED_FEE_MAX_SOL = 0.0001;
const NUMERIC_ZERO_EPSILON = 0.0000001;
const TOKEN_AMOUNT_ZERO_EPSILON = 0.00000001;
const DEMO_BUTTON_BASE_CLASSNAME = 'dex-plain-button peM48g__dex MovYo___dex q7TVYE__dex demo-mode-toggle';
let lastPanelUpdateAt = 0;
let lastPnLDisplayUpdateAt = 0;
let pendingDemoComponentsUpdate = null;
let lastTpnlColorSign = null;
let lastRpnlColorSign = null;
let lastUpnlColorSign = null;
let lastSolApiFetchAt = 0;
let lastSettingsSyncAt = 0;
let isPanelDragging = false;
let pendingPanelRebuildAfterDrag = false;
let pendingDropdownRefreshAfterDrag = false;
let pendingPanelValueRefreshAfterDrag = false;
let tokenChangeUiRefreshTimeout = null;
function debugLog(...args) {
if (DEBUG_LOGS) {
console.log(...args);
}
}
function normalizeNearZero(value, epsilon = NUMERIC_ZERO_EPSILON) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return 0;
return Math.abs(numeric) < epsilon ? 0 : numeric;
}
function isEffectivelyZero(value, epsilon = NUMERIC_ZERO_EPSILON) {
return normalizeNearZero(value, epsilon) === 0;
}
function getImageUrlFromElement(element) {
if (!element) return null;
const fromSource = element.closest('picture')?.querySelector('source')?.getAttribute('srcset');
const fromCurrentSrc = element.currentSrc;
const fromSrc = element.getAttribute('src') || element.src;
const fromDataSrc = element.getAttribute('data-src') || element.getAttribute('data-lazy-src');
const candidates = [fromSource, fromCurrentSrc, fromSrc, fromDataSrc].filter(Boolean);
for (const raw of candidates) {
let url = String(raw).trim();
if (!url) continue;
// srcset birden fazla URL içeriyorsa sadece ilk URL'yi al.
// Query içindeki virgül karakterlerini bozmayacak şekilde split edilir.
if (/\s*,\s*https?:\/\//i.test(url)) {
url = url.split(/\s*,\s*(?=https?:\/\/)/i)[0].trim();
}
// "... 1x" / "... 2x" / "... 640w" descriptor'larını temizle
url = url
.replace(/\s+\d+(?:\.\d+)?x$/i, '')
.replace(/\s+\d+w$/i, '')
.trim();
if (url.startsWith('//')) {
url = `https:${url}`;
} else if (url.startsWith('/')) {
try {
url = new URL(url, window.location.origin).href;
} catch (_) {}
}
url = url.replace(/&/gi, '&');
const lower = url.toLowerCase();
if (!/^https?:\/\//.test(lower)) continue;
if (lower.includes('/network/') || lower.includes('network-logo')) continue;
if (lower.includes('blank') || lower.includes('placeholder')) continue;
return url;
}
return null;
}
function buildImageSrcset(url) {
if (!url) return '';
const normalized = normalizeImageUrl(url);
if (!normalized) return '';
if (!/^https?:\/\//i.test(normalized)) return '';
// x-oss-process parametresini tekilleştir ve stabil bir srcset üret
try {
const parsed = new URL(normalized);
parsed.searchParams.delete('x-oss-process');
parsed.searchParams.append('x-oss-process', 'image/format,webp/ignore-error,1');
return parsed.toString();
} catch (_) {
const withoutProcess = normalized
.replace(/([?&])x-oss-process=[^&]*/gi, '$1')
.replace(/[?&]$/, '');
const separator = withoutProcess.includes('?') ? '&' : '?';
return `${withoutProcess}${separator}x-oss-process=image/format,webp/ignore-error,1`;
}
}
function normalizeImageUrl(url) {
if (typeof url !== 'string') return '';
let normalized = url.trim();
if (!normalized) return '';
// srcset birden fazla URL içeriyorsa sadece ilk URL'yi al.
if (/\s*,\s*https?:\/\//i.test(normalized)) {
normalized = normalized.split(/\s*,\s*(?=https?:\/\/)/i)[0].trim();
}
normalized = normalized
.replace(/&/gi, '&')
.replace(/\s+\d+(?:\.\d+)?x$/i, '')
.replace(/\s+\d+w$/i, '')
.replace(/\?&/g, '?')
.replace(/&&+/g, '&')
.trim();
if (normalized.startsWith('//')) {
normalized = `https:${normalized}`;
} else if (normalized.startsWith('/')) {
try {
normalized = new URL(normalized, window.location.origin).href;
} catch (_) {}
}
return normalized;
}
function isLikelyTokenImageUrl(url) {
const normalized = normalizeImageUrl(url);
if (!normalized) return false;
const lower = normalized.toLowerCase();
if (!/^https?:\/\//.test(lower)) return false;
if (!lower.includes('/currency/token/')) return false;
if (lower.includes('/network/') || lower.includes('network-logo')) return false;
if (lower.includes('11111111111111111111111111111111')) return false;
return true;
}
function resolveTokenImageUrl(...candidates) {
// Önce gerçek token icon URL'lerini tercih et
for (const candidate of candidates) {
const normalized = normalizeImageUrl(candidate);
if (!normalized) continue;
if (isLikelyTokenImageUrl(normalized)) return normalized;
}
// Sonra herhangi bir geçerli string fallback'ini kullan
for (const candidate of candidates) {
const normalized = normalizeImageUrl(candidate);
if (normalized) return normalized;
}
return normalizeImageUrl(DEFAULT_TOKEN_IMAGE) || DEFAULT_TOKEN_IMAGE;
}
function setTextIfChanged(element, nextText) {
if (!element || typeof nextText !== 'string') return;
if (element.textContent !== nextText) {
element.textContent = nextText;
}
}
function setClassIfChanged(element, nextClass) {
if (!element || typeof nextClass !== 'string') return;
if (element.className !== nextClass) {
element.className = nextClass;
}
}
function setStyleIfChanged(element, styleName, nextValue) {
if (!element || !styleName) return;
if (element.style[styleName] !== nextValue) {
element.style[styleName] = nextValue;
}
}
function getHotkeyBadgeHTML(key) {
const safeKey = String(key || '').trim().slice(0, 1).toUpperCase();
return `<span class="demo-hotkey-badge" style="display: inline-flex; align-items: center; justify-content: center; min-width: 32px; height: 28px; padding: 0 8px; margin-left: -2px; border: none; border-radius: 6px; clip-path: inset(0 0 0 2px); font-size: 16px; font-weight: 700; line-height: 1; color: #dedede; background: #383838; text-transform: uppercase; letter-spacing: 0.2px; box-sizing: border-box; flex-shrink: 0;">${safeKey}</span>`;
}
function isEditableTarget(target) {
if (!target || !(target instanceof Element)) return false;
if (target.isContentEditable) return true;
const tagName = (target.tagName || '').toLowerCase();
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
return true;
}
return Boolean(target.closest('input, textarea, select, [contenteditable="true"]'));
}
function rebuildDemoPanelPreservingPosition() {
if (!demoPanelVisible) return;
if (isPanelDragging) {
pendingPanelRebuildAfterDrag = true;
return;
}
const currentPanel = document.querySelector('.demo-trade-panel');
let panelStyle = {};
if (currentPanel) {
panelStyle = {
left: currentPanel.style.left,
top: currentPanel.style.top
};
}
showDemoPanel(true);
if (panelStyle.left && panelStyle.top) {
const newPanel = document.querySelector('.demo-trade-panel');
if (newPanel) {
newPanel.style.left = panelStyle.left;
newPanel.style.top = panelStyle.top;
}
}
}
function requestPanelRebuildPreservingPosition() {
if (isPanelDragging) {
pendingPanelRebuildAfterDrag = true;
return;
}
rebuildDemoPanelPreservingPosition();
}
function queueDropdownRefreshAfterDrag() {
pendingDropdownRefreshAfterDrag = true;
pendingPanelValueRefreshAfterDrag = true;
}
function queuePanelValueRefreshAfterDrag() {
pendingPanelValueRefreshAfterDrag = true;
}
function flushDeferredPanelActionsAfterDrag() {
if (isPanelDragging || !demoPanelVisible) return;
if (pendingPanelRebuildAfterDrag) {
pendingPanelRebuildAfterDrag = false;
pendingDropdownRefreshAfterDrag = false;
pendingPanelValueRefreshAfterDrag = false;
rebuildDemoPanelPreservingPosition();
return;
}
if (pendingDropdownRefreshAfterDrag) {
pendingDropdownRefreshAfterDrag = false;
setupDropdowns();
setupDropdownEvents();
}
if (pendingPanelValueRefreshAfterDrag) {
pendingPanelValueRefreshAfterDrag = false;
lastPanelUpdateAt = 0;
lastPnLDisplayUpdateAt = 0;
updateDemoPanelValues();
}
}
function triggerHotkeyActionByKey(rawKey) {
const key = String(rawKey || '').toLowerCase();
const buyKeyMap = { q: 0, w: 1, e: 2, r: 3 };
const sellKeyMap = { a: 0, s: 1, d: 2, f: 3 };
if (Object.prototype.hasOwnProperty.call(buyKeyMap, key)) {
const buyButtons = document.querySelectorAll('.demo-buy-quick');
const buyButton = buyButtons[buyKeyMap[key]];
if (buyButton) {
buyButton.click();
}
return;
}
if (Object.prototype.hasOwnProperty.call(sellKeyMap, key)) {
const sellButtons = document.querySelectorAll('.demo-sell-quick');
const sellButton = sellButtons[sellKeyMap[key]];
if (sellButton) {
sellButton.click();
}
}
}
function setupKeyboardHotkeys() {
if (hotkeysListenersBound) return;
hotkeysListenersBound = true;
document.addEventListener('keydown', function(e) {
const isSpaceKey = e.code === 'Space' || e.key === ' ' || e.key === 'Spacebar';
if (isSpaceKey) {
if (!hotkeysEnabled || isEditableTarget(e.target)) return;
if (!hotkeysSpacePressed) {
hotkeysSpacePressed = true;
if (demoPanelVisible && !customizeMode) {
requestPanelRebuildPreservingPosition();
}
}
e.preventDefault();
return;
}
if (!hotkeysEnabled || !hotkeysSpacePressed || !demoPanelVisible || customizeMode) return;
if (isEditableTarget(e.target)) return;
const pressedKey = String(e.key || '').toLowerCase();
if (!'qwerasdf'.includes(pressedKey)) return;
if (e.repeat) {
e.preventDefault();
return;
}
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
triggerHotkeyActionByKey(pressedKey);
}, true);
document.addEventListener('keyup', function(e) {
const isSpaceKey = e.code === 'Space' || e.key === ' ' || e.key === 'Spacebar';
if (!isSpaceKey) return;
if (hotkeysSpacePressed) {
hotkeysSpacePressed = false;
if (demoPanelVisible && !customizeMode) {
requestPanelRebuildPreservingPosition();
}
}
}, true);
window.addEventListener('blur', function() {
if (!hotkeysSpacePressed) return;
hotkeysSpacePressed = false;
if (demoPanelVisible && !customizeMode) {
requestPanelRebuildPreservingPosition();
}
});
}
function getCurrencyToggleIconHTML(isFiatMode) {
if (isFiatMode) {
return '<i class="icon iconfont dex-okx-defi-spot-DCA rrglRU__dex" role="img" aria-hidden="true" style="font-size: 12px;"></i>';
}
return `
<div class="vHgkEt__dex jn7mYO__dex" style="width: 12px; height: 12px;">
<picture class="dex dex-picture srK4SZ__dex dex-picture-font">
<source srcset="${buildImageSrcset(SOL_WALLET_ICON)}">
<img class="base-logo srK4SZ__dex" alt="" src="${SOL_WALLET_ICON}">
</picture>
</div>
`;
}
function setImageIfChanged(imageElement, sourceElement, imageUrl) {
const normalizedImageUrl = normalizeImageUrl(imageUrl);
if (!imageElement || !normalizedImageUrl) return;
const previousSrc = normalizeImageUrl(imageElement.getAttribute('src') || '');
const previousSourceSrcset = sourceElement ? normalizeImageUrl(sourceElement.getAttribute('srcset') || '') : '';
const nextSrcset = buildImageSrcset(normalizedImageUrl);
const currentSrc = previousSrc;
const currentSrcset = normalizeImageUrl(imageElement.getAttribute('srcset') || '');
if (currentSrc !== normalizedImageUrl) {
imageElement.setAttribute('src', normalizedImageUrl);
}
if (currentSrcset !== nextSrcset) {
imageElement.setAttribute('srcset', nextSrcset);
}
if (sourceElement) {
const sourceSrcset = normalizeImageUrl(sourceElement.getAttribute('srcset') || '');
if (sourceSrcset !== nextSrcset) {
sourceElement.setAttribute('srcset', nextSrcset);
}
}
// Görsel yüklenmezse önce bir önceki geçerli adrese dön, en son default'a düş
const fallbackUrl = resolveTokenImageUrl(previousSourceSrcset, previousSrc, currentTokenImage, DEFAULT_TOKEN_IMAGE);
if (fallbackUrl) {
imageElement.onerror = function() {
this.onerror = null;
this.setAttribute('src', fallbackUrl);
this.setAttribute('srcset', buildImageSrcset(fallbackUrl));
if (sourceElement) {
sourceElement.setAttribute('srcset', buildImageSrcset(fallbackUrl));
}
};
}
}
function getLiveTokenImageFromDOM() {
const strictTokenCA = getTokenCAFromURL({ allowLastKnown: false });
const normalizedStrictTokenCA =
strictTokenCA && strictTokenCA !== 'default_token' && strictTokenCA !== 'error_token'
? strictTokenCA
: '';
const currentTokenCA = String(normalizedStrictTokenCA || '').toLowerCase();
const defaultImageLower = normalizeImageUrl(DEFAULT_TOKEN_IMAGE).toLowerCase();
const candidates = [];
const pushUrl = (rawUrl, baseScore = 0) => {
const normalized = normalizeImageUrl(rawUrl);
if (!normalized) return;
const lower = normalized.toLowerCase();
if (!/^https?:\/\//.test(lower)) return;
if (!lower.includes('/currency/token/')) return;
if (lower.includes('/network/') || lower.includes('network-logo')) return;
if (lower.includes('11111111111111111111111111111111')) return; // SOL logo
let score = baseScore;
if (currentTokenCA && lower.includes(`-${currentTokenCA}-`)) {
score += 220;
} else if (currentTokenCA && lower.includes(currentTokenCA)) {
score += 160;
}
if (lower.includes('/large/')) score += 20;
if (lower.includes('type=default_90_0')) score += 12;
if (lower.includes('type=default_350_0')) score += 8;
if (lower === defaultImageLower) score -= 60;
candidates.push({ url: normalized, score });
};
const pushCandidate = (imageElement, baseScore = 0) => {
if (!imageElement) return;
if (imageElement.closest('.demo-trade-panel')) return;
if (imageElement.closest('.demo-notification-container')) return;
const candidateUrl = getImageUrlFromElement(imageElement);
if (candidateUrl) {
pushUrl(candidateUrl, baseScore);
}
};
const symbolElement = document.querySelector('.B73M_W__dex');
if (symbolElement) {
const scopes = [
symbolElement.closest('[class*="token"]'),
symbolElement.closest('[class*="pair"]'),
symbolElement.closest('section'),
symbolElement.closest('div')
].filter(Boolean);
for (const scope of scopes) {
scope.querySelectorAll('picture source').forEach(source => {
if (source.closest('.demo-trade-panel')) return;
pushUrl(source.getAttribute('srcset') || '', 80);
});
scope.querySelectorAll('img').forEach(img => pushCandidate(img, 80));
}
}
if (currentTokenCA) {
const urlNodes = document.querySelectorAll('img[src], img[srcset], picture source[srcset]');
urlNodes.forEach(node => {
if (node.closest('.demo-trade-panel')) return;
const raw =
(node.tagName && node.tagName.toLowerCase() === 'source')
? (node.getAttribute('srcset') || '')
: (node.getAttribute('srcset') || node.getAttribute('src') || '');
if (raw && String(raw).toLowerCase().includes(currentTokenCA)) {
pushUrl(raw, 130);
}
});
}
const sourceNodes = document.querySelectorAll('picture source[srcset*="/currency/token/"]');
sourceNodes.forEach(source => {
if (source.closest('.demo-trade-panel')) return;
pushUrl(source.getAttribute('srcset') || '', 40);
});
const selectors = [
'.lj9oR7__dex img',
'.hiAF42__dex img',
'.UvAw5q__dex img',
'.vP8ohB__dex img',
'[class*="token-avatar"] img',
'[class*="TokenAvatar"] img',
'.IMDFTB__dex img',
'.BaV8Ko__dex img',
'img[src*="/currency/token/"]',
'img[srcset*="/currency/token/"]',
'picture.dex-picture img'
];
for (const selector of selectors) {
const images = document.querySelectorAll(selector);
for (const image of images) {
pushCandidate(image, 30);
}
}
if (!candidates.length) {
const rememberedImage = currentTokenCA && demoPortfolio[currentTokenCA]
? demoPortfolio[currentTokenCA].image
: '';
return isLikelyTokenImageUrl(rememberedImage) ? normalizeImageUrl(rememberedImage) : null;
}
const uniqueCandidates = new Map();
for (const item of candidates) {
const existing = uniqueCandidates.get(item.url);
if (!existing || item.score > existing.score) {
uniqueCandidates.set(item.url, item);
}
}
const best = Array.from(uniqueCandidates.values()).sort((a, b) => b.score - a.score)[0];
return best ? best.url : null;
}
function getLiveTokenMetaFromDOM() {
const sanitize = (value) => String(value || '').replace(/\s+/g, ' ').trim();
const normalizeSymbol = (value) => sanitize(value).toUpperCase();
const isBlockedSymbol = (value) => ['SOL', 'USDC', 'USDT', 'TOKEN'].includes(value);
let symbol = '';
let name = '';
const tokenHeader = document.querySelector('.E36_sj__dex, [class*="E36_sj"]');
if (tokenHeader) {
const explicitNameEl = tokenHeader.querySelector('.AgB6GU__dex .ellipsis, [class*="AgB6GU"] .ellipsis');
const explicitSymbolEl = tokenHeader.querySelector('.iI1U_c__dex.cQ_2nK__dex .ellipsis, .hXByxe__dex .ellipsis, [class*="hXByxe"] .ellipsis');
name = sanitize(explicitNameEl ? explicitNameEl.textContent : '');
symbol = normalizeSymbol(explicitSymbolEl ? explicitSymbolEl.textContent : '');
const allLabels = Array.from(tokenHeader.querySelectorAll('.ellipsis'))
.map(el => sanitize(el.textContent))
.filter(Boolean);
if (!name && allLabels.length > 0) {
name = allLabels[0];
}
if (!symbol) {
const upperCandidate = allLabels.find(label => /[A-Za-z]/.test(label) && label === label.toUpperCase());
symbol = normalizeSymbol(upperCandidate || allLabels[1] || allLabels[0] || '');
}
}
if (!symbol) {
const fallbackSymbolEl = document.querySelector('.B73M_W__dex, [class*="B73M_W"]');
symbol = normalizeSymbol(fallbackSymbolEl ? fallbackSymbolEl.textContent : '');
}
if (!name && symbol) {
name = symbol;
}
if (isBlockedSymbol(symbol)) {
symbol = '';
}
if (/^token$/i.test(name)) {
name = '';
}
return { symbol, name };
}
function formatNumber(value, isFiat = true) {
if (value === 0 || value === null || value === undefined) {
return isFiat ? '$0.00' : '0.00';
}
if (isFiat) {
if (Math.abs(value) >= 1000000) {
return '$' + (value / 1000000).toFixed(2) + 'M';
} else if (Math.abs(value) >= 1000) {
return '$' + (value / 1000).toFixed(2) + 'K';
} else {
return '$' + value.toFixed(2);
}
} else {
// Token miktarı için daha kompakt format
const absValue = Math.abs(value);
if (absValue >= 1000000) {
return (value / 1000000).toFixed(2) + 'M';
} else if (absValue >= 1000) {
return (value / 1000).toFixed(2) + 'K';
} else if (absValue >= 1) {
return value.toFixed(2);
} else if (absValue >= 0.01) {
return value.toFixed(3);
} else if (absValue >= 0.0001) {
return value.toFixed(5);
} else {
return value.toFixed(7);
}
}
}
function formatBalanceDisplay(value) {
if (value === null || value === undefined || !Number.isFinite(Number(value))) {
return '0';
}
const numeric = Number(value);
if (isEffectivelyZero(numeric)) {
return '0';
}
const raw = formatNumber(numeric, false);
return raw
.replace(/(\.[0-9]*?[1-9])0+(?=[A-Za-z]*$)/, '$1')
.replace(/\.0+(?=[A-Za-z]*$)/, '');
}
function formatPnLDisplay(value, isFiat = true) {
if (value === null || value === undefined || !Number.isFinite(Number(value))) {
return isFiat ? '$0.00' : '0';
}
const numeric = Number(value);
if (isEffectivelyZero(numeric)) {
return isFiat ? '$0.00' : '0';
}
const raw = formatNumber(numeric, isFiat);
return raw
.replace(/(\.[0-9]*?[1-9])0+(?=[A-Za-z]*$)/, '$1')
.replace(/\.0+(?=[A-Za-z]*$)/, '');
}
function formatPnLSummaryAmountDisplay(value, isFiat = true) {
if (!isFiat) {
return formatPnLDisplay(value, false);
}
const numeric = Number(value);
if (!Number.isFinite(numeric) || isEffectivelyZero(numeric)) {
return '$0.0';
}
const abs = Math.abs(numeric);
if (abs >= 1000000) {
return `$${(numeric / 1000000).toFixed(1)}M`;
}
if (abs >= 1000) {
return `$${(numeric / 1000).toFixed(1)}K`;
}
return `$${numeric.toFixed(1)}`;
}
function formatPnLIntegerDisplay(value, isFiat = true, nonFiatFractionDigits = 2) {
const safeNonFiatDigits = Number.isInteger(nonFiatFractionDigits)
? Math.max(0, Math.min(8, nonFiatFractionDigits))
: 2;
if (value === null || value === undefined || !Number.isFinite(Number(value))) {
return isFiat ? '$0.0' : (0).toFixed(safeNonFiatDigits);
}
const numeric = Number(value);
const absoluteNumeric = Math.abs(numeric);
if (isEffectivelyZero(absoluteNumeric)) {
return isFiat ? '$0.0' : (0).toFixed(safeNonFiatDigits);
}
const fractionDigits = isFiat ? (absoluteNumeric > 99 ? 0 : 1) : safeNonFiatDigits;
const multiplier = Math.pow(10, fractionDigits);
let rounded = Math.round(numeric * multiplier) / multiplier;
if (isEffectivelyZero(rounded)) {
rounded = 0;
}
if (isFiat) {
const absoluteText = Math.abs(rounded).toFixed(fractionDigits);
if (rounded > 0) {
return `+$${absoluteText}`;
}
if (rounded < 0) {
return `-$${absoluteText}`;
}
return `$${absoluteText}`;
}
const absoluteText = Math.abs(rounded).toFixed(fractionDigits);
if (rounded > 0) {
return `+${absoluteText}`;
}
if (rounded < 0) {
return `-${absoluteText}`;
}
return absoluteText;
}
function formatSignedUsdPnL(value, fractionDigits = 1) {
const safeFractionDigits = Number.isInteger(fractionDigits)
? Math.max(0, Math.min(8, fractionDigits))
: 1;
if (value === null || value === undefined || !Number.isFinite(Number(value))) {
return `$${(0).toFixed(safeFractionDigits)}`;
}
const numeric = Number(value);
const multiplier = Math.pow(10, safeFractionDigits);
let rounded = Math.round(numeric * multiplier) / multiplier;
if (isEffectivelyZero(rounded)) {
rounded = 0;
}
const absoluteText = Math.abs(rounded).toFixed(safeFractionDigits);
if (rounded > 0) {
return `+$${absoluteText}`;
}
if (rounded < 0) {
return `-$${absoluteText}`;
}
return `$${absoluteText}`;
}
function copyTextToClipboard(text) {
const safeText = typeof text === 'string' ? text : String(text || '');
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
return navigator.clipboard.writeText(safeText);
}
return new Promise((resolve, reject) => {
try {
const textArea = document.createElement('textarea');
textArea.value = safeText;
textArea.setAttribute('readonly', 'readonly');
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.select();
const copied = document.execCommand('copy');
document.body.removeChild(textArea);
if (copied) {
resolve();
return;
}
reject(new Error('Copy command failed'));
} catch (error) {
reject(error);
}
});
}
function formatTokenPriceDisplay(value) {
if (value === null || value === undefined || !Number.isFinite(Number(value))) {
return '$0';
}
const numeric = Number(value);
const absValue = Math.abs(numeric);
if (absValue === 0) {
return '$0';
}
if (absValue < 0.000000000001) {
return `$${numeric.toExponential(6)}`;
}
let decimals = 6;
if (absValue < 1) decimals = 8;
if (absValue < 0.01) decimals = 10;
if (absValue < 0.0001) decimals = 12;
const formatted = numeric
.toFixed(decimals)
.replace(/(\.[0-9]*?[1-9])0+$/, '$1')
.replace(/\.0+$/, '');
return `$${formatted}`;
}
function parseTokenPriceFromText(rawPriceText) {
if (!rawPriceText || typeof rawPriceText !== 'string') return null;
let cleanedPrice = rawPriceText
.replace(/\s+/g, '')
.replace('$', '')
.replace(/,/g, '')
.replace(/USDT|USD/gi, '');
// OKX: 0.0₅123 => 0.00000123 (subscript, ondalık sonrası sıfır adedini temsil eder)
const subscriptMap = {
'\u2080': '0', '\u2081': '1', '\u2082': '2', '\u2083': '3', '\u2084': '4',
'\u2085': '5', '\u2086': '6', '\u2087': '7', '\u2088': '8', '\u2089': '9'
};
const subscriptMatch = cleanedPrice.match(/^0\.0([\u2080-\u2089]+)(.*)$/);
if (subscriptMatch) {
const zeroCountText = Array.from(subscriptMatch[1])
.map(char => subscriptMap[char] || '')
.join('');
const zeroCount = parseInt(zeroCountText, 10);
const rest = (subscriptMatch[2] || '').replace(/[^\d]/g, '');
if (!isNaN(zeroCount) && rest) {
cleanedPrice = `0.${'0'.repeat(Math.min(zeroCount, 30))}${rest}`;
}
}
cleanedPrice = cleanedPrice.replace(/[^\d.]/g, '');
// Birden fazla nokta varsa tek ondalık formata indir
if ((cleanedPrice.match(/\./g) || []).length > 1) {
const parts = cleanedPrice.split('.');
cleanedPrice = `${parts[0]}.${parts.slice(1).join('')}`;
}
const parsed = Number(cleanedPrice);
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
function extractPriceText(rawText, allowPlainNumber = false) {
if (!rawText || typeof rawText !== 'string') return null;
const normalized = rawText.replace(/\s+/g, ' ').trim();
if (!normalized) return null;
const dollarMatch = normalized.match(/\$\s*[0-9][0-9.,\u2080-\u2089]*/);
if (dollarMatch && dollarMatch[0]) {
return dollarMatch[0].replace(/\s+/g, '');
}
if (allowPlainNumber) {
const compact = normalized.replace(/\s+/g, '').replace(/,/g, '');
if (/^[0-9][0-9.\u2080-\u2089]*$/.test(compact)) {
return compact;
}
const plainMatch = normalized.match(/([0-9][0-9.,\u2080-\u2089]*)/);
if (plainMatch && plainMatch[1]) {
return plainMatch[1].replace(/\s+/g, '');
}
}
return null;
}
function getLiveSolPriceFromDOM() {
const solImageHints = [
'501-11111111111111111111111111111111-1',
'11111111111111111111111111111111',
'/501-',
'solana'
];
const isSolImage = (rawUrl) => {
const url = normalizeImageUrl(rawUrl || '');
if (!url) return false;
const lower = url.toLowerCase();
return solImageHints.some(hint => lower.includes(hint));
};
const parsePriceFromContainer = (container) => {
if (!container) return null;
const textCandidates = [
...Array.from(container.querySelectorAll('span')).map(el => el.textContent || ''),
container.getAttribute ? container.getAttribute('aria-label') : null,
container.textContent || ''
].filter(Boolean);
for (const rawText of textCandidates) {
const extracted = extractPriceText(rawText, true);
const parsed = parseTokenPriceFromText(extracted || '');
if (parsed && parsed > 0) {
return parsed;
}
}
return null;
};
// 1) Kullanıcının paylaştığı üst bar yapısını önceliklendir: .PwSNTD__dex > .mB70vW__dex
const scopedPopups = document.querySelectorAll('.PwSNTD__dex .mB70vW__dex');
for (const popup of scopedPopups) {
const image = popup.querySelector('img, .SN8BmF__dex');
const imageUrl = getImageUrlFromElement(image) || (image ? image.getAttribute('src') : '');
const popupText = popup.textContent || '';
const isSolPopup = isSolImage(imageUrl) || /\bsol\b/i.test(popupText);
if (!isSolPopup) continue;
const parsedPrice = parsePriceFromContainer(popup);
if (parsedPrice && parsedPrice > 0) {
return parsedPrice;
}
}
// 2) Genel fallback: tüm popup alanlarında SOL eşleşmesi ara
const popupSelectors = [
'.PwSNTD__dex .mB70vW__dex',
'[data-testid="okd-popup"].mB70vW__dex',
'.mB70vW__dex',
'[data-testid="okd-popup"]'
];
const popups = document.querySelectorAll(popupSelectors.join(','));
for (const popup of popups) {
const image = popup.querySelector('img');
const imageUrl = getImageUrlFromElement(image) || (image ? image.getAttribute('src') : '');
const popupText = popup.textContent || '';
const isSolPopup = isSolImage(imageUrl) || /\bsol\b/i.test(popupText);
if (!isSolPopup) continue;
const parsedPrice = parsePriceFromContainer(popup);
if (parsedPrice && parsedPrice > 0) {
return parsedPrice;
}
}
// 3) Son fallback: mB70vW satırındaki fiyat span'leri
const directCandidates = document.querySelectorAll('.PwSNTD__dex .mB70vW__dex span, .mB70vW__dex span, .SN8BmF__dex + span');
for (const element of directCandidates) {
const extracted = extractPriceText(element.textContent || '', true);
const parsed = parseTokenPriceFromText(extracted || '');
if (parsed && parsed > 0) {
return parsed;
}
}
return null;
}
function getLiveTokenPriceFromDOM() {
const labelSelectors = [
'.Cg2_xV__dex',
'.KMsU5K__dex',
'[class*="Cg2_xV"]',
'[class*="KMsU5K"]'
];
const priceLabels = Array.from(document.querySelectorAll(labelSelectors.join(',')))
.filter(el => (el.textContent || '').trim().toLowerCase() === 'price');
for (const label of priceLabels) {
const contextCandidates = [
label.closest('.glKoFv__dex'),
label.parentElement,
label.closest('div'),
label.parentElement ? label.parentElement.nextElementSibling : null,
label.nextElementSibling
].filter(Boolean);
for (const context of contextCandidates) {
const valueCandidates = [
...context.querySelectorAll('.BaV8Ko__dex, .IMDFTB__dex, [class*="BaV8Ko"], [class*="IMDFTB"]'),
context,
context.nextElementSibling
].filter(Boolean);
for (const candidate of valueCandidates) {
const textCandidates = [
candidate.getAttribute ? candidate.getAttribute('data-clipboard-text') : null,
candidate.getAttribute ? candidate.getAttribute('data-copy') : null,
candidate.getAttribute ? candidate.getAttribute('title') : null,
candidate.getAttribute ? candidate.getAttribute('aria-label') : null,
candidate.textContent || ''
].filter(Boolean);
for (const rawText of textCandidates) {
const text = extractPriceText(rawText, true);
const parsed = parseTokenPriceFromText(text || '');
if (parsed && parsed > 0) {
return parsed;
}
}
}
}
}
const directSelectors = [
'.BaV8Ko__dex',
'.IMDFTB__dex',
'[class*="BaV8Ko"]',
'[class*="IMDFTB"]',
'[data-testid*="price"]',
'[class*="price"]',
'[class*="Price"]'
];
for (const selector of directSelectors) {
const elements = document.querySelectorAll(selector);
for (const element of elements) {
const textCandidates = [
element.getAttribute ? element.getAttribute('data-clipboard-text') : null,
element.getAttribute ? element.getAttribute('data-copy') : null,
element.getAttribute ? element.getAttribute('title') : null,
element.getAttribute ? element.getAttribute('aria-label') : null,
element.textContent || ''
].filter(Boolean);
for (const rawText of textCandidates) {
const text = extractPriceText(rawText, selector.includes('BaV8Ko') || selector.includes('IMDFTB'));
const parsed = parseTokenPriceFromText(text || '');
if (parsed && parsed > 0) {
return parsed;
}
}
}
}
return null;
}
function saveSettingsToStorage() {
localStorage.setItem('demoSettings', JSON.stringify(currentSettings));
}
function parseSlippagePercent(value, fallback = 0) {
const numeric = parseFloat(String(value || '').replace('%', '').replace(',', '.').trim());
if (!Number.isFinite(numeric)) {
return fallback;
}
return Math.max(0, Math.min(100, numeric));
}
function normalizeSlippageText(value, fallback = '0%') {
const numeric = parseSlippagePercent(value, parseSlippagePercent(fallback, 0));
const fixed = Number(numeric.toFixed(4));
return `${fixed}%`;
}
function parseFixedFeeSol(value, fallback = 0) {
const normalizedText = String(value || '')
.replace(',', '.')
.replace(/[^0-9.]/g, '')
.trim();
const numeric = parseFloat(normalizedText);
if (!Number.isFinite(numeric)) {
return fallback;
}
return Math.max(0, numeric);
}
function normalizeFixedFeeSolText(value, fallback = '0.00005') {
const fallbackNumeric = parseFixedFeeSol(fallback, 0.00005);
const numeric = parseFixedFeeSol(value, fallbackNumeric);
return numeric
.toFixed(8)
.replace(/0+$/, '')
.replace(/\.$/, '') || '0';
}
function getTokenCAFromCurrentURLStrict() {
try {
const href = window.location.href;
const parsedUrl = new URL(href);
debugLog('🔠Strict URL parsing for token:', href);
// 1) Query param'ler
const queryKeys = ['token', 'mint', 'address', 'ca', 'contractAddress', 'contract', 'tokenAddress', 'pairAddress'];
for (const key of queryKeys) {
const rawValue = parsedUrl.searchParams.get(key);
const normalized = rawValue ? decodeURIComponent(rawValue).trim() : '';
if (normalized && normalized.length > 10) {
return normalized;
}
}
// 2) Path formatı: /token/{chain}/{contractAddress}
const pathMatch = href.match(/\/token\/[^/]+\/([^/?#]+)/i);
if (pathMatch && pathMatch[1] && pathMatch[1].length > 10) {
return decodeURIComponent(pathMatch[1]);
}
// 3) Path segmentlerinden adres yakalama (Solana/EVM)
const pathSegments = parsedUrl.pathname
.split('/')
.map(segment => decodeURIComponent(segment || '').trim())
.filter(Boolean);
for (let i = pathSegments.length - 1; i >= 0; i--) {
const segment = pathSegments[i].replace(/[?#].*$/, '').trim();
if (!segment) continue;
if (/^0x[a-fA-F0-9]{40}$/.test(segment)) {
return segment;
}
if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(segment) && segment !== '11111111111111111111111111111111') {
return segment;
}
}
// 4) Son fallback: URL genelinde eÅŸleÅŸme
const evmMatch = href.match(/0x[a-fA-F0-9]{40}/);
if (evmMatch && evmMatch[0]) {
return evmMatch[0];
}
const solMatches = href.match(/[1-9A-HJ-NP-Za-km-z]{32,44}/g) || [];
const solCandidate = solMatches.find(value => value !== '11111111111111111111111111111111');
if (solCandidate) {
return solCandidate;
}
return '';
} catch (error) {
console.error('Error parsing token CA (strict):', error);
return '';
}
}
function getTokenCAFromURL(options = {}) {
const allowLastKnown = options.allowLastKnown !== false;
try {
const strictTokenCA = getTokenCAFromCurrentURLStrict();
if (strictTokenCA) {
debugLog('✅ Token CA from current URL:', strictTokenCA);
return strictTokenCA;
}
// Son başarılı CA yalnızca açıkça izin verildiğinde fallback olarak kullanılır.
if (allowLastKnown && lastTokenCA) {
debugLog('â„¹ï¸ Using last known token CA:', lastTokenCA);
return lastTokenCA;
}
debugLog('⌠No token identifier found');
return 'default_token';
} catch (error) {
console.error('Error parsing token CA:', error);
return 'error_token';
}
}
function calculatePortfolioRealizedPnLSum() {
let realizedSum = 0;
Object.keys(demoPortfolio || {}).forEach(key => {
const token = demoPortfolio[key];
if (!token || typeof token !== 'object') return;
const tokenRealized = Number(token.realizedPnL);
if (Number.isFinite(tokenRealized)) {
realizedSum += tokenRealized;
}
});
return realizedSum;
}
function migrateLegacyPortfolioRealizedPnL() {
let hasNonZeroTokenRealizedPnL = false;
Object.keys(demoPortfolio || {}).forEach(key => {
const token = demoPortfolio[key];
if (!token || typeof token !== 'object') return;
const numericRealized = Number(token.realizedPnL);
token.realizedPnL = Number.isFinite(numericRealized) ? numericRealized : 0;
if (token.realizedPnL !== 0) {
hasNonZeroTokenRealizedPnL = true;
}
});
const legacyGlobalRealized = Number(totalRealizedPnL);
if (!hasNonZeroTokenRealizedPnL && Number.isFinite(legacyGlobalRealized) && Math.abs(legacyGlobalRealized) > 0) {
const strictTokenCA = getTokenCAFromCurrentURLStrict();
const fallbackTokenCA = strictTokenCA || lastTokenCA || Object.keys(demoPortfolio).find(key => key !== 'SOL') || 'SOL';
if (!demoPortfolio[fallbackTokenCA]) {
demoPortfolio[fallbackTokenCA] = {
amount: 0,
avgPrice: 0,
symbol: fallbackTokenCA === 'SOL' ? 'SOL' : (currentTokenSymbol || 'TOKEN'),
image: '',
totalInvested: 0,
totalSold: 0,
totalInvestedSol: 0,
totalSoldSol: 0,
realizedPnL: 0
};
}
demoPortfolio[fallbackTokenCA].realizedPnL += legacyGlobalRealized;
}
totalRealizedPnL = calculatePortfolioRealizedPnLSum();
}
function getTokenSolTotalsFromTradeHistory(tokenCA) {
const normalizedTokenCA = String(tokenCA || '').trim();
if (!normalizedTokenCA || !Array.isArray(tradeHistory)) {
return { totalInvestedSol: 0, totalSoldSol: 0 };
}
let totalInvestedSol = 0;
let totalSoldSol = 0;
tradeHistory.forEach(trade => {
if (!trade || typeof trade !== 'object') return;
if (String(trade.tokenCA || '').trim() !== normalizedTokenCA) return;
const tradeType = String(trade.type || '').toUpperCase();
const solAmount = Number(trade.solAmount);
const totalFees = Number(trade.totalFees);
if (tradeType === 'BUY') {
const buySol = Number.isFinite(solAmount) ? solAmount : 0;
const buyFeeSol = Number.isFinite(totalFees) ? totalFees : 0;
totalInvestedSol += Math.max(0, buySol + buyFeeSol);
return;
}
if (tradeType === 'SELL') {
const netSoldSol = Number.isFinite(solAmount) ? solAmount : 0;
totalSoldSol += Math.max(0, netSoldSol);
}
});
return { totalInvestedSol, totalSoldSol };
}
function migrateLegacyPortfolioSolTotals() {
Object.keys(demoPortfolio || {}).forEach(key => {
const token = demoPortfolio[key];
if (!token || typeof token !== 'object') return;
const hasTotalInvestedSol = Number.isFinite(Number(token.totalInvestedSol)) && Number(token.totalInvestedSol) >= 0;
const hasTotalSoldSol = Number.isFinite(Number(token.totalSoldSol)) && Number(token.totalSoldSol) >= 0;
if (hasTotalInvestedSol && hasTotalSoldSol) return;
const historyTotals = getTokenSolTotalsFromTradeHistory(key);
if (!hasTotalInvestedSol) {
token.totalInvestedSol = historyTotals.totalInvestedSol;
}
if (!hasTotalSoldSol) {
token.totalSoldSol = historyTotals.totalSoldSol;
}
});
}
function getCurrentTokenPortfolio() {
// Her seferinde URL'den CA al
const tokenCA = getTokenCAFromURL();
debugLog('🔄 Portfolio check - Current CA:', tokenCA, 'Symbol:', currentTokenSymbol);
const normalizedCurrentImage = isLikelyTokenImageUrl(currentTokenImage)
? normalizeImageUrl(currentTokenImage)
: '';
// Mevcut token için portföy oluştur veya getir
if (!demoPortfolio[tokenCA]) {
demoPortfolio[tokenCA] = {
amount: 0,
avgPrice: 0,
symbol: currentTokenSymbol,
image: normalizedCurrentImage,
totalInvested: 0,
totalSold: 0,
totalInvestedSol: 0,
totalSoldSol: 0,
realizedPnL: 0
};
debugLog('🆕 Created NEW portfolio for:', currentTokenSymbol, 'CA:', tokenCA);
} else {
// Mevcut portföy bilgilerini güncelle (sembol + geçerli ikon)
demoPortfolio[tokenCA].symbol = currentTokenSymbol;
if (normalizedCurrentImage) {
demoPortfolio[tokenCA].image = normalizedCurrentImage;
}
debugLog('📠Loaded EXISTING portfolio for:', currentTokenSymbol, 'Amount:', demoPortfolio[tokenCA].amount);
}
const activePortfolio = demoPortfolio[tokenCA];
const legacySolTotals = getTokenSolTotalsFromTradeHistory(tokenCA);
activePortfolio.amount = Number.isFinite(Number(activePortfolio.amount)) && Number(activePortfolio.amount) >= 0
? Number(activePortfolio.amount)
: 0;
activePortfolio.amount = Math.max(0, normalizeNearZero(activePortfolio.amount, TOKEN_AMOUNT_ZERO_EPSILON));
activePortfolio.avgPrice = Number.isFinite(Number(activePortfolio.avgPrice)) && Number(activePortfolio.avgPrice) >= 0
? Number(activePortfolio.avgPrice)
: 0;
activePortfolio.avgPrice = normalizeNearZero(activePortfolio.avgPrice);
if (isEffectivelyZero(activePortfolio.amount, TOKEN_AMOUNT_ZERO_EPSILON)) {
activePortfolio.amount = 0;
activePortfolio.avgPrice = 0;
}
activePortfolio.totalInvested = Number.isFinite(Number(activePortfolio.totalInvested)) && Number(activePortfolio.totalInvested) >= 0
? Number(activePortfolio.totalInvested)
: 0;
activePortfolio.totalSold = Number.isFinite(Number(activePortfolio.totalSold)) && Number(activePortfolio.totalSold) >= 0
? Number(activePortfolio.totalSold)
: 0;
activePortfolio.totalInvestedSol = Number.isFinite(Number(activePortfolio.totalInvestedSol)) && Number(activePortfolio.totalInvestedSol) >= 0
? Number(activePortfolio.totalInvestedSol)
: legacySolTotals.totalInvestedSol;
activePortfolio.totalSoldSol = Number.isFinite(Number(activePortfolio.totalSoldSol)) && Number(activePortfolio.totalSoldSol) >= 0
? Number(activePortfolio.totalSoldSol)
: legacySolTotals.totalSoldSol;
activePortfolio.realizedPnL = Number.isFinite(Number(activePortfolio.realizedPnL))
? Number(activePortfolio.realizedPnL)
: 0;
return activePortfolio;
}
function calculateTotalBalance() {
let totalBalance = 0;
// SOL bakiyesini ekle
if (demoPortfolio.SOL && demoPortfolio.SOL.amount) {
totalBalance += demoPortfolio.SOL.amount * currentSolPrice;
}
const strictActiveTokenCA = getTokenCAFromURL({ allowLastKnown: false });
const activeTokenCA =
strictActiveTokenCA && strictActiveTokenCA !== 'default_token' && strictActiveTokenCA !== 'error_token'
? strictActiveTokenCA
: getTokenCAFromURL();
// Diğer tokenların bakiyesini ekle
Object.keys(demoPortfolio).forEach(key => {
const token = demoPortfolio[key];
const tokenAmount = token ? Math.max(0, normalizeNearZero(token.amount, TOKEN_AMOUNT_ZERO_EPSILON)) : 0;
if (key !== 'SOL' && tokenAmount > 0) {
// EÄŸer bu token ÅŸu anki token ise current price kullan, deÄŸilse avg price kullan
const tokenPrice = (key === activeTokenCA) ? currentTokenPrice : (token.avgPrice || 0);
totalBalance += tokenAmount * tokenPrice;
}
});
return totalBalance;
}
function initDemoTrading() {
if (isInitializing) {
debugLog('🚫 Already initializing, skipping...');
return;
}
isInitializing = true;
debugLog('Initializing Demo Trading...');
try {
addDemoTab();
addDemoButton();
setupDemoMode();
loadSettingsFromRealPanel();
migrateLegacyPortfolioRealizedPnL();
migrateLegacyPortfolioSolTotals();
savePortfolioToStorage();
startPriceUpdates();
startSolPriceUpdates();
setupKeyboardHotkeys();
setupPageObserver();
} catch (error) {
console.error('Error in initDemoTrading:', error);
} finally {
setTimeout(() => {
isInitializing = false;
}, 1000);
}
}
function setupPageObserver() {
// Eğer observer zaten varsa, önce onu durdur
if (pageObserver) {
pageObserver.disconnect();
}
// Sayfa deÄŸiÅŸikliklerini izle
pageObserver = new MutationObserver(function(mutations) {
let shouldUpdate = false;
mutations.forEach(function(mutation) {
// Yeni node'lar eklendiÄŸinde kontrol et
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
for (let node of mutation.addedNodes) {
if (node.nodeType === 1) { // Element node
// Tab container deÄŸiÅŸti mi?
if (node.querySelector && (
node.querySelector('.dex-tabs-pane-list-container') ||
node.querySelector('.Q68nJL__dex') ||
node.querySelector('.vP8ohB__dex')
)) {
shouldUpdate = true;
break;
}
}
}
}
// Attribute deÄŸiÅŸiklikleri (active tab deÄŸiÅŸimi)
if (mutation.type === 'attributes' &&
(mutation.attributeName === 'class' || mutation.attributeName === 'data-pane-id')) {
shouldUpdate = true;
}
});
if (shouldUpdate && !isInitializing) {
debugLog('Page changed, updating demo components...');
if (pendingDemoComponentsUpdate) {
clearTimeout(pendingDemoComponentsUpdate);
}
pendingDemoComponentsUpdate = setTimeout(() => {
pendingDemoComponentsUpdate = null;
updateDemoComponents();
}, 450);
}
});
// Tüm sayfayı izle
pageObserver.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'data-pane-id']
});
}
function updateDemoComponents() {
const strictTokenCA = getTokenCAFromCurrentURLStrict();
if (!strictTokenCA && demoPanelVisible) {
hideDemoPanel();
}
// Demo tab'ı kontrol et ve ekle
if (!document.querySelector('[data-pane-id="demo_trading"]')) {
addDemoTab();
}
// Demo butonunu kontrol et ve ekle
if (!document.querySelector('.demo-trade-button')) {
addDemoButton();
}
// Demo panel açıksa güncelle
if (demoPanelVisible) {
updateDemoPanelValues();
}
// Aktif tab'ı güncelle
updateActiveTab();
}
function loadSettingsFromRealPanel() {
const realPanel = document.querySelector('.cIknRK__dex:not(.demo-trade-panel)');
if (realPanel) {
const settingsSnapshotBefore = JSON.stringify(currentSettings);
// Kullanıcı demo panelde slippage'i elle düzenlediyse, real panelden overwrite etme
if (!currentSettings.customBuySlippage) {
const buySlippage = realPanel.querySelector('.Nox6EF__dex .font-500');
if (buySlippage) {
currentSettings.buySlippage = normalizeSlippageText(buySlippage.textContent || '15%', '15%');
}
} else {
currentSettings.buySlippage = normalizeSlippageText(currentSettings.buySlippage, '15%');
}
if (!currentSettings.customSellSlippage) {
const sellSlippageElements = realPanel.querySelectorAll('.Nox6EF__dex .font-500');
if (sellSlippageElements.length > 1) {
currentSettings.sellSlippage = normalizeSlippageText(sellSlippageElements[1].textContent || '20%', '20%');
}
} else {
currentSettings.sellSlippage = normalizeSlippageText(currentSettings.sellSlippage, '20%');
}
// Buy amounts
if (!currentSettings.customBuyAmounts) {
const buyAmounts = realPanel.querySelectorAll('.H5f_ax__dex.Cg7h1c__dex .l5GPKh__dex');
if (buyAmounts.length > 0) {
currentSettings.buyAmounts = [
(buyAmounts[0] && buyAmounts[0].textContent ? buyAmounts[0].textContent.trim() : '') || currentSettings.buyAmounts[0] || defaultBuyAmounts[0],
(buyAmounts[1] && buyAmounts[1].textContent ? buyAmounts[1].textContent.trim() : '') || currentSettings.buyAmounts[1] || defaultBuyAmounts[1],
(buyAmounts[2] && buyAmounts[2].textContent ? buyAmounts[2].textContent.trim() : '') || currentSettings.buyAmounts[2] || defaultBuyAmounts[2],
(buyAmounts[3] && buyAmounts[3].textContent ? buyAmounts[3].textContent.trim() : '') || currentSettings.buyAmounts[3] || defaultBuyAmounts[3]
];
}
}
currentSettings.buyAmounts = currentSettings.buyAmounts
.map(v => String(v || '').trim())
.filter(Boolean)
.slice(0, 4);
while (currentSettings.buyAmounts.length < 4) {
currentSettings.buyAmounts.push(defaultBuyAmounts[currentSettings.buyAmounts.length]);
}
// Sell percents
if (!currentSettings.customSellPercents) {
const sellPercents = realPanel.querySelectorAll('.H5f_ax__dex.ODJ17x__dex .l5GPKh__dex');
if (sellPercents.length > 0) {
currentSettings.sellPercents = [
(sellPercents[0] && sellPercents[0].textContent ? sellPercents[0].textContent.trim() : '') || currentSettings.sellPercents[0] || defaultSellPercents[0],
(sellPercents[1] && sellPercents[1].textContent ? sellPercents[1].textContent.trim() : '') || currentSettings.sellPercents[1] || defaultSellPercents[1],
(sellPercents[2] && sellPercents[2].textContent ? sellPercents[2].textContent.trim() : '') || currentSettings.sellPercents[2] || defaultSellPercents[2],
(sellPercents[3] && sellPercents[3].textContent ? sellPercents[3].textContent.trim() : '') || currentSettings.sellPercents[3] || defaultSellPercents[3]
];
}
}
currentSettings.sellPercents = currentSettings.sellPercents
.map(v => String(v || '').trim())
.filter(Boolean)
.slice(0, 4);
while (currentSettings.sellPercents.length < 4) {
currentSettings.sellPercents.push(defaultSellPercents[currentSettings.sellPercents.length]);
}
currentSettings.buySlippage = normalizeSlippageText(currentSettings.buySlippage, '15%');
currentSettings.sellSlippage = normalizeSlippageText(currentSettings.sellSlippage, '20%');
currentSettings.buyFixedFeeSol = normalizeFixedFeeSolText(currentSettings.buyFixedFeeSol, '0.00005');
currentSettings.sellFixedFeeSol = normalizeFixedFeeSolText(currentSettings.sellFixedFeeSol, '0.00005');
// Customize panel açıldığında eski değerlerin geri yazılmasını önlemek için temp değerleri de senkron tut
tempBuyAmounts = [...currentSettings.buyAmounts];
tempSellPercents = [...currentSettings.sellPercents];
tempBuySlippage = currentSettings.buySlippage;
tempSellSlippage = currentSettings.sellSlippage;
tempBuyFixedFeeSol = currentSettings.buyFixedFeeSol;
tempSellFixedFeeSol = currentSettings.sellFixedFeeSol;
const buySlippageValueEl = document.querySelector('.demo-buy-slippage-value');
if (buySlippageValueEl) {
buySlippageValueEl.textContent = currentSettings.buySlippage;
}
const sellSlippageValueEl = document.querySelector('.demo-sell-slippage-value');
if (sellSlippageValueEl) {
sellSlippageValueEl.textContent = currentSettings.sellSlippage;
}
const buyFixedFeeValueEl = document.querySelector('.demo-buy-fixed-fee-value');
if (buyFixedFeeValueEl) {
buyFixedFeeValueEl.textContent = currentSettings.buyFixedFeeSol;
}
const sellFixedFeeValueEl = document.querySelector('.demo-sell-fixed-fee-value');
if (sellFixedFeeValueEl) {
sellFixedFeeValueEl.textContent = currentSettings.sellFixedFeeSol;
}
if (JSON.stringify(currentSettings) !== settingsSnapshotBefore) {
saveSettingsToStorage();
}
}
}
function getCurrentPrices() {
const previousTokenPrice = currentTokenPrice;
try {
const previousTokenCA = lastTokenCA;
const previousTokenSymbol = currentTokenSymbol;
debugLog('--- COIN PRICE SEARCH STARTED ---');
// 1) Token sembolü ve resmi
const liveTokenMeta = getLiveTokenMetaFromDOM();
if (liveTokenMeta.symbol) {
currentTokenSymbol = liveTokenMeta.symbol;
}
if (liveTokenMeta.name) {
currentTokenName = liveTokenMeta.name;
}
const detectedTokenImage = getLiveTokenImageFromDOM();
if (detectedTokenImage) {
currentTokenImage = detectedTokenImage;
} else if (!isLikelyTokenImageUrl(currentTokenImage)) {
const tokenCA = getTokenCAFromURL();
const rememberedImage = tokenCA && demoPortfolio[tokenCA] ? demoPortfolio[tokenCA].image : '';
if (isLikelyTokenImageUrl(rememberedImage)) {
currentTokenImage = normalizeImageUrl(rememberedImage);
}
}
debugLog('✅ Token symbol and image updated:', currentTokenSymbol, currentTokenName, currentTokenImage);
// 2) SOL fiyatını canlı DOM'dan al (varsa)
const liveSolPrice = getLiveSolPriceFromDOM();
if (liveSolPrice && liveSolPrice > 0) {
currentSolPrice = liveSolPrice;
debugLog('✅ SOL PRICE FROM LIVE DOM:', currentSolPrice);
} else if (!currentSolPrice || currentSolPrice <= 0 || Number.isNaN(currentSolPrice)) {
// DOM'dan yakalanamadıysa async fallback'i tetikle; sabit sayıya düşme yapma
fetchSolPrice();
}
// 3) Token fiyatını yeni + eski selector stratejileri ile bul
const detectedPrice = getLiveTokenPriceFromDOM();
if (detectedPrice && detectedPrice > 0) {
currentTokenPrice = detectedPrice;
debugLog('✅ PRICE SUCCESSFULLY PARSED:', currentTokenPrice);
} else {
// Hatalı parse durumunda son geçerli fiyatı koru
currentTokenPrice = previousTokenPrice > 0 ? previousTokenPrice : 0;
debugLog('⌠Price parsing failed, keeping previous price:', currentTokenPrice);
}
debugLog('✅ FINAL TOKEN PRICE:', currentTokenPrice);
debugLog('--- COIN PRICE SEARCH ENDED ---');
// 4) Token CA güncelle
const strictCurrentTokenCA = getTokenCAFromURL({ allowLastKnown: false });
const hasStrictCurrentTokenCA =
strictCurrentTokenCA && strictCurrentTokenCA !== 'default_token' && strictCurrentTokenCA !== 'error_token';
const currentTokenCA = hasStrictCurrentTokenCA ? strictCurrentTokenCA : getTokenCAFromURL();
// lastTokenCA yalnızca strict URL çözümü başarılıysa güncellensin (stale fallback zincirini kırar)
if (hasStrictCurrentTokenCA) {
lastTokenCA = strictCurrentTokenCA;
}
// 5) Token değişim kontrolü
const tokenChanged =
(previousTokenCA !== currentTokenCA || previousTokenSymbol !== currentTokenSymbol) &&
currentTokenCA !== 'default_token' &&
currentTokenCA !== 'error_token' &&
currentTokenSymbol !== 'TOKEN';
if (tokenChanged) {
debugLog('🔄 TOKEN CHANGED DETECTED!', {
from: `${previousTokenSymbol} (${previousTokenCA})`,
to: `${currentTokenSymbol} (${currentTokenCA})`
});
// Token deÄŸiÅŸiminde unrealized PnL realize edilmiyor.
// Realized PnL yalnızca SELL işlemlerinde güncellenir.
if (demoPanelVisible) {
debugLog('🔄 Rebuilding dropdowns for new token...');
if (tokenChangeUiRefreshTimeout) {
clearTimeout(tokenChangeUiRefreshTimeout);
}
tokenChangeUiRefreshTimeout = setTimeout(() => {
tokenChangeUiRefreshTimeout = null;
if (isPanelDragging) {
queueDropdownRefreshAfterDrag();
return;
}
calculatePnL();
lastPanelUpdateAt = 0;
lastPnLDisplayUpdateAt = 0;
setupDropdowns();
setupDropdownEvents();
updateDemoPanelValues(true);
updatePnLDisplay(true);
}, 100);
}
}
calculatePnL();
} catch (e) {
console.error('⌠Price update error:', e);
currentTokenPrice = previousTokenPrice > 0 ? previousTokenPrice : 0;
}
}
// SOL fiyatını öncelikle canlı DOM'dan, yoksa API'den çek
function fetchSolPrice() {
const liveSolPrice = getLiveSolPriceFromDOM();
if (liveSolPrice && liveSolPrice > 0) {
currentSolPrice = liveSolPrice;
debugLog('✅ SOL PRICE FROM LIVE DOM:', currentSolPrice);
if (demoPanelVisible) {
updateDemoPanelValues();
}
return;
}
const now = Date.now();
if (now - lastSolApiFetchAt < SOL_API_FALLBACK_INTERVAL_MS) {
return;
}
lastSolApiFetchAt = now;
fetch('https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd')
.then(response => response.json())
.then(data => {
if (data.solana && data.solana.usd) {
currentSolPrice = data.solana.usd;
debugLog('✅ SOL PRICE FROM API:', currentSolPrice);
// Panel açıksa değerleri güncelle
if (demoPanelVisible) {
updateDemoPanelValues();
}
}
})
.catch(() => {
debugLog('⌠SOL price unavailable from DOM/API, keeping last known price:', currentSolPrice);
});
}
// SOL fiyatını periyodik olarak güncelle
function startSolPriceUpdates() {
// İlk güncellemeyi hemen yap
fetchSolPrice();
// Sonra her 1 saniyede bir güncelle
setInterval(fetchSolPrice, SOL_PRICE_UPDATE_INTERVAL_MS);
}
function calculatePnL() {
const tokenPortfolio = getCurrentTokenPortfolio();
const currentTokenCA = getTokenCAFromURL();
debugLog('🔄 Calculating PnL for:', {
symbol: currentTokenSymbol,
CA: currentTokenCA,
amount: tokenPortfolio.amount,
avgPrice: tokenPortfolio.avgPrice,
currentPrice: currentTokenPrice
});
// Reset values
tokenPnL = 0;
tokenPnLPercent = 0;
totalPnL = 0;
totalPnLPercent = 0;
tokenPortfolio.amount = Math.max(0, normalizeNearZero(tokenPortfolio.amount, TOKEN_AMOUNT_ZERO_EPSILON));
tokenPortfolio.avgPrice = Math.max(0, normalizeNearZero(tokenPortfolio.avgPrice));
if (isEffectivelyZero(tokenPortfolio.amount, TOKEN_AMOUNT_ZERO_EPSILON)) {
tokenPortfolio.amount = 0;
tokenPortfolio.avgPrice = 0;
}
// Sadece mevcut token için unrealized PnL hesapla
if (tokenPortfolio.amount > 0 && tokenPortfolio.avgPrice > 0) {
const currentValue = tokenPortfolio.amount * currentTokenPrice;
const costBasis = tokenPortfolio.amount * tokenPortfolio.avgPrice;
tokenPnL = normalizeNearZero(currentValue - costBasis);
tokenPnLPercent = costBasis > 0 ? (tokenPnL / costBasis) * 100 : 0;
debugLog('📊 Current Token PnL:', {
amount: tokenPortfolio.amount,
avgPrice: tokenPortfolio.avgPrice,
currentPrice: currentTokenPrice,
costBasis: costBasis,
currentValue: currentValue,
pnl: tokenPnL,
percent: tokenPnLPercent
});
}
activeTokenRealizedPnL = Number.isFinite(Number(tokenPortfolio.realizedPnL))
? Number(tokenPortfolio.realizedPnL)
: 0;
// Global realized (hesap geneli) state'i de koru
totalRealizedPnL = calculatePortfolioRealizedPnLSum();
// Aktif coin TPnL = aktif realized + aktif unrealized
totalPnL = normalizeNearZero(tokenPnL + activeTokenRealizedPnL);
// Panelde aktif coin TPnL: realized + unrealized birlikte gösterilir.
// Formül: sold + currentBalanceValue - totalInvested
boughtAmount = Number.isFinite(Number(tokenPortfolio.totalInvested)) ? Number(tokenPortfolio.totalInvested) : 0;
soldAmount = Number.isFinite(Number(tokenPortfolio.totalSold)) ? Number(tokenPortfolio.totalSold) : 0;
boughtAmountSol = Number.isFinite(Number(tokenPortfolio.totalInvestedSol)) ? Number(tokenPortfolio.totalInvestedSol) : 0;
soldAmountSol = Number.isFinite(Number(tokenPortfolio.totalSoldSol)) ? Number(tokenPortfolio.totalSoldSol) : 0;
balanceAmount = normalizeNearZero(tokenPortfolio.amount * currentTokenPrice);
const activeTokenTpnl = soldAmount + balanceAmount - boughtAmount;
tpnlAmount = Number.isFinite(activeTokenTpnl) ? normalizeNearZero(activeTokenTpnl) : 0;
const tpnlBase = boughtAmount > 0 ? boughtAmount : 0;
totalPnLPercent = tpnlBase > 0 ? (tpnlAmount / tpnlBase) * 100 : 0;
// NaN kontrolü
if (isNaN(totalPnL)) totalPnL = 0;
if (isNaN(totalPnLPercent)) totalPnLPercent = 0;
if (isNaN(tokenPnL)) tokenPnL = 0;
if (isNaN(tokenPnLPercent)) tokenPnLPercent = 0;
if (isNaN(activeTokenRealizedPnL)) activeTokenRealizedPnL = 0;
if (isNaN(totalRealizedPnL)) totalRealizedPnL = 0;
debugLog('✅ Final PnL:', {
tokenPnL: tokenPnL,
tokenPnLPercent: tokenPnLPercent.toFixed(2) + '%',
activeTokenRealizedPnL: activeTokenRealizedPnL,
totalPnL: totalPnL,
totalPnLPercent: totalPnLPercent.toFixed(2) + '%',
totalRealizedPnL: totalRealizedPnL
});
}
function savePortfolioToStorage() {
totalRealizedPnL = calculatePortfolioRealizedPnLSum();
localStorage.setItem('demoPortfolio', JSON.stringify(demoPortfolio));
localStorage.setItem('totalRealizedPnL', totalRealizedPnL.toString());
localStorage.setItem('totalTradeVolume', totalTradeVolume.toString());
localStorage.setItem('tradeHistory', JSON.stringify(tradeHistory));
debugLog('Portfolio saved to localStorage');
}
function startPriceUpdates() {
// İlk fiyat güncellemesini hemen yap
getCurrentPrices();
fetchSolPrice();
// Sonra periyodik olarak devam et
setInterval(() => {
if (demoMode) {
getCurrentPrices();
if (demoPanelVisible) {
updateDemoPanelValues();
}
}
}, 1000); // 1 saniye (log/perf spam fix)
}
function addDemoTab() {
const tabList = document.querySelector('.dex-tabs-pane-list-container');
if (!tabList) {
setTimeout(addDemoTab, 1000);
return;
}
if (document.querySelector('[data-pane-id="demo_trading"]')) {
return;
}
const demoTabHTML = `
<div class="dex-tabs-pane dex-tabs-pane-lg dex-tabs-pane-blue dex-tabs-pane-underline AV2oC5__dex" data-pane-id="demo_trading" id=":r2if:-demo_trading" role="tab" aria-selected="false" tabindex="-1">
<h2 class="font-inherit">Demo Trading</h2>
</div>
`;
const lastTab = tabList.querySelector('.dex-tabs-pane:last-child');
if (lastTab) {
lastTab.insertAdjacentHTML('beforebegin', demoTabHTML);
addTabClickHandler();
}
}
function addDemoButton() {
const buttonContainer = document.querySelector('.Q68nJL__dex');
if (!buttonContainer) {
setTimeout(addDemoButton, 1000);
return;
}
if (document.querySelector('.demo-trade-button')) {
return;
}
const demoButtonHTML = `
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-coach-popover dex-coachmark-var demo-trade-button">
<button type="button" class="dex-plain-button peM48g__dex MovYo___dex q7TVYE__dex demo-mode-toggle">
<span class="zGJMLt__dex MovYo___dex">
<i class="icon iconfont dex-okx-defi-dex-quick-filled uZT0ej__dex" role="img" aria-hidden="true"></i>
<span>Demo trade</span>
</span>
</button>
</div>
`;
const instantTradeBtn = buttonContainer.querySelector('[data-testid="okd-popup"]:last-child');
if (instantTradeBtn) {
instantTradeBtn.insertAdjacentHTML('beforebegin', demoButtonHTML);
addButtonClickHandler();
updateDemoButton();
}
}
function addTabClickHandler() {
const demoTab = document.querySelector('[data-pane-id="demo_trading"]');
if (demoTab) {
demoTab.addEventListener('click', function() {
switchToDemoMode();
updateActiveTab();
showDemoPanel();
});
}
}
function addButtonClickHandler() {
const demoButton = document.querySelector('.demo-mode-toggle');
if (demoButton) {
setupDemoButtonHoverSync(demoButton);
demoButton.addEventListener('click', function(e) {
e.stopPropagation();
demoMode = !demoMode;
updateDemoButton();
if (demoMode) {
showDemoPanel();
} else {
hideDemoPanel();
}
});
}
}
function switchToDemoMode() {
demoMode = true;
updateDemoButton();
showDemoPanel();
}
function setupDemoButtonHoverSync(demoButton) {
if (!demoButton || demoButton.dataset.hoverSyncBound === '1') return;
demoButton.dataset.hoverSyncBound = '1';
const applyClosedVisualSyncIfNeeded = () => {
if (!demoMode) {
syncDemoButtonClosedStyleFromInstantTrade(demoButton);
}
};
demoButton.addEventListener('mouseenter', applyClosedVisualSyncIfNeeded);
demoButton.addEventListener('mouseleave', applyClosedVisualSyncIfNeeded);
demoButton.addEventListener('focus', applyClosedVisualSyncIfNeeded);
demoButton.addEventListener('blur', applyClosedVisualSyncIfNeeded);
}
function getInstantTradeButtonFromContainer(demoButton) {
if (!demoButton) return null;
const buttonContainer = demoButton.closest('.Q68nJL__dex');
if (!buttonContainer) return null;
return buttonContainer.querySelector('[data-testid="okd-popup"]:last-child button');
}
function syncDemoButtonClosedStyleFromInstantTrade(demoButton) {
if (!demoButton) return;
demoButton.className = DEMO_BUTTON_BASE_CLASSNAME;
const instantTradeButton = getInstantTradeButtonFromContainer(demoButton);
if (!instantTradeButton || instantTradeButton === demoButton) return;
const instantStyle = window.getComputedStyle(instantTradeButton);
if (!instantStyle) return;
demoButton.style.background = instantStyle.background;
demoButton.style.backgroundColor = instantStyle.backgroundColor;
demoButton.style.color = instantStyle.color;
demoButton.style.borderColor = instantStyle.borderColor;
demoButton.style.borderStyle = instantStyle.borderStyle;
demoButton.style.borderWidth = instantStyle.borderWidth;
demoButton.style.boxShadow = instantStyle.boxShadow;
demoButton.style.borderRadius = instantStyle.borderRadius;
}
function updateDemoButton() {
const demoButton = document.querySelector('.demo-mode-toggle');
if (!demoButton) return;
setupDemoButtonHoverSync(demoButton);
const label = demoButton.querySelector('span span');
if (label) {
label.textContent = 'Demo trade';
}
const instantTradeButton = getInstantTradeButtonFromContainer(demoButton);
const isInstantTradeVisible = instantTradeButton && window.getComputedStyle(instantTradeButton).display !== 'none';
if (demoMode || isInstantTradeVisible) {
demoButton.className = DEMO_BUTTON_BASE_CLASSNAME;
demoButton.style.position = 'relative';
demoButton.style.left = '';
demoButton.style.top = '';
demoButton.style.background = '';
demoButton.style.backgroundColor = '';
demoButton.style.color = '';
demoButton.style.borderColor = '';
demoButton.style.borderStyle = '';
demoButton.style.borderWidth = '';
demoButton.style.boxShadow = '';
demoButton.style.borderRadius = '';
} else {
syncDemoButtonClosedStyleFromInstantTrade(demoButton);
demoButton.style.position = 'fixed';
demoButton.style.left = 'calc(50% - 50px)';
demoButton.style.top = '100px';
}
}
function updateActiveTab() {
const tabs = document.querySelectorAll('.dex-tabs-pane');
tabs.forEach(tab => {
tab.classList.remove('dex-tabs-pane-underline-active', 'p0ZfTr__dex');
});
const activeTab = document.querySelector('[data-pane-id="demo_trading"]');
if (activeTab) {
activeTab.classList.add('dex-tabs-pane-underline-active', 'p0ZfTr__dex');
}
}
function ensureDemoPanelStyles() {
if (document.querySelector('#demo-panel-extra-styles')) return;
const style = document.createElement('style');
style.id = 'demo-panel-extra-styles';
style.textContent = `
@keyframes demoCustomizeInputIn {
0% {
opacity: 0;
transform: translateY(8px) scale(0.985);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes demoCustomizeInputOut {
0% {
opacity: 0;
transform: translateY(-8px) scale(0.985);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.demo-trade-panel .demo-buy-input,
.demo-trade-panel .demo-sell-input,
.demo-trade-panel .demo-buy-slippage-input,
.demo-trade-panel .demo-sell-slippage-input,
.demo-trade-panel .demo-buy-fixed-fee-input,
.demo-trade-panel .demo-sell-fixed-fee-input,
.demo-trade-panel .demo-sol-balance-input,
.demo-trade-panel .demo-token-balance-input,
.demo-trade-panel .demo-service-fee-input {
animation: demoCustomizeInputIn 0.22s cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform, opacity;
}
.demo-trade-panel.demo-customize-exit .demo-buy-quick,
.demo-trade-panel.demo-customize-exit .demo-sell-quick,
.demo-trade-panel.demo-customize-exit .demo-buy-slippage-value,
.demo-trade-panel.demo-customize-exit .demo-sell-slippage-value,
.demo-trade-panel.demo-customize-exit .demo-buy-fixed-fee-value,
.demo-trade-panel.demo-customize-exit .demo-sell-fixed-fee-value,
.demo-trade-panel.demo-customize-exit .demo-service-fee-value,
.demo-trade-panel.demo-customize-exit .VHD_xh__dex {
animation: demoCustomizeInputOut 0.22s cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform, opacity;
}
.demo-trade-panel .demo-customize-btn {
transition: background-color 0.16s ease, color 0.16s ease, box-shadow 0.16s ease;
}
.demo-trade-panel .demo-customize-btn i {
transition: transform 0.14s ease;
}
.demo-trade-panel .demo-customize-btn-active i {
transform: scale(1.05);
}
`;
document.head.appendChild(style);
}
function showDemoPanel(forceRebuild = false) {
if (isPanelDragging) {
if (forceRebuild) {
pendingPanelRebuildAfterDrag = true;
} else {
queuePanelValueRefreshAfterDrag();
}
return;
}
if (!getTokenCAFromCurrentURLStrict()) {
hideDemoPanel();
return;
}
if (demoPanelVisible && !forceRebuild) return;
if (forceRebuild) {
hideDemoPanel();
}
ensureDemoPanelStyles();
loadSettingsFromRealPanel();
getCurrentPrices();
// Mevcut token portföyünü al
const tokenPortfolio = getCurrentTokenPortfolio();
// Panel açılırken de canlı DOM'dan icon al
const liveTokenImage = getLiveTokenImageFromDOM();
if (liveTokenImage) {
currentTokenImage = liveTokenImage;
tokenPortfolio.image = liveTokenImage;
} else if (!isLikelyTokenImageUrl(currentTokenImage) && isLikelyTokenImageUrl(tokenPortfolio.image)) {
currentTokenImage = normalizeImageUrl(tokenPortfolio.image);
}
const activeTokenImage = resolveTokenImageUrl(liveTokenImage, currentTokenImage, tokenPortfolio.image, DEFAULT_TOKEN_IMAGE);
const demoPanelHTML = `
<div class="cIknRK__dex gRJpse__dex demo-trade-panel${customizeExitAnimationPending ? ' demo-customize-exit' : ''}" role="button" tabindex="-1" style="left: 400px; top: 200px; opacity: 1; transition: all 0.2s; cursor: default; z-index: 1007;">
<div class="vM9Rz8__dex F_UE_X__dex" role="button" tabindex="0" aria-disabled="false" aria-roledescription="draggable">
<div class="bSH5VS__dex" aria-label="drag-to-move-dialog"><i class="icon iconfont cenOGi__dex dex-okx-defi-dex-drag" role="img" aria-hidden="true"></i></div>
<div>
<div class="NZD_dv__dex">
<div class="dex dex-select-var dex-select select-text Hp9ZjI__dex demo-default-select">
<div class="dex-select-value-box display-area pm_IiY__dex">
<div class="LFH5He__dex">
<div class="D3fk1I__dex">Default</div>
<i class="icon iconfont dex-okds-chevron-down icon-sign select-up dex-select-reference-icon dex-select-reference-icon-md" role="img" aria-label="" aria-hidden="true"></i>
</div>
</div>
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup select-popup-reference"></div>
</div>
</div>
</div>
<div class="aynava__dex" style="display: flex; align-items: center; gap: 6px;">
<!-- RESET BUTON -->
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral" style="margin: 0 0 0 4px;">
<button type="button" class="dex-plain-button hs2qqD__dex demo-reset-balance">
<i class="icon iconfont McyHpj__dex dex-okx-defi-dex-refresh" role="img" aria-label="Reset Demo"></i>
</button>
</div>
<!-- CUSTOMIZE BUTON -->
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup" style="margin: 0;">
<button type="button" class="dex-plain-button hs2qqD__dex demo-customize-btn ${customizeMode ? 'demo-customize-btn-active QzYi7N__dex' : ''}" aria-pressed="${customizeMode ? 'true' : 'false'}" style="${customizeMode ? 'background-color: rgba(188,255,47,0.16); color: #bcff2f; border-radius: 8px;' : ''}">
<i class="icon iconfont McyHpj__dex ${customizeMode ? 'dex-okx-defi-dex-check' : 'dex-okx-defi-marketplace-edit'}" role="img" aria-label="Customize amounts"></i>
</button>
</div>
<!-- HOTKEYS BUTON -->
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral " style="margin: 0;">
<button type="button" class="dex-plain-button hs2qqD__dex demo-hotkeys-btn ${hotkeysEnabled ? 'demo-hotkeys-btn-active' : ''}" aria-pressed="${hotkeysEnabled ? 'true' : 'false'}" style="${hotkeysEnabled ? 'background-color: rgba(188,255,47,0.16); color: #bcff2f; border-radius: 8px;' : ''}">
<i class="icon iconfont McyHpj__dex dex-okx-defi-dex-keyboard" role="img" aria-label="Click to activate hotkeys"></i>
</button>
</div>
<!-- PNL BUTON - GÜNCELLENDİ -->
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral " style="margin: 0;">
<button type="button" class="dex-plain-button hs2qqD__dex demo-pnl-btn ${pnlSectionVisible ? 'QzYi7N__dex' : ''}">
<i class="icon iconfont McyHpj__dex dex-okx-defi-web3-leaderboard ${pnlSectionVisible ? 'WAaPvo__dex' : ''}" role="img" aria-label="PnL"></i>
</button>
</div>
<i class="icon iconfont vuymPd__dex dex-okx-defi-marketplace-close dex-a11y-button demo-panel-close" role="button" aria-label="Close" tabindex="0" style="margin: 0;"></i>
</div>
</div>
<div class="JlS9Ws__dex" style="padding-bottom: 0;">
<div>
<div class="flex justify-between items-center uyKNVr__dex">
<div class="dex dex-select-var dex-select select-text lnOLCt__dex instant-trade-token-selector-buy demo-buy-select">
<div class="dex-select-value-box display-area NC4WG2__dex">
<div class="e4mTHT__dex flex items-center font-12">
<span class="ioQevx__dex">Buy with</span>
<span class="font-500 UE7t1T__dex">SOL</span>
<i class="icon iconfont dex-okx-defi-dex-market-sort-down RQGYJ6__dex" role="img" aria-hidden="true"></i>
</div>
</div>
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup select-popup-reference"></div>
</div>
<div class="flex items-center">
${customizeMode ? `
<div class="dex dex-input-var dex-input dex-input-sm" style="width: 80px; margin-right: 8px;">
<div class="dex-input-box auto-size" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="9007199254740991" autocomplete="off" step="1" id="demo-sol-balance-input" class="dex-input-input demo-sol-balance-input" autocapitalize="off" autocorrect="off" type="text" value="${demoPortfolio.SOL.amount.toFixed(4)}" name="demo_sol_balance_input">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `
<span class="VHD_xh__dex">${formatBalanceDisplay(demoPortfolio.SOL.amount)}</span>
`}
<picture class="dex dex-picture dex-picture-font"><source srcset="${buildImageSrcset(SOL_TOKEN_IMAGE)}"><img width="16" height="16" class="Lrw8Qc__dex" alt="" src="${SOL_TOKEN_IMAGE}" style="width: 16px; height: 16px;"></picture>
</div>
</div>
<div class="flex justify-between lFouoK__dex">
<div class="k_jil0__dex xr2g7U__dex">
<div class="ThQSDh__dex options-wrapper">
${customizeMode ? `
<!-- CUSTOMIZE MODE: INPUT ALANLARI -->
<div class="dex dex-input-var dex-input dex-input-md __XCfV__dex mW93WY__dex">
<div class="dex-input-box auto-size E_NN8J__dex utlHnK__dex" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="9007199254740991" autocomplete="off" step="1" id="demo-buy-input-1" class="dex-input-input PfVQrS__dex demo-buy-input" autocapitalize="off" autocorrect="off" type="text" value="${tempBuyAmounts[0]}" name="demo_buy_input_1">
<div class="dex-input-suffix"></div>
</div>
</div>
<div class="dex dex-input-var dex-input dex-input-md __XCfV__dex mW93WY__dex">
<div class="dex-input-box auto-size E_NN8J__dex utlHnK__dex" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="9007199254740991" autocomplete="off" step="1" id="demo-buy-input-2" class="dex-input-input PfVQrS__dex demo-buy-input" autocapitalize="off" autocorrect="off" type="text" value="${tempBuyAmounts[1]}" name="demo_buy_input_2">
<div class="dex-input-suffix"></div>
</div>
</div>
<div class="dex dex-input-var dex-input dex-input-md __XCfV__dex mW93WY__dex">
<div class="dex-input-box auto-size E_NN8J__dex utlHnK__dex" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="9007199254740991" autocomplete="off" step="1" id="demo-buy-input-3" class="dex-input-input PfVQrS__dex demo-buy-input" autocapitalize="off" autocorrect="off" type="text" value="${tempBuyAmounts[2]}" name="demo_buy_input_3">
<div class="dex-input-suffix"></div>
</div>
</div>
<div class="dex dex-input-var dex-input dex-input-md __XCfV__dex mW93WY__dex">
<div class="dex-input-box auto-size E_NN8J__dex utlHnK__dex" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="9007199254740991" autocomplete="off" step="1" id="demo-buy-input-4" class="dex-input-input PfVQrS__dex demo-buy-input" autocapitalize="off" autocorrect="off" type="text" value="${tempBuyAmounts[3]}" name="demo_buy_input_4">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `
<!-- NORMAL MODE: BUTONLAR -->
<div class="flex items-center kw6r9M__dex">
<div class="H5f_ax__dex Cg7h1c__dex VmOKPA__dex demo-buy-quick" data-amount="${parseFloat(currentSettings.buyAmounts[0]) || 0.03}" style="cursor: pointer; background-color: rgba(18,18,18,0.72); color: #bcff2f; ${hotkeysEnabled && hotkeysSpacePressed ? 'border-radius: 8px 0 0 8px;' : ''}">
<div class="l5GPKh__dex ellipsis" style="color: #bcff2f;">${currentSettings.buyAmounts[0] || '0.03'}</div>
</div>
${hotkeysEnabled && hotkeysSpacePressed ? getHotkeyBadgeHTML('Q') : ''}
</div>
<div class="flex items-center kw6r9M__dex">
<div class="H5f_ax__dex Cg7h1c__dex VmOKPA__dex demo-buy-quick" data-amount="${parseFloat(currentSettings.buyAmounts[1]) || 0.05}" style="cursor: pointer; background-color: rgba(18,18,18,0.72); color: #bcff2f; ${hotkeysEnabled && hotkeysSpacePressed ? 'border-radius: 8px 0 0 8px;' : ''}">
<div class="l5GPKh__dex ellipsis" style="color: #bcff2f;">${currentSettings.buyAmounts[1] || '0.05'}</div>
</div>
${hotkeysEnabled && hotkeysSpacePressed ? getHotkeyBadgeHTML('W') : ''}
</div>
<div class="flex items-center kw6r9M__dex">
<div class="H5f_ax__dex Cg7h1c__dex VmOKPA__dex demo-buy-quick" data-amount="${parseFloat(currentSettings.buyAmounts[2]) || 0.07}" style="cursor: pointer; background-color: rgba(18,18,18,0.72); color: #bcff2f; ${hotkeysEnabled && hotkeysSpacePressed ? 'border-radius: 8px 0 0 8px;' : ''}">
<div class="l5GPKh__dex ellipsis" style="color: #bcff2f;">${currentSettings.buyAmounts[2] || '0.07'}</div>
</div>
${hotkeysEnabled && hotkeysSpacePressed ? getHotkeyBadgeHTML('E') : ''}
</div>
<div class="flex items-center kw6r9M__dex">
<div class="H5f_ax__dex Cg7h1c__dex VmOKPA__dex demo-buy-quick" data-amount="${parseFloat(currentSettings.buyAmounts[3]) || 0.1}" style="cursor: pointer; background-color: rgba(18,18,18,0.72); color: #bcff2f; ${hotkeysEnabled && hotkeysSpacePressed ? 'border-radius: 8px 0 0 8px;' : ''}">
<div class="l5GPKh__dex ellipsis" style="color: #bcff2f;">${currentSettings.buyAmounts[3] || '0.1'}</div>
</div>
${hotkeysEnabled && hotkeysSpacePressed ? getHotkeyBadgeHTML('R') : ''}
</div>
`}
</div>
</div>
</div>
<div class="flex justify-start Nox6EF__dex">
<div class="flex items-center Fb9n18__dex" role="presentation">
<i class="icon iconfont dex-okx-defi-dex-slippage GuPJu7__dex" role="img" aria-hidden="true"></i>
${customizeMode ? `
<div class="dex dex-input-var dex-input dex-input-sm" style="width: 70px; margin-left: 4px;">
<div class="dex-input-box auto-size" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" autocomplete="off" step="0.1" id="demo-buy-slippage-input" class="dex-input-input demo-buy-slippage-input" autocapitalize="off" autocorrect="off" type="text" value="${tempBuySlippage}" name="demo_buy_slippage_input">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `
<span class="font-12 ml-4 font-500 demo-buy-slippage-value">${currentSettings.buySlippage}</span>
`}
<span class="ROEGPH__dex"></span>
<i class="icon iconfont dex-okx-defi-dex-fee GuPJu7__dex" role="img" aria-hidden="true"></i>
${customizeMode ? `
<div class="dex dex-input-var dex-input dex-input-sm" style="width: 90px; margin-left: 4px;">
<div class="dex-input-box auto-size" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" autocomplete="off" step="0.000001" id="demo-buy-fixed-fee-input" class="dex-input-input demo-buy-fixed-fee-input" autocapitalize="off" autocorrect="off" type="text" value="${tempBuyFixedFeeSol}" name="demo_buy_fixed_fee_input">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `
<span class="font-12 ml-4 font-500 demo-buy-fixed-fee-value">${currentSettings.buyFixedFeeSol}</span>
`}
<span class="ROEGPH__dex"></span>
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral "><div class="flex items-center"><i class="icon iconfont dex-okx-defi-web3-shield Xm6VIO__dex" role="img" aria-label="config-item"></i><span class="Or4Gzq__dex">Auto</span></div></div>
<i class="icon iconfont dex-okx-defi-marketplace-chevron-right C0NIv2__dex demo-buy-settings" role="img" aria-hidden="true" style="cursor: pointer;"></i>
</div>
</div>
</div>
<div class="QwqNAT__dex">
<div class="flex justify-between items-center uyKNVr__dex">
<div class="dex dex-select-var dex-select select-text lnOLCt__dex instant-trade-token-selector-sell demo-sell-select">
<div class="dex-select-value-box display-area NC4WG2__dex">
<div class="e4mTHT__dex flex items-center font-12">
<span class="ioQevx__dex demo-sell-for-label">Sell for <span class="font-500 UE7t1T__dex">SOL</span></span>
<i class="icon iconfont dex-okx-defi-dex-market-sort-down RQGYJ6__dex" role="img" aria-hidden="true" style="margin-left: -2px;"></i>
</div>
</div>
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup select-popup-reference"></div>
</div>
<div class="flex items-center">
${customizeMode ? `
<div class="dex dex-input-var dex-input dex-input-sm" style="width: 80px; margin-right: 8px;">
<div class="dex-input-box auto-size" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="9007199254740991" autocomplete="off" step="1" id="demo-token-balance-input" class="dex-input-input demo-token-balance-input" autocapitalize="off" autocorrect="off" type="text" value="${tokenPortfolio.amount.toFixed(4)}" name="demo_token_balance_input">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `
<span class="VHD_xh__dex">${formatBalanceDisplay(tokenPortfolio.amount)}</span>
`}
<picture class="dex dex-picture dex-picture-font"><source srcset="${buildImageSrcset(activeTokenImage)}"><img width="16" height="16" class="Lrw8Qc__dex" alt="" src="${activeTokenImage}" style="width: 16px; height: 16px;"></picture>
</div>
</div>
<div class="flex justify-between lFouoK__dex">
<div class="k_jil0__dex xr2g7U__dex">
<div class="ThQSDh__dex options-wrapper">
${customizeMode ? `
<!-- CUSTOMIZE MODE: INPUT ALANLARI -->
<div class="dex dex-input-var dex-input dex-input-md __XCfV__dex mW93WY__dex">
<div class="dex-input-box auto-size E_NN8J__dex utlHnK__dex" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="100" autocomplete="off" step="1" id="demo-sell-input-1" class="dex-input-input PfVQrS__dex demo-sell-input" autocapitalize="off" autocorrect="off" type="text" value="${tempSellPercents[0]}" name="demo_sell_input_1">
<div class="dex-input-suffix"></div>
</div>
</div>
<div class="dex dex-input-var dex-input dex-input-md __XCfV__dex mW93WY__dex">
<div class="dex-input-box auto-size E_NN8J__dex utlHnK__dex" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="100" autocomplete="off" step="1" id="demo-sell-input-2" class="dex-input-input PfVQrS__dex demo-sell-input" autocapitalize="off" autocorrect="off" type="text" value="${tempSellPercents[1]}" name="demo_sell_input_2">
<div class="dex-input-suffix"></div>
</div>
</div>
<div class="dex dex-input-var dex-input dex-input-md __XCfV__dex mW93WY__dex">
<div class="dex-input-box auto-size E_NN8J__dex utlHnK__dex" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="100" autocomplete="off" step="1" id="demo-sell-input-3" class="dex-input-input PfVQrS__dex demo-sell-input" autocapitalize="off" autocorrect="off" type="text" value="${tempSellPercents[2]}" name="demo_sell_input_3">
<div class="dex-input-suffix"></div>
</div>
</div>
<div class="dex dex-input-var dex-input dex-input-md __XCfV__dex mW93WY__dex">
<div class="dex-input-box auto-size E_NN8J__dex utlHnK__dex" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="100" autocomplete="off" step="1" id="demo-sell-input-4" class="dex-input-input PfVQrS__dex demo-sell-input" autocapitalize="off" autocorrect="off" type="text" value="${tempSellPercents[3]}" name="demo_sell_input_4">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `
<!-- NORMAL MODE: BUTONLAR -->
<div class="flex items-center kw6r9M__dex">
<div class="H5f_ax__dex ODJ17x__dex VmOKPA__dex demo-sell-quick" data-percent="${parseFloat((currentSettings.sellPercents[0] || '25%').replace('%', '')) || 25}" style="cursor: pointer; background-color: rgba(18,18,18,0.72); color: #fc46ab; ${hotkeysEnabled && hotkeysSpacePressed ? 'border-radius: 8px 0 0 8px;' : ''}">
<div class="l5GPKh__dex ellipsis" style="color: #fc46ab;">${currentSettings.sellPercents[0] || '25%'}</div>
</div>
${hotkeysEnabled && hotkeysSpacePressed ? getHotkeyBadgeHTML('A') : ''}
</div>
<div class="flex items-center kw6r9M__dex">
<div class="H5f_ax__dex ODJ17x__dex VmOKPA__dex demo-sell-quick" data-percent="${parseFloat((currentSettings.sellPercents[1] || '50%').replace('%', '')) || 50}" style="cursor: pointer; background-color: rgba(18,18,18,0.72); color: #fc46ab; ${hotkeysEnabled && hotkeysSpacePressed ? 'border-radius: 8px 0 0 8px;' : ''}">
<div class="l5GPKh__dex ellipsis" style="color: #fc46ab;">${currentSettings.sellPercents[1] || '50%'}</div>
</div>
${hotkeysEnabled && hotkeysSpacePressed ? getHotkeyBadgeHTML('S') : ''}
</div>
<div class="flex items-center kw6r9M__dex">
<div class="H5f_ax__dex ODJ17x__dex VmOKPA__dex demo-sell-quick" data-percent="${parseFloat((currentSettings.sellPercents[2] || '75%').replace('%', '')) || 75}" style="cursor: pointer; background-color: rgba(18,18,18,0.72); color: #fc46ab; ${hotkeysEnabled && hotkeysSpacePressed ? 'border-radius: 8px 0 0 8px;' : ''}">
<div class="l5GPKh__dex ellipsis" style="color: #fc46ab;">${currentSettings.sellPercents[2] || '75%'}</div>
</div>
${hotkeysEnabled && hotkeysSpacePressed ? getHotkeyBadgeHTML('D') : ''}
</div>
<div class="flex items-center kw6r9M__dex">
<div class="H5f_ax__dex ODJ17x__dex VmOKPA__dex demo-sell-quick" data-percent="${parseFloat((currentSettings.sellPercents[3] || '100%').replace('%', '')) || 100}" style="cursor: pointer; background-color: rgba(18,18,18,0.72); color: #fc46ab; ${hotkeysEnabled && hotkeysSpacePressed ? 'border-radius: 8px 0 0 8px;' : ''}">
<div class="l5GPKh__dex ellipsis" style="color: #fc46ab;">${currentSettings.sellPercents[3] || '100%'}</div>
</div>
${hotkeysEnabled && hotkeysSpacePressed ? getHotkeyBadgeHTML('F') : ''}
</div>
`}
</div>
</div>
</div>
<div class="flex justify-start Nox6EF__dex">
<div class="flex items-center Fb9n18__dex" role="presentation">
<i class="icon iconfont dex-okx-defi-dex-slippage GuPJu7__dex" role="img" aria-hidden="true"></i>
${customizeMode ? `
<div class="dex dex-input-var dex-input dex-input-sm" style="width: 70px; margin-left: 4px;">
<div class="dex-input-box auto-size" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" autocomplete="off" step="0.1" id="demo-sell-slippage-input" class="dex-input-input demo-sell-slippage-input" autocapitalize="off" autocorrect="off" type="text" value="${tempSellSlippage}" name="demo_sell_slippage_input">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `
<span class="font-12 ml-4 font-500 demo-sell-slippage-value">${currentSettings.sellSlippage}</span>
`}
<span class="ROEGPH__dex"></span>
<i class="icon iconfont dex-okx-defi-dex-fee GuPJu7__dex" role="img" aria-hidden="true"></i>
${customizeMode ? `
<div class="dex dex-input-var dex-input dex-input-sm" style="width: 90px; margin-left: 4px;">
<div class="dex-input-box auto-size" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" autocomplete="off" step="0.000001" id="demo-sell-fixed-fee-input" class="dex-input-input demo-sell-fixed-fee-input" autocapitalize="off" autocorrect="off" type="text" value="${tempSellFixedFeeSol}" name="demo_sell_fixed_fee_input">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `
<span class="font-12 ml-4 font-500 demo-sell-fixed-fee-value">${currentSettings.sellFixedFeeSol}</span>
`}
<span class="ROEGPH__dex"></span>
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral "><div class="flex items-center"><i class="icon iconfont dex-okx-defi-web3-shield Xm6VIO__dex" role="img" aria-label="config-item"></i><span class="Or4Gzq__dex">Auto</span></div></div>
<i class="icon iconfont dex-okx-defi-marketplace-chevron-right C0NIv2__dex demo-sell-settings" role="img" aria-hidden="true" style="cursor: pointer;"></i>
</div>
</div>
</div>
<!-- SERVICE FEE KISMI - BOÅžLUK SADECE SERVICE FEE ALTINDA -->
<div class="MEKLl5__dex LAaFhW__dex" style="justify-content: center; margin: 0px 0 24px;">
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral cursor-pointer flex items-center underline-dash vgre7c__dex"><span class="Nx6sMk__dex">Service fee</span></div>
${customizeMode ? `
<div class="dex dex-input-var dex-input dex-input-sm" style="width: 80px; display: inline-block;">
<div class="dex-input-box auto-size" role="none">
<input autocomplete="off" readonly="" type="hidden" style="display: none;">
<input inputmode="decimal" enable_thousands="true" min="0" max="100" autocomplete="off" step="0.01" id="demo-service-fee-input" class="dex-input-input demo-service-fee-input" autocapitalize="off" autocorrect="off" type="text" value="${tempServiceFee}" name="demo_service_fee_input">
<div class="dex-input-suffix"></div>
</div>
</div>
` : `<span class="demo-service-fee-value">${currentSettings.serviceFee}</span>`}
</div>
<!-- PNL BÖLÜMÜ - YENİ TASARIM -->
${pnlSectionVisible ? `
<!-- ÇİZGİ: EXTRA ALT BOŞLUK KALDIRILDI -->
<div class="zt1bsn__dex" style="margin: 0 -12px !important; width: calc(100% + 24px);"></div>
<div class="flex gap-2 font-12 items-center">
<div class="K_Nnu2__dex">
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral ">
<div class="sU7u0q__dex underline-dash">Bought</div>
</div>
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral ">
<div class="bXHX98__dex HV7ko9__dex demo-pnl-bought">${formatPnLSummaryAmountDisplay(boughtAmount, showFiat)}</div>
</div>
</div>
<div class="K_Nnu2__dex">
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral ">
<div class="sU7u0q__dex underline-dash">Sold</div>
</div>
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral ">
<div class="bXHX98__dex HV7ko9__dex demo-pnl-sold">${formatPnLSummaryAmountDisplay(soldAmount, showFiat)}</div>
</div>
</div>
<div class="K_Nnu2__dex">
<div class="sU7u0q__dex">Balance</div>
<div class="bXHX98__dex HV7ko9__dex demo-pnl-balance">${formatPnLSummaryAmountDisplay(balanceAmount, showFiat)}</div>
</div>
<div class="lWdbPD__dex">
<div class="flex flex-row items-center justify-center">
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral ">
<div class="sU7u0q__dex underline-dash">T/R/UPnl</div>
</div>
<div data-testid="okd-popup" class="dex dex-popup-var dex-popup dex-tooltip dex-tooltip-var dex-tooltip-neutral ">
<div role="button" tabindex="0" class="l6WLJO__dex border-1 rounded-full w-16 h-16 ml-2 demo-currency-toggle" data-icon-mode="${showFiat ? 'fiat' : 'token'}">
${getCurrencyToggleIconHTML(showFiat)}
</div>
</div>
</div>
<div class="VlintX__dex">
<div class="ibsEqm__dex HV7ko9__dex VlintX__dex demo-pnl-inline-values" style="display: flex; align-items: center; gap: 0; white-space: nowrap;">
<span class="demo-pnl-tpnl">${formatPnLIntegerDisplay(totalPnL, showFiat)}</span><span class="demo-pnl-space" style="display: inline-block; width: 0.35em; min-width: 0.35em;"></span><span class="demo-pnl-rpnl">${formatPnLIntegerDisplay(activeTokenRealizedPnL, showFiat)}</span><span class="demo-pnl-space" style="display: inline-block; width: 0.35em; min-width: 0.35em;"></span><span class="demo-pnl-upnl">${formatPnLIntegerDisplay(tokenPnL, showFiat)}</span>
</div>
</div>
</div>
</div>
` : ''}
</div>
</div>
`;
hideDemoPanel();
document.body.insertAdjacentHTML('beforeend', demoPanelHTML);
setTimeout(() => {
try {
setupDemoPanelEvents();
setupAdvancedDragAndDrop();
setupDropdowns();
setupDropdownEvents();
updateDemoPanelValues();
} catch (error) {
console.error('Error setting up demo panel:', error);
}
}, 200);
demoPanelVisible = true;
customizeExitAnimationPending = false;
}
function hideDemoPanel() {
const demoPanel = document.querySelector('.demo-trade-panel');
if (demoPanel) {
demoPanel.remove();
}
if (tokenChangeUiRefreshTimeout) {
clearTimeout(tokenChangeUiRefreshTimeout);
tokenChangeUiRefreshTimeout = null;
}
isPanelDragging = false;
pendingPanelRebuildAfterDrag = false;
pendingDropdownRefreshAfterDrag = false;
pendingPanelValueRefreshAfterDrag = false;
demoPanelVisible = false;
}
function setupAdvancedDragAndDrop() {
const demoPanel = document.querySelector('.demo-trade-panel');
if (!demoPanel) return;
let isDragging = false;
let startX, startY, initialX, initialY;
const dragHandle = demoPanel.querySelector('.vM9Rz8__dex');
const dragStart = (e) => {
if (e.target.closest('.vM9Rz8__dex')) {
isDragging = true;
isPanelDragging = true;
startX = e.clientX || e.touches[0].clientX;
startY = e.clientY || e.touches[0].clientY;
const rect = demoPanel.getBoundingClientRect();
initialX = rect.left;
initialY = rect.top;
demoPanel.style.cursor = 'grabbing';
demoPanel.style.transition = 'none';
demoPanel.style.opacity = '0.85';
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
};
const drag = (e) => {
if (!isDragging) return;
const currentX = e.clientX || e.touches[0].clientX;
const currentY = e.clientY || e.touches[0].clientY;
const deltaX = currentX - startX;
const deltaY = currentY - startY;
demoPanel.style.left = (initialX + deltaX) + 'px';
demoPanel.style.top = (initialY + deltaY) + 'px';
e.preventDefault();
e.stopPropagation();
};
const dragEnd = () => {
if (!isDragging) return;
isDragging = false;
isPanelDragging = false;
demoPanel.style.cursor = 'default';
demoPanel.style.transition = 'all 0.2s';
demoPanel.style.opacity = '1';
const hadPendingRebuild = pendingPanelRebuildAfterDrag;
flushDeferredPanelActionsAfterDrag();
if (!hadPendingRebuild) {
lastPanelUpdateAt = 0;
lastPnLDisplayUpdateAt = 0;
updateDemoPanelValues();
}
};
dragHandle.addEventListener('mousedown', dragStart, true);
dragHandle.addEventListener('touchstart', dragStart, { passive: false });
document.addEventListener('mousemove', drag, true);
document.addEventListener('touchmove', drag, { passive: false });
document.addEventListener('mouseup', dragEnd, true);
document.addEventListener('touchend', dragEnd, true);
document.addEventListener('mouseleave', dragEnd, true);
}
function setupDropdowns() {
if (isPanelDragging) {
queueDropdownRefreshAfterDrag();
return;
}
debugLog('🔄 Setting up dropdowns with current token:', currentTokenSymbol, currentTokenImage);
// Default select dropdown
const defaultSelect = document.querySelector('.demo-default-select');
if (defaultSelect) {
const popupReference = defaultSelect.querySelector('.select-popup-reference');
if (popupReference) {
popupReference.innerHTML = `
<div class="dex dex-popup-var dex-popup-layer dex-popup-layer-visible" style="z-index: 10000; visibility: hidden; position: absolute; left: 0px; top: 0px; margin: 0px; transform: translate(0px, 34px);" data-popper-placement="bottom-start">
<div class="dex-popup-layer-content" style="width: 140px;">
<div class="dex-select-var dex-select-option dex-select-option-pc yip61W__dex align-left drop-mode option-md">
<div class="dex-select-option-box">
<div class="pc-option-scroll" style="max-height: 200px;">
<div class="dex-select-item-container dex-select-item-container-real">
<div class="dex-select-item dex-select-item-active dex-dropdown-option demo-default-option" role="option" aria-selected="true" data-value="Default">
<div class="flex justify-between items-center Be9HsZ__dex">
<div class="vZhXdm__dex">Default</div>
<i class="icon iconfont dex-okx-defi-dex-check DaCpQM__dex qriW7s__dex" role="img" aria-hidden="true"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
}
}
// Buy token select dropdown
const buySelect = document.querySelector('.demo-buy-select');
if (buySelect) {
const popupReference = buySelect.querySelector('.select-popup-reference');
if (popupReference) {
const solBalance = demoPortfolio.SOL.amount.toFixed(4);
const solValue = (demoPortfolio.SOL.amount * currentSolPrice).toFixed(2);
popupReference.innerHTML = `
<div class="dex dex-popup-var dex-popup-layer dex-popup-layer-visible" style="z-index: 10001; visibility: hidden; position: absolute; left: 0px; top: 0px; margin: 0px; transform: translate(0px, 40px);" data-popper-placement="bottom-start">
<div class="dex-popup-layer-content" style="width: 308px;">
<div class="dex-select-var dex-select-option dex-select-option-pc WFwMwa__dex instant-trade-token-selector-option-cont-buy align-left drop-mode option-md">
<div class="dex-select-option-box">
<div class="pc-option-scroll" style="max-height: 208px;">
<div class="dex-select-item-container dex-select-item-container-real">
<div class="dex-select-item dex-select-item-active dex-dropdown-option demo-buy-option" role="option" aria-selected="true" data-value="SOL">
<div class="flex justify-between items-center G9BMtd__dex">
<div class="flex items-center">
<picture class="dex dex-picture dex-picture-font">
<source srcset="${buildImageSrcset(SOL_TOKEN_IMAGE)}">
<img width="28" height="28" class="jxzKU9__dex" alt="" src="${SOL_TOKEN_IMAGE}" style="width: 28px; height: 28px;">
</picture>
<div class="flex flex-col">
<span class="tiQWEG__dex">SOL</span>
<span class="inAvcR__dex">Solana</span>
</div>
</div>
<div class="flex flex-col tsKlgJ__dex">
<span class="tiQWEG__dex">${solBalance}</span>
<span class="inAvcR__dex">$${solValue}</span>
</div>
</div>
</div>
<div class="dex-select-item dex-dropdown-option demo-buy-option" role="option" aria-selected="false" data-value="USDC">
<div class="flex justify-between items-center G9BMtd__dex">
<div class="flex items-center">
<picture class="dex dex-picture dex-picture-font">
<source srcset="https://web3.okx.com/cdn/web3/currency/token/large/637-0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b-107/type=default_90_0?v=1756203256814&x-oss-process=image/format,webp/ignore-error,1">
<img width="28" height="28" class="jxzKU9__dex" alt="" src="https://web3.okx.com/cdn/web3/currency/token/large/637-0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b-107/type=default_90_0?v=1756203256814" style="width: 28px; height: 28px;">
</picture>
<div class="flex flex-col">
<span class="tiQWEG__dex">USDC</span>
<span class="inAvcR__dex">USD Coin</span>
</div>
</div>
<div class="flex flex-col tsKlgJ__dex">
<span class="tiQWEG__dex">0</span>
<span class="inAvcR__dex">$0.00</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
// Sadece SOL seçeneğini bırak
popupReference
.querySelectorAll('.demo-buy-option:not([data-value="SOL"])')
.forEach(el => el.remove());
}
}
// Sell token select dropdown
const sellSelect = document.querySelector('.demo-sell-select');
if (sellSelect) {
const popupReference = sellSelect.querySelector('.select-popup-reference');
if (popupReference) {
const tokenPortfolio = getCurrentTokenPortfolio();
const tokenBalance = tokenPortfolio.amount.toFixed(4);
const tokenValueUsdNumeric = tokenPortfolio.amount * currentTokenPrice;
const tokenValueInUsd = tokenValueUsdNumeric.toFixed(2);
const tokenValueInSol = currentSolPrice > 0
? (tokenValueUsdNumeric / currentSolPrice).toFixed(4)
: '0.0000';
const liveTokenImage = getLiveTokenImageFromDOM();
if (liveTokenImage) {
currentTokenImage = liveTokenImage;
tokenPortfolio.image = liveTokenImage;
}
const resolvedTokenImage = resolveTokenImageUrl(liveTokenImage, currentTokenImage, tokenPortfolio.image, DEFAULT_TOKEN_IMAGE);
popupReference.innerHTML = `
<div class="dex dex-popup-var dex-popup-layer dex-popup-layer-visible" style="z-index: 10002; visibility: hidden; position: absolute; left: 0px; top: 0px; margin: 0px; transform: translate(0px, 40px);" data-popper-placement="bottom-start">
<div class="dex-popup-layer-content" style="width: 308px;">
<div class="dex-select-var dex-select-option dex-select-option-pc WFwMwa__dex instant-trade-token-selector-option-cont-sell align-left drop-mode option-md">
<div class="dex-select-option-box">
<div class="pc-option-scroll" style="max-height: 208px;">
<div class="dex-select-item-container dex-select-item-container-real">
<div class="dex-select-item dex-select-item-active dex-dropdown-option demo-sell-option" role="option" aria-selected="true" data-value="${currentTokenSymbol}">
<div class="flex justify-between items-center G9BMtd__dex">
<div class="flex items-center">
<picture class="dex dex-picture dex-picture-font">
<source srcset="${buildImageSrcset(SOL_TOKEN_IMAGE)}">
<img width="28" height="28" class="jxzKU9__dex" alt="" src="${SOL_TOKEN_IMAGE}" style="width: 28px; height: 28px;">
</picture>
<div class="flex flex-col">
<span class="tiQWEG__dex">${currentTokenSymbol}</span>
<span class="inAvcR__dex">${currentTokenName || 'Current Token'}</span>
</div>
</div>
<div class="flex flex-col tsKlgJ__dex">
<span class="tiQWEG__dex">${tokenBalance}</span>
<span class="inAvcR__dex">$${tokenValueInUsd}</span>
</div>
</div>
</div>
<div class="dex-select-item dex-dropdown-option demo-sell-option" role="option" aria-selected="false" data-value="SOL">
<div class="flex justify-between items-center G9BMtd__dex">
<div class="flex items-center">
<picture class="dex dex-picture dex-picture-font">
<source srcset="${buildImageSrcset(SOL_TOKEN_IMAGE)}">
<img width="28" height="28" class="jxzKU9__dex" alt="" src="${SOL_TOKEN_IMAGE}" style="width: 28px; height: 28px;">
</picture>
<div class="flex flex-col">
<span class="tiQWEG__dex">SOL</span>
<span class="inAvcR__dex">Solana</span>
</div>
</div>
<div class="flex flex-col tsKlgJ__dex">
<span class="tiQWEG__dex">${tokenValueInSol}</span>
<span class="inAvcR__dex">$${tokenValueInUsd}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
popupReference
.querySelectorAll('.demo-sell-option:not([data-value="SOL"])')
.forEach(el => el.remove());
const sellOnlyOption = popupReference.querySelector('.demo-sell-option[data-value="SOL"]');
if (sellOnlyOption) {
sellOnlyOption.classList.add('dex-select-item-active');
sellOnlyOption.setAttribute('aria-selected', 'true');
}
const sellDisplayLabel = document.querySelector('.demo-sell-select .demo-sell-for-label');
if (sellDisplayLabel) {
sellDisplayLabel.innerHTML = 'Sell for <span class="font-500 UE7t1T__dex">SOL</span>';
}
debugLog('✅ Sell dropdown updated with SOL-only option');
}
}
}
function toggleDropdownPopupVisibility(selectValueBox, popupLayer, zIndex = '10009') {
if (!selectValueBox || !popupLayer) return;
const isVisible = popupLayer.style.visibility === 'visible';
document.querySelectorAll('.dex-popup-layer').forEach(layer => {
layer.style.visibility = 'hidden';
layer.style.zIndex = '-1';
});
if (isVisible) return;
const selectWrapper = selectValueBox.closest('.dex-select') || selectValueBox;
const wrapperRect = selectWrapper.getBoundingClientRect();
const popupOffsetY = Math.round((wrapperRect.height || selectValueBox.getBoundingClientRect().height || 34) + DROPDOWN_VERTICAL_OFFSET_PX);
popupLayer.style.position = 'absolute';
popupLayer.style.left = '0px';
popupLayer.style.top = '0px';
popupLayer.style.margin = '0';
popupLayer.style.transform = `translate(0px, ${popupOffsetY}px)`;
popupLayer.style.visibility = 'visible';
popupLayer.style.zIndex = zIndex;
}
function setupDropdownEvents() {
if (isPanelDragging) {
queueDropdownRefreshAfterDrag();
return;
}
debugLog('Setting up dropdown events...');
// Default select
const defaultSelect = document.querySelector('.demo-default-select .dex-select-value-box');
if (defaultSelect) {
defaultSelect.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const popup = this.closest('.dex-select').querySelector('.dex-popup-layer');
toggleDropdownPopupVisibility(this, popup, '10008');
return false;
}, true);
}
// Buy select
const buySelect = document.querySelector('.demo-buy-select .dex-select-value-box');
if (buySelect) {
buySelect.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const popup = this.closest('.dex-select').querySelector('.dex-popup-layer');
toggleDropdownPopupVisibility(this, popup, '10009');
return false;
}, true);
}
// Sell select
const sellSelect = document.querySelector('.demo-sell-select .dex-select-value-box');
if (sellSelect) {
sellSelect.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const popup = this.closest('.dex-select').querySelector('.dex-popup-layer');
toggleDropdownPopupVisibility(this, popup, '10009');
return false;
}, true);
}
// Dropdown options
setTimeout(() => {
document.querySelectorAll('.demo-default-option').forEach(option => {
option.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const value = this.getAttribute('data-value');
const display = document.querySelector('.demo-default-select .D3fk1I__dex');
if (display) display.textContent = value;
document.querySelectorAll('.dex-popup-layer').forEach(p => {
p.style.visibility = 'hidden';
p.style.zIndex = '-1';
});
showDemoNotification(`Mode changed to: ${value}`, 'info');
return false;
}, true);
});
document.querySelectorAll('.demo-buy-option').forEach(option => {
option.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const value = this.getAttribute('data-value');
const display = document.querySelector('.demo-buy-select .UE7t1T__dex');
if (display) display.textContent = value;
document.querySelectorAll('.dex-popup-layer').forEach(p => {
p.style.visibility = 'hidden';
p.style.zIndex = '-1';
});
showDemoNotification(`Buy with: ${value}`, 'info');
return false;
}, true);
});
document.querySelectorAll('.demo-sell-option').forEach(option => {
option.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const value = this.getAttribute('data-value');
const display = document.querySelector('.demo-sell-select .demo-sell-for-label');
if (display) display.innerHTML = 'Sell for <span class="font-500 UE7t1T__dex">SOL</span>';
document.querySelectorAll('.dex-popup-layer').forEach(p => {
p.style.visibility = 'hidden';
p.style.zIndex = '-1';
});
showDemoNotification(`Sell for: ${value}`, 'info');
return false;
}, true);
});
}, 100);
// Document click to close dropdowns
document.addEventListener('click', function(e) {
// Sadece demo dropdown'ları değilse kapat
if (!e.target.closest('.demo-default-select') &&
!e.target.closest('.demo-buy-select') &&
!e.target.closest('.demo-sell-select') &&
!e.target.closest('.dex-popup-layer')) {
document.querySelectorAll('.dex-popup-layer').forEach(popup => {
popup.style.visibility = 'hidden';
popup.style.zIndex = '-1';
});
}
}, true);
}
function setupDemoPanelEvents() {
debugLog('Setting up demo panel events...');
// Kapat butonu
const closeBtn = document.querySelector('.demo-panel-close');
if (closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
hideDemoPanel();
return false;
}, true);
}
// Reset butonu
const resetBtn = document.querySelector('.demo-reset-balance');
if (resetBtn) {
resetBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
resetDemoAccount();
return false;
}, true);
}
// Customize butonu
const customizeBtn = document.querySelector('.demo-customize-btn');
if (customizeBtn) {
debugLog('Customize button found, setting up click event...');
const newCustomizeBtn = customizeBtn.cloneNode(true);
customizeBtn.parentNode.replaceChild(newCustomizeBtn, customizeBtn);
newCustomizeBtn.addEventListener('click', function(e) {
debugLog('✅ CUSTOMIZE BUTTON CLICKED! Mode:', customizeMode);
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
toggleCustomizeMode();
return false;
}, true);
}
// Hotkeys butonu
const hotkeysBtn = document.querySelector('.demo-hotkeys-btn');
if (hotkeysBtn) {
const newHotkeysBtn = hotkeysBtn.cloneNode(true);
hotkeysBtn.parentNode.replaceChild(newHotkeysBtn, hotkeysBtn);
newHotkeysBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
hotkeysEnabled = !hotkeysEnabled;
hotkeysSpacePressed = false;
if (hotkeysEnabled) {
showDemoNotification('Hotkeys enabled. Hold SPACE to show keycaps, then use QWER / ASDF.', 'info');
} else {
showDemoNotification('Hotkeys disabled.', 'info');
}
requestPanelRebuildPreservingPosition();
return false;
}, true);
}
// PnL butonu - YENİ EKLENDİ
const pnlBtn = document.querySelector('.demo-pnl-btn');
if (pnlBtn) {
pnlBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
debugLog('✅ PNL BUTTON CLICKED! Current state:', pnlSectionVisible);
pnlSectionVisible = !pnlSectionVisible;
// Panel'i yeniden oluÅŸtur
if (demoPanelVisible) {
requestPanelRebuildPreservingPosition();
}
return false;
}, true);
}
// Currency toggle butonu - GÜNCELLENDİ
const currencyToggle = document.querySelector('.demo-currency-toggle');
if (currencyToggle) {
// Mevcut event listener'ları temizle
const newCurrencyToggle = currencyToggle.cloneNode(true);
currencyToggle.parentNode.replaceChild(newCurrencyToggle, currencyToggle);
newCurrencyToggle.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
debugLog('💰 Currency toggle clicked. Current mode:', showFiat);
showFiat = !showFiat;
// Butonun görünürlüğünü koru ve güncelle
this.style.display = 'flex';
this.style.alignItems = 'center';
this.style.justifyContent = 'center';
updatePnLDisplay(true);
return false;
}, true);
}
// Buy settings butonu (prompt kaldırıldı, slippage sadece customize mode'dan düzenlenir)
const buySettings = document.querySelector('.demo-buy-settings');
if (buySettings) {
buySettings.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
return false;
}, true);
}
// Sell settings butonu (prompt kaldırıldı, slippage sadece customize mode'dan düzenlenir)
const sellSettings = document.querySelector('.demo-sell-settings');
if (sellSettings) {
sellSettings.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
return false;
}, true);
}
// Hızlı alım butonları (sadece customize mode kapalıyken)
if (!customizeMode) {
const buyButtons = document.querySelectorAll('.demo-buy-quick');
buyButtons.forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const amount = parseFloat(this.getAttribute('data-amount')) || 0.03;
executeDemoBuy(amount);
return false;
}, true);
});
}
// Hızlı satış butonları (sadece customize mode kapalıyken)
if (!customizeMode) {
const sellButtons = document.querySelectorAll('.demo-sell-quick');
sellButtons.forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const percent = parseInt(this.getAttribute('data-percent')) || 50;
executeDemoSell(percent);
return false;
}, true);
});
}
// Input event'leri (sadece customize mode açıkken)
if (customizeMode) {
// Buy amount input'ları
const buyInputs = document.querySelectorAll('.demo-buy-input');
buyInputs.forEach((input, index) => {
input.addEventListener('input', function() {
tempBuyAmounts[index] = this.value;
debugLog(`Buy amount ${index + 1} updated: ${this.value}`);
});
input.addEventListener('blur', function() {
tempBuyAmounts[index] = this.value;
debugLog(`Buy amount ${index + 1} saved: ${this.value}`);
});
});
// Sell percent input'ları
const sellInputs = document.querySelectorAll('.demo-sell-input');
sellInputs.forEach((input, index) => {
input.addEventListener('input', function() {
tempSellPercents[index] = this.value;
debugLog(`Sell percent ${index + 1} updated: ${this.value}`);
});
input.addEventListener('blur', function() {
tempSellPercents[index] = this.value;
debugLog(`Sell percent ${index + 1} saved: ${this.value}`);
});
});
// Buy slippage input event'i
const buySlippageInput = document.querySelector('.demo-buy-slippage-input');
if (buySlippageInput) {
buySlippageInput.addEventListener('input', function() {
tempBuySlippage = this.value;
debugLog(`Buy slippage updated: ${this.value}`);
});
buySlippageInput.addEventListener('blur', function() {
tempBuySlippage = normalizeSlippageText(this.value, currentSettings.buySlippage || '15%');
this.value = tempBuySlippage;
debugLog(`Buy slippage saved: ${this.value}`);
});
}
// Sell slippage input event'i
const sellSlippageInput = document.querySelector('.demo-sell-slippage-input');
if (sellSlippageInput) {
sellSlippageInput.addEventListener('input', function() {
tempSellSlippage = this.value;
debugLog(`Sell slippage updated: ${this.value}`);
});
sellSlippageInput.addEventListener('blur', function() {
tempSellSlippage = normalizeSlippageText(this.value, currentSettings.sellSlippage || '20%');
this.value = tempSellSlippage;
debugLog(`Sell slippage saved: ${this.value}`);
});
}
// Buy fixed fee input event'i
const buyFixedFeeInput = document.querySelector('.demo-buy-fixed-fee-input');
if (buyFixedFeeInput) {
buyFixedFeeInput.addEventListener('input', function() {
tempBuyFixedFeeSol = this.value;
debugLog(`Buy fixed fee updated: ${this.value}`);
});
buyFixedFeeInput.addEventListener('blur', function() {
tempBuyFixedFeeSol = normalizeFixedFeeSolText(this.value, currentSettings.buyFixedFeeSol || '0.00005');
this.value = tempBuyFixedFeeSol;
debugLog(`Buy fixed fee saved: ${this.value}`);
});
}
// Sell fixed fee input event'i
const sellFixedFeeInput = document.querySelector('.demo-sell-fixed-fee-input');
if (sellFixedFeeInput) {
sellFixedFeeInput.addEventListener('input', function() {
tempSellFixedFeeSol = this.value;
debugLog(`Sell fixed fee updated: ${this.value}`);
});
sellFixedFeeInput.addEventListener('blur', function() {
tempSellFixedFeeSol = normalizeFixedFeeSolText(this.value, currentSettings.sellFixedFeeSol || '0.00005');
this.value = tempSellFixedFeeSol;
debugLog(`Sell fixed fee saved: ${this.value}`);
});
}
// Service fee input event'i
const serviceFeeInput = document.querySelector('.demo-service-fee-input');
if (serviceFeeInput) {
serviceFeeInput.addEventListener('input', function() {
tempServiceFee = this.value;
debugLog(`Service fee updated: ${this.value}`);
});
serviceFeeInput.addEventListener('blur', function() {
tempServiceFee = this.value;
debugLog(`Service fee saved: ${this.value}`);
});
}
// SOL balance input event'i
const solBalanceInput = document.querySelector('.demo-sol-balance-input');
if (solBalanceInput) {
solBalanceInput.addEventListener('input', function() {
const newValue = parseFloat(this.value);
if (!isNaN(newValue) && newValue >= 0) {
demoPortfolio.SOL.amount = newValue;
debugLog(`SOL balance updated: ${this.value}`);
}
});
solBalanceInput.addEventListener('blur', function() {
const newValue = parseFloat(this.value);
if (!isNaN(newValue) && newValue >= 0) {
demoPortfolio.SOL.amount = newValue;
savePortfolioToStorage();
debugLog(`SOL balance saved: ${this.value}`);
}
});
}
// Token balance input event'i
const tokenBalanceInput = document.querySelector('.demo-token-balance-input');
if (tokenBalanceInput) {
tokenBalanceInput.addEventListener('input', function() {
const newValue = parseFloat(this.value);
if (!isNaN(newValue) && newValue >= 0) {
const tokenPortfolio = getCurrentTokenPortfolio();
tokenPortfolio.amount = newValue;
debugLog(`Token balance updated: ${this.value}`);
}
});
tokenBalanceInput.addEventListener('blur', function() {
const newValue = parseFloat(this.value);
if (!isNaN(newValue) && newValue >= 0) {
const tokenPortfolio = getCurrentTokenPortfolio();
tokenPortfolio.amount = newValue;
savePortfolioToStorage();
debugLog(`Token balance saved: ${this.value}`);
}
});
}
}
}
function toggleCustomizeMode() {
debugLog('Toggling customize mode. Current mode:', customizeMode);
const wasCustomizeMode = customizeMode;
customizeMode = !customizeMode;
customizeExitAnimationPending = wasCustomizeMode && !customizeMode;
if (customizeMode) {
// Geçici değerleri mevcut ayarlarla doldur
tempBuyAmounts = [...currentSettings.buyAmounts];
tempSellPercents = [...currentSettings.sellPercents];
tempBuySlippage = currentSettings.buySlippage;
tempSellSlippage = currentSettings.sellSlippage;
tempBuyFixedFeeSol = currentSettings.buyFixedFeeSol;
tempSellFixedFeeSol = currentSettings.sellFixedFeeSol;
tempServiceFee = currentSettings.serviceFee;
showDemoNotification('Customize mode activated - Edit buy/sell amounts, slippage, fixed fees and service fee', 'info');
} else {
// DeÄŸiÅŸiklikleri kaydet
currentSettings.buyAmounts = [...tempBuyAmounts];
currentSettings.sellPercents = [...tempSellPercents];
currentSettings.buySlippage = normalizeSlippageText(tempBuySlippage, currentSettings.buySlippage || '15%');
currentSettings.sellSlippage = normalizeSlippageText(tempSellSlippage, currentSettings.sellSlippage || '20%');
currentSettings.customBuySlippage = true;
currentSettings.customSellSlippage = true;
currentSettings.customBuyAmounts = true;
currentSettings.customSellPercents = true;
currentSettings.buyFixedFeeSol = normalizeFixedFeeSolText(tempBuyFixedFeeSol, currentSettings.buyFixedFeeSol || '0.00005');
currentSettings.sellFixedFeeSol = normalizeFixedFeeSolText(tempSellFixedFeeSol, currentSettings.sellFixedFeeSol || '0.00005');
currentSettings.serviceFee = tempServiceFee;
// Ayarları localStorage'a kaydet
saveSettingsToStorage();
savePortfolioToStorage();
showDemoNotification('Custom amounts, slippage, fixed fees and service fee saved successfully!', 'success');
}
// PANELİ YENİDEN OLUŞTUR
if (demoPanelVisible) {
requestPanelRebuildPreservingPosition();
debugLog('Panel rebuilt with customize mode:', customizeMode);
}
}
function resetDemoAccount() {
if (confirm('Are you sure you want to reset your demo account? All portfolio data will be lost.')) {
let resetSolAmount = DEBUG_RESET_SOL_FALLBACK_AMOUNT;
if (DEBUG_RESET_WITH_USD_TARGET_ENABLED) {
const liveSolPrice = getLiveSolPriceFromDOM();
if (liveSolPrice && liveSolPrice > 0) {
currentSolPrice = liveSolPrice;
}
const usdTargetAmount = Number(DEBUG_RESET_USD_TARGET_AMOUNT);
const solPriceForReset = Number(currentSolPrice);
if (Number.isFinite(usdTargetAmount) && usdTargetAmount > 0 && Number.isFinite(solPriceForReset) && solPriceForReset > 0) {
resetSolAmount = usdTargetAmount / solPriceForReset;
} else {
debugLog('âš ï¸ Reset USD target aktif ama SOL fiyatı geçersiz, fallback SOL amount kullanılacak.', {
usdTargetAmount,
solPriceForReset,
fallback: DEBUG_RESET_SOL_FALLBACK_AMOUNT
});
}
}
if (!Number.isFinite(resetSolAmount) || resetSolAmount <= 0) {
resetSolAmount = DEBUG_RESET_SOL_FALLBACK_AMOUNT;
}
demoBalance = Number.isFinite(currentSolPrice) && currentSolPrice > 0
? resetSolAmount * currentSolPrice
: 1000;
// Sadece SOL'u koru, diğer tüm tokenları temizle
demoPortfolio = {
'SOL': {
amount: resetSolAmount,
avgPrice: Number.isFinite(currentSolPrice) && currentSolPrice > 0 ? currentSolPrice : 100,
symbol: 'SOL',
image: 'https://web3.okx.com/cdn/web3/currency/token/501-11111111111111111111111111111111-1.png/type=default_350_0?v=1734571825920',
totalInvested: 0,
totalSold: 0,
totalInvestedSol: 0,
totalSoldSol: 0,
realizedPnL: 0
}
};
totalPnL = 0;
totalPnLPercent = 0;
tokenPnL = 0;
tokenPnLPercent = 0;
activeTokenRealizedPnL = 0;
totalRealizedPnL = 0;
totalTradeVolume = 0;
tradeHistory = [];
boughtAmount = 0;
soldAmount = 0;
boughtAmountSol = 0;
soldAmountSol = 0;
balanceAmount = 0;
tpnlAmount = 0;
// Ayarlar/buton/fee değerlerine dokunma: sadece portföy + PnL reset
lastSettingsSyncAt = 0;
// localStorage'ı da temizle
savePortfolioToStorage();
// PnL state'ini reset sonrasında zorunlu olarak yeniden hesapla
calculatePnL();
if (demoPanelVisible) {
requestPanelRebuildPreservingPosition();
}
updateDemoPanelValues(true);
updatePnLDisplay(true);
const resetNotificationText = DEBUG_RESET_WITH_USD_TARGET_ENABLED
? `Demo account reset! SOL: ${resetSolAmount.toFixed(6)} (~$${Number(DEBUG_RESET_USD_TARGET_AMOUNT).toFixed(2)})`
: `Demo account reset! SOL: ${resetSolAmount.toFixed(6)}`;
showDemoNotification(resetNotificationText, 'success');
}
}
// Rastgele SOL fee hesaplama fonksiyonu
function calculateRandomSolFee() {
const min = 0.0004;
const max = 0.0009;
return (Math.random() * (max - min) + min).toFixed(6);
}
// Service fee hesaplama fonksiyonu
function calculateServiceFee(amount, feePercentage) {
const percentage = parseFloat(feePercentage.replace('%', '')) / 100;
return amount * percentage;
}
function clampNumber(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function resolveDelayFeeSol(rawFixedFeeSol) {
const numericFee = Number(rawFixedFeeSol);
if (!Number.isFinite(numericFee)) {
return TRADE_FEE_MIN_SOL;
}
return clampNumber(numericFee, TRADE_FEE_MIN_SOL, TRADE_SPEED_FEE_MAX_SOL);
}
function calculateTradeDelaySecondsFromFee(paidFeeSol) {
const delayFeeSol = resolveDelayFeeSol(paidFeeSol);
const feeRange = Math.max(TRADE_SPEED_FEE_MAX_SOL - TRADE_FEE_MIN_SOL, Number.EPSILON);
const normalizedFee = (delayFeeSol - TRADE_FEE_MIN_SOL) / feeRange;
const clampedNormalizedFee = clampNumber(normalizedFee, 0, 1);
// 0.000001 fee => 0.600s, 0.0001 fee => 0.350s (tam lineer oran)
const delaySeconds = TRADE_DELAY_MAX_SECONDS - (clampedNormalizedFee * (TRADE_DELAY_MAX_SECONDS - TRADE_DELAY_MIN_SECONDS));
return clampNumber(delaySeconds, TRADE_DELAY_MIN_SECONDS, TRADE_DELAY_MAX_SECONDS);
}
async function executeDemoBuy(amount) {
const tokenPortfolio = getCurrentTokenPortfolio();
// SOL fiyatını işlem anında canlı DOM'dan tazele
const liveSolPrice = getLiveSolPriceFromDOM();
if (liveSolPrice && liveSolPrice > 0) {
currentSolPrice = liveSolPrice;
}
if (!currentSolPrice || currentSolPrice <= 0 || isNaN(currentSolPrice)) {
showDemoNotification('Cannot execute buy - SOL price not available yet!', 'error');
return;
}
// Fiyat kontrolü (işlem öncesi bir kez daha tazele)
if (!currentTokenPrice || currentTokenPrice <= 0 || isNaN(currentTokenPrice)) {
getCurrentPrices();
}
if (!currentTokenPrice || currentTokenPrice <= 0 || isNaN(currentTokenPrice)) {
const emergencyPrice = getLiveTokenPriceFromDOM();
if (emergencyPrice && emergencyPrice > 0) {
currentTokenPrice = emergencyPrice;
}
}
if (!currentTokenPrice || currentTokenPrice <= 0 || isNaN(currentTokenPrice)) {
showDemoNotification('Cannot execute buy - token price not available!', 'error');
return;
}
if (!demoMode) {
showDemoNotification('Please activate demo mode first!', 'error');
return;
}
const slippagePercent = parseSlippagePercent(currentSettings.buySlippage, 0);
// Demo modda slippage bir tolerans değeridir; gerçekleşen fiyatı otomatik olarak kötüleştirmiyoruz.
const executionTokenPrice = currentTokenPrice;
const solCost = amount;
if (solCost > demoPortfolio.SOL.amount) {
showDemoNotification(`Insufficient SOL! Need ${solCost.toFixed(4)} SOL but only have ${demoPortfolio.SOL.amount.toFixed(4)} SOL`, 'error');
return;
}
const orderTokenCA = getTokenCAFromURL();
const orderTokenSymbol = currentTokenSymbol;
debugLog('🛒 Executing BUY for:', orderTokenSymbol, 'CA:', orderTokenCA);
// Fee hesaplamaları
const serviceFeeAmount = calculateServiceFee(solCost, currentSettings.serviceFee);
const fixedFeeSol = parseFixedFeeSol(currentSettings.buyFixedFeeSol, 0.00005);
const totalFees = serviceFeeAmount + fixedFeeSol;
const totalSolCost = solCost + totalFees;
if (totalSolCost > demoPortfolio.SOL.amount) {
showDemoNotification(`Insufficient SOL for fees! Need ${totalSolCost.toFixed(6)} SOL`, 'error');
return;
}
// Gecikme hesabında service fee dahil edilmez; yalnızca buy fixed fee kullanılır.
const delayFeeSol = resolveDelayFeeSol(fixedFeeSol);
const delaySeconds = calculateTradeDelaySecondsFromFee(delayFeeSol);
const delayMs = delaySeconds * 1000;
showDemoNotification(`Buy processing... ${delaySeconds.toFixed(4)}s (${delayMs.toFixed(1)}ms) | fee: ${delayFeeSol.toFixed(6)} SOL`, 'info');
await wait(delayMs);
if (totalSolCost > demoPortfolio.SOL.amount) {
showDemoNotification('Buy aborted: SOL balance changed during delay.', 'error');
return;
}
// Token miktarı hesaplama (gerçekleşen fiyat)
const tokenAmount = (solCost * currentSolPrice) / executionTokenPrice;
const usdValue = solCost * currentSolPrice;
const totalFeesUsd = totalFees * currentSolPrice;
debugLog('💰 Buy calculation:', {
tokenAmount: tokenAmount,
tokenPrice: currentTokenPrice,
executionTokenPrice,
slippagePercent,
solCost: solCost,
usdValue: usdValue,
serviceFeeAmount,
fixedFeeSol,
totalFees,
totalFeesUsd
});
// Weighted average price hesaplama (alış fee'si maliyete dahil)
const previousAmount = tokenPortfolio.amount;
const previousAvgPrice = tokenPortfolio.avgPrice;
const previousTotalValue = previousAmount * previousAvgPrice;
const newTotalValue = previousTotalValue + (tokenAmount * executionTokenPrice) + totalFeesUsd;
const totalTokenAmount = previousAmount + tokenAmount;
// Yeni ortalama fiyat
tokenPortfolio.avgPrice = totalTokenAmount > 0 ? newTotalValue / totalTokenAmount : executionTokenPrice;
tokenPortfolio.amount = totalTokenAmount;
tokenPortfolio.totalInvested += (usdValue + totalFeesUsd);
tokenPortfolio.totalInvestedSol += totalSolCost;
// SOL bakiyesini güncelle
demoPortfolio.SOL.amount -= totalSolCost;
// Trade history
tradeHistory.push({
type: 'BUY',
tokenSymbol: orderTokenSymbol,
tokenCA: orderTokenCA,
tokenAmount: tokenAmount,
tokenPrice: executionTokenPrice,
solAmount: solCost,
usdValue: usdValue,
slippagePercent,
serviceFeeAmount,
fixedFeeSol,
totalFees,
executionDelaySec: delaySeconds,
timestamp: new Date()
});
totalTradeVolume += usdValue;
// Local storage'a kaydet
savePortfolioToStorage();
calculatePnL();
updateDemoPanelValues(true);
updatePnLDisplay(true);
showDemoNotification(`Bought ${tokenAmount.toFixed(2)} ${orderTokenSymbol} for ${solCost.toFixed(4)} SOL | Delay: ${delaySeconds.toFixed(4)}s | Slippage Tol: ${slippagePercent.toFixed(2)}% | Fees: ${totalFees.toFixed(6)} SOL | Avg Price: ${formatTokenPriceDisplay(tokenPortfolio.avgPrice)}`, 'success');
}
async function executeDemoSell(percent) {
const tokenPortfolio = getCurrentTokenPortfolio();
// SOL fiyatını işlem anında canlı DOM'dan tazele
const liveSolPrice = getLiveSolPriceFromDOM();
if (liveSolPrice && liveSolPrice > 0) {
currentSolPrice = liveSolPrice;
}
if (!currentSolPrice || currentSolPrice <= 0 || isNaN(currentSolPrice)) {
showDemoNotification('Cannot execute sell - SOL price not available yet!', 'error');
return;
}
if (!demoMode) {
showDemoNotification('Please activate demo mode first!', 'error');
return;
}
tokenPortfolio.amount = Math.max(0, normalizeNearZero(tokenPortfolio.amount, TOKEN_AMOUNT_ZERO_EPSILON));
if (tokenPortfolio.amount <= 0) {
tokenPortfolio.amount = 0;
tokenPortfolio.avgPrice = 0;
showDemoNotification(`No ${currentTokenSymbol} to sell!`, 'error');
return;
}
const orderTokenCA = getTokenCAFromURL();
const orderTokenSymbol = currentTokenSymbol;
const slippagePercent = parseSlippagePercent(currentSettings.sellSlippage, 0);
// Demo modda slippage bir tolerans değeridir; gerçekleşen fiyatı otomatik olarak düşürmüyoruz.
const executionTokenPrice = currentTokenPrice;
const tokenAmount = tokenPortfolio.amount * (percent / 100);
// Gelir hesaplama (slippage sonrası efektif fiyat)
const solRevenue = (tokenAmount * executionTokenPrice) / currentSolPrice;
const usdValue = tokenAmount * executionTokenPrice;
// Fee hesaplamaları
const serviceFeeAmount = calculateServiceFee(solRevenue, currentSettings.serviceFee);
const fixedFeeSol = parseFixedFeeSol(currentSettings.sellFixedFeeSol, 0.00005);
const totalFees = serviceFeeAmount + fixedFeeSol;
const totalFeesUsd = totalFees * currentSolPrice;
const netSolRevenue = solRevenue - totalFees;
if (netSolRevenue < 0) {
showDemoNotification(`Fees exceed revenue! Cannot sell.`, 'error');
return;
}
// Gecikme hesabında service fee dahil edilmez; yalnızca sell fixed fee kullanılır.
const delayFeeSol = resolveDelayFeeSol(fixedFeeSol);
const delaySeconds = calculateTradeDelaySecondsFromFee(delayFeeSol);
const delayMs = delaySeconds * 1000;
showDemoNotification(`Sell processing... ${delaySeconds.toFixed(4)}s (${delayMs.toFixed(1)}ms) | fee: ${delayFeeSol.toFixed(6)} SOL`, 'info');
await wait(delayMs);
if (tokenAmount > tokenPortfolio.amount) {
showDemoNotification('Sell aborted: token balance changed during delay.', 'error');
return;
}
// PnL hesaplama (satış fee'si realize PnL'e dahil)
const costBasis = tokenAmount * tokenPortfolio.avgPrice;
const realizedPnL = (usdValue - totalFeesUsd) - costBasis;
debugLog('💰 Sell calculation:', {
tokenAmount: tokenAmount,
avgPrice: tokenPortfolio.avgPrice,
currentPrice: currentTokenPrice,
executionTokenPrice,
slippagePercent,
costBasis: costBasis,
revenue: usdValue,
realizedPnL: realizedPnL,
serviceFeeAmount,
fixedFeeSol,
totalFees,
totalFeesUsd
});
// Portföyü güncelle
tokenPortfolio.amount = Math.max(0, normalizeNearZero(tokenPortfolio.amount - tokenAmount, TOKEN_AMOUNT_ZERO_EPSILON));
tokenPortfolio.totalSold += (usdValue - totalFeesUsd);
tokenPortfolio.totalSoldSol += netSolRevenue;
// Eğer tüm tokenlar satıldıysa average price'ı sıfırla
if (isEffectivelyZero(tokenPortfolio.amount, TOKEN_AMOUNT_ZERO_EPSILON)) {
tokenPortfolio.amount = 0;
tokenPortfolio.avgPrice = 0;
}
// SOL bakiyesine net geliri ekle
demoPortfolio.SOL.amount += netSolRevenue;
// Realize edilen PnL'yi coin bazında güncelle
tokenPortfolio.realizedPnL = Number(tokenPortfolio.realizedPnL || 0) + realizedPnL;
totalRealizedPnL = calculatePortfolioRealizedPnLSum();
// Trade history
tradeHistory.push({
type: 'SELL',
tokenSymbol: orderTokenSymbol,
tokenCA: orderTokenCA,
tokenAmount: tokenAmount,
tokenPrice: executionTokenPrice,
solAmount: netSolRevenue,
usdValue: usdValue,
slippagePercent,
serviceFeeAmount,
fixedFeeSol,
totalFees,
realizedPnL: realizedPnL,
executionDelaySec: delaySeconds,
timestamp: new Date()
});
totalTradeVolume += usdValue;
// Local storage'a kaydet
savePortfolioToStorage();
calculatePnL();
updateDemoPanelValues(true);
updatePnLDisplay(true);
const pnlPercent = costBasis > 0 ? (realizedPnL / costBasis) * 100 : 0;
const pnlText = realizedPnL >= 0 ?
`Profit: +$${realizedPnL.toFixed(2)} (${pnlPercent.toFixed(2)}%)` :
`Loss: -$${Math.abs(realizedPnL).toFixed(2)} (${Math.abs(pnlPercent).toFixed(2)}%)`;
showDemoNotification(`Sold ${tokenAmount.toFixed(2)} ${orderTokenSymbol} for ${netSolRevenue.toFixed(4)} SOL | Delay: ${delaySeconds.toFixed(4)}s | Slippage Tol: ${slippagePercent.toFixed(2)}% | Fees: ${totalFees.toFixed(6)} SOL | ${pnlText}`, 'success');
}
function updateDemoPanelValues(forceUpdate = false) {
const demoPanel = document.querySelector('.demo-trade-panel');
if (!demoPanel) return;
if (isPanelDragging) {
queuePanelValueRefreshAfterDrag();
return;
}
const now = Date.now();
if (!forceUpdate && now - lastPanelUpdateAt < PANEL_UPDATE_THROTTLE_MS) return;
lastPanelUpdateAt = now;
// Panel açıkken real panel ayarları belirli aralıklarla çekilsin ki interval/slippage değişimleri canlı yansısın.
if (!customizeMode && (forceUpdate || now - lastSettingsSyncAt >= SETTINGS_SYNC_INTERVAL_MS)) {
lastSettingsSyncAt = now;
loadSettingsFromRealPanel();
}
const tokenPortfolio = getCurrentTokenPortfolio();
const detectedTokenImage = getLiveTokenImageFromDOM();
if (detectedTokenImage) {
if (detectedTokenImage !== currentTokenImage) {
currentTokenImage = detectedTokenImage;
}
if (tokenPortfolio && tokenPortfolio.image !== detectedTokenImage) {
tokenPortfolio.image = detectedTokenImage;
}
} else if (!isLikelyTokenImageUrl(currentTokenImage) && isLikelyTokenImageUrl(tokenPortfolio.image)) {
currentTokenImage = normalizeImageUrl(tokenPortfolio.image);
}
// Balance hesapla - tüm tokenları dahil et
const strictActiveTokenCA = getTokenCAFromURL({ allowLastKnown: false });
const activeTokenCA =
strictActiveTokenCA && strictActiveTokenCA !== 'default_token' && strictActiveTokenCA !== 'error_token'
? strictActiveTokenCA
: getTokenCAFromURL();
let totalBalance = demoPortfolio.SOL.amount * currentSolPrice;
Object.keys(demoPortfolio).forEach(key => {
if (key !== 'SOL') {
const token = demoPortfolio[key];
// Geçerli token fiyatını kullan (sadece aktif token için currentTokenPrice var)
const tokenPrice = (key === activeTokenCA) ? currentTokenPrice : (token.avgPrice || 0);
totalBalance += token.amount * tokenPrice;
}
});
demoBalance = totalBalance;
// Token görüntülerini güncelle
const buyTokenDisplay = demoPanel.querySelector('.instant-trade-token-selector-buy + .flex.items-center .VHD_xh__dex');
const sellTokenDisplay = demoPanel.querySelector('.instant-trade-token-selector-sell + .flex.items-center .VHD_xh__dex');
const sellForLabel = demoPanel.querySelector('.instant-trade-token-selector-sell .demo-sell-for-label');
const sellPicture = demoPanel.querySelector('.instant-trade-token-selector-sell + .flex.items-center picture.dex-picture');
const sellTokenImage = sellPicture ? sellPicture.querySelector('img') : null;
const sellTokenSource = sellPicture ? sellPicture.querySelector('source') : null;
const resolvedTokenImage = resolveTokenImageUrl(detectedTokenImage, currentTokenImage, tokenPortfolio.image, DEFAULT_TOKEN_IMAGE);
const sellDropdownTokenOption = demoPanel.querySelector('.demo-sell-select .demo-sell-option:not([data-value="SOL"])');
if (sellDropdownTokenOption && currentTokenSymbol) {
if (sellDropdownTokenOption.getAttribute('data-value') !== currentTokenSymbol) {
sellDropdownTokenOption.setAttribute('data-value', currentTokenSymbol);
}
const optionPrimaryText = sellDropdownTokenOption.querySelectorAll('.tiQWEG__dex');
const optionSecondaryText = sellDropdownTokenOption.querySelectorAll('.inAvcR__dex');
const optionPicture = sellDropdownTokenOption.querySelector('picture');
const optionImage = optionPicture ? optionPicture.querySelector('img') : null;
const optionSource = optionPicture ? optionPicture.querySelector('source') : null;
setTextIfChanged(optionPrimaryText[0], currentTokenSymbol);
setTextIfChanged(optionSecondaryText[0], currentTokenName || 'Current Token');
setTextIfChanged(optionPrimaryText[1], tokenPortfolio.amount.toFixed(4));
setTextIfChanged(optionSecondaryText[1], `$${(tokenPortfolio.amount * currentTokenPrice).toFixed(2)}`);
setImageIfChanged(optionImage, optionSource, resolvedTokenImage);
}
setTextIfChanged(buyTokenDisplay, formatBalanceDisplay(demoPortfolio.SOL.amount));
setTextIfChanged(sellTokenDisplay, formatBalanceDisplay(tokenPortfolio.amount));
if (sellForLabel && sellForLabel.innerHTML !== 'Sell for <span class="font-500 UE7t1T__dex">SOL</span>') {
sellForLabel.innerHTML = 'Sell for <span class="font-500 UE7t1T__dex">SOL</span>';
}
setImageIfChanged(sellTokenImage, sellTokenSource, resolvedTokenImage);
// Quick buy/sell buton metinlerini ve data attribute'larını canlı güncelle
const buyQuickButtons = demoPanel.querySelectorAll('.demo-buy-quick');
buyQuickButtons.forEach((button, index) => {
const nextText = String(currentSettings.buyAmounts[index] || defaultBuyAmounts[index] || '').trim();
const textEl = button.querySelector('.l5GPKh__dex');
setTextIfChanged(textEl, nextText);
const parsedAmount = parseFloat(nextText);
const fallbackAmount = parseFloat(defaultBuyAmounts[index]) || 0;
const nextAmount = Number.isFinite(parsedAmount) ? parsedAmount : fallbackAmount;
const nextAmountAttr = String(nextAmount);
if (button.getAttribute('data-amount') !== nextAmountAttr) {
button.setAttribute('data-amount', nextAmountAttr);
}
});
const sellQuickButtons = demoPanel.querySelectorAll('.demo-sell-quick');
sellQuickButtons.forEach((button, index) => {
const nextText = String(currentSettings.sellPercents[index] || defaultSellPercents[index] || '').trim();
const textEl = button.querySelector('.l5GPKh__dex');
setTextIfChanged(textEl, nextText);
const parsedPercent = parseFloat(nextText.replace('%', ''));
const fallbackPercent = parseFloat(defaultSellPercents[index].replace('%', '')) || 0;
const nextPercent = Number.isFinite(parsedPercent) ? parsedPercent : fallbackPercent;
const nextPercentAttr = String(nextPercent);
if (button.getAttribute('data-percent') !== nextPercentAttr) {
button.setAttribute('data-percent', nextPercentAttr);
}
});
setTextIfChanged(demoPanel.querySelector('.demo-buy-slippage-value'), currentSettings.buySlippage);
setTextIfChanged(demoPanel.querySelector('.demo-sell-slippage-value'), currentSettings.sellSlippage);
setTextIfChanged(demoPanel.querySelector('.demo-buy-fixed-fee-value'), currentSettings.buyFixedFeeSol);
setTextIfChanged(demoPanel.querySelector('.demo-sell-fixed-fee-value'), currentSettings.sellFixedFeeSol);
setTextIfChanged(demoPanel.querySelector('.demo-service-fee-value'), currentSettings.serviceFee);
// Dropdown menülerindeki SOL bakiyesini de canlı güncelle
const buyDropdownSolOption = demoPanel.querySelector('.demo-buy-select .demo-buy-option[data-value="SOL"]');
if (buyDropdownSolOption) {
const buyOptionPrimaryText = buyDropdownSolOption.querySelectorAll('.tiQWEG__dex');
const buyOptionSecondaryText = buyDropdownSolOption.querySelectorAll('.inAvcR__dex');
setTextIfChanged(buyOptionPrimaryText[1], demoPortfolio.SOL.amount.toFixed(4));
setTextIfChanged(buyOptionSecondaryText[1], `$${(demoPortfolio.SOL.amount * currentSolPrice).toFixed(2)}`);
}
const sellDropdownSolOption = demoPanel.querySelector('.demo-sell-select .demo-sell-option[data-value="SOL"]');
if (sellDropdownSolOption) {
const sellOptionPrimaryText = sellDropdownSolOption.querySelectorAll('.tiQWEG__dex');
const sellOptionSecondaryText = sellDropdownSolOption.querySelectorAll('.inAvcR__dex');
const tokenValueUsdNumeric = tokenPortfolio.amount * currentTokenPrice;
const tokenValueInSol = currentSolPrice > 0
? (tokenValueUsdNumeric / currentSolPrice).toFixed(4)
: '0.0000';
setTextIfChanged(sellOptionPrimaryText[1], tokenValueInSol);
setTextIfChanged(sellOptionSecondaryText[1], `$${tokenValueUsdNumeric.toFixed(2)}`);
}
// PnL değerlerini güncelle
updatePnLDisplay();
// Dropdown içeriklerini her tick yeniden kurmuyoruz (perf + log spam fix)
}
function updatePnLDisplay(forceUpdate = false) {
const demoPanel = document.querySelector('.demo-trade-panel');
if (!demoPanel) return;
if (isPanelDragging) {
queuePanelValueRefreshAfterDrag();
return;
}
const now = Date.now();
if (!forceUpdate && now - lastPnLDisplayUpdateAt < PNL_UPDATE_THROTTLE_MS) return;
lastPnLDisplayUpdateAt = now;
const boughtFiat = boughtAmount;
const soldFiat = soldAmount;
const balanceFiat = balanceAmount;
const boughtSol = boughtAmountSol;
const soldSol = soldAmountSol;
const balanceSol = currentSolPrice > 0 ? balanceAmount / currentSolPrice : 0;
const totalPnlFiat = totalPnL;
const realizedPnlFiat = activeTokenRealizedPnL;
const unrealizedPnlFiat = tokenPnL;
const totalPnlSol = currentSolPrice > 0 ? totalPnlFiat / currentSolPrice : 0;
const realizedPnlSol = currentSolPrice > 0 ? realizedPnlFiat / currentSolPrice : 0;
const unrealizedPnlSol = currentSolPrice > 0 ? unrealizedPnlFiat / currentSolPrice : 0;
const boughtElement = demoPanel.querySelector('.demo-pnl-bought');
const soldElement = demoPanel.querySelector('.demo-pnl-sold');
const balanceElement = demoPanel.querySelector('.demo-pnl-balance');
const tpnlElement = demoPanel.querySelector('.demo-pnl-tpnl');
const rpnlElement = demoPanel.querySelector('.demo-pnl-rpnl');
const upnlElement = demoPanel.querySelector('.demo-pnl-upnl');
const currencyToggleButton = demoPanel.querySelector('.demo-currency-toggle');
const boughtText = showFiat
? formatPnLSummaryAmountDisplay(boughtFiat, true)
: formatPnLSummaryAmountDisplay(boughtSol, false);
const soldText = showFiat
? formatPnLSummaryAmountDisplay(soldFiat, true)
: formatPnLSummaryAmountDisplay(soldSol, false);
const balanceText = showFiat
? formatPnLSummaryAmountDisplay(balanceFiat, true)
: formatPnLSummaryAmountDisplay(balanceSol, false);
const isNearZero = (value) => isEffectivelyZero(value);
const largeSolPnLCount = [totalPnlSol, realizedPnlSol, unrealizedPnlSol]
.filter(value => Number.isFinite(Number(value)) && Math.abs(Number(value)) > 999)
.length;
const hasZeroSolPnL = [totalPnlSol, realizedPnlSol, unrealizedPnlSol].some(isNearZero);
const hideAllSolPnLFractions = largeSolPnLCount >= 2 && hasZeroSolPnL;
const baseTpnlSolFractionDigits = Math.abs(totalPnlSol) > 99 ? 1 : (Math.abs(totalPnlSol) > 9 ? 2 : 3);
const baseRpnlSolFractionDigits = Math.abs(realizedPnlSol) > 99 ? 1 : 2;
const baseUpnlSolFractionDigits = Math.abs(unrealizedPnlSol) > 99 ? 1 : 2;
const tpnlSolFractionDigits = hideAllSolPnLFractions ? 0 : baseTpnlSolFractionDigits;
const rpnlSolFractionDigits = hideAllSolPnLFractions ? 0 : baseRpnlSolFractionDigits;
const upnlSolFractionDigits = hideAllSolPnLFractions ? 0 : baseUpnlSolFractionDigits;
const largeFiatPnLCount = [totalPnlFiat, realizedPnlFiat, unrealizedPnlFiat]
.filter(value => Number.isFinite(Number(value)) && Math.abs(Number(value)) > 999)
.length;
const hasZeroFiatPnL = [totalPnlFiat, realizedPnlFiat, unrealizedPnlFiat].some(isNearZero);
const hideAllFiatPnLFractions = largeFiatPnLCount >= 2 && hasZeroFiatPnL;
const tpnlFiatFractionDigits = hideAllFiatPnLFractions ? 0 : (Math.abs(totalPnlFiat) > 99 ? 0 : 1);
const rpnlFiatFractionDigits = hideAllFiatPnLFractions ? 0 : (Math.abs(realizedPnlFiat) > 99 ? 0 : 1);
const upnlFiatFractionDigits = hideAllFiatPnLFractions ? 0 : (Math.abs(unrealizedPnlFiat) < 100 ? 1 : 0);
const tpnlText = showFiat
? formatSignedUsdPnL(totalPnlFiat, tpnlFiatFractionDigits)
: formatPnLIntegerDisplay(totalPnlSol, false, tpnlSolFractionDigits);
const rpnlText = showFiat
? formatSignedUsdPnL(realizedPnlFiat, rpnlFiatFractionDigits)
: formatPnLIntegerDisplay(realizedPnlSol, false, rpnlSolFractionDigits);
const upnlText = showFiat
? formatSignedUsdPnL(unrealizedPnlFiat, upnlFiatFractionDigits)
: formatPnLIntegerDisplay(unrealizedPnlSol, false, upnlSolFractionDigits);
setTextIfChanged(boughtElement, boughtText);
setTextIfChanged(soldElement, soldText);
setTextIfChanged(balanceElement, balanceText);
setTextIfChanged(tpnlElement, tpnlText);
setTextIfChanged(rpnlElement, rpnlText);
setTextIfChanged(upnlElement, upnlText);
const tpnlSign = totalPnlFiat >= 0 ? 1 : -1;
const rpnlSign = realizedPnlFiat >= 0 ? 1 : -1;
const upnlSign = unrealizedPnlFiat >= 0 ? 1 : -1;
if (lastTpnlColorSign !== tpnlSign) {
setStyleIfChanged(tpnlElement, 'color', tpnlSign > 0 ? '#bcff2f' : '#fc46ab');
lastTpnlColorSign = tpnlSign;
}
if (lastRpnlColorSign !== rpnlSign) {
setStyleIfChanged(rpnlElement, 'color', rpnlSign > 0 ? '#bcff2f' : '#fc46ab');
lastRpnlColorSign = rpnlSign;
}
if (lastUpnlColorSign !== upnlSign) {
setStyleIfChanged(upnlElement, 'color', upnlSign > 0 ? '#bcff2f' : '#fc46ab');
lastUpnlColorSign = upnlSign;
}
if (currencyToggleButton) {
const nextIconMode = showFiat ? 'fiat' : 'token';
if (currencyToggleButton.getAttribute('data-icon-mode') !== nextIconMode) {
currencyToggleButton.innerHTML = getCurrencyToggleIconHTML(showFiat);
currencyToggleButton.setAttribute('data-icon-mode', nextIconMode);
}
}
}
function showDemoNotification(message, type = 'info') {
let notificationContainer = document.querySelector('.demo-notification-container');
if (!notificationContainer) {
notificationContainer = document.createElement('div');
notificationContainer.className = 'demo-notification-container dex-message-container dex-notice-stack-closed';
notificationContainer.style.cssText = `
position: fixed;
top: 72px;
left: 50%;
transform: translateX(-50%);
z-index: 10000;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
max-width: 420px;
width: calc(100vw - 24px);
pointer-events: none;
`;
document.body.appendChild(notificationContainer);
addNotificationStyles();
}
const currentMessages = notificationContainer.querySelectorAll('.dex-message-box');
if (currentMessages.length >= 2) {
const messagesToRemove = Array.from(currentMessages).slice(0, currentMessages.length - 1);
messagesToRemove.forEach((messageEl, index) => {
setTimeout(() => {
messageEl.style.animation = 'demoMessageQuickOut 0.25s ease-out forwards';
setTimeout(() => {
if (messageEl.parentNode) {
messageEl.parentNode.removeChild(messageEl);
}
}, 250);
}, index * 70);
});
}
const notificationId = 'demo-notification-' + Date.now();
let iconClass = 'dex-okds-info-circle-fill';
let boxClass = 'info';
let iconLabel = 'Info';
if (type === 'error') {
iconClass = 'dex-okds-fail-circle-fill';
boxClass = 'error';
iconLabel = 'Error';
} else if (type === 'success') {
iconClass = 'dex-okds-success-circle-fill';
boxClass = 'success';
iconLabel = 'Success';
} else if (type === 'warning') {
iconClass = 'dex-okds-warning-circle-fill';
boxClass = 'warning';
iconLabel = 'Warning';
}
const notificationHTML = `
<div class="dex-message-var dex-message-box ${boxClass} auto-width"
id="${notificationId}"
role="alert"
aria-live="polite"
style="animation: demoMessageIn 0.35s ease-out;">
<span class="dex-message-icon-circle-container">
<i class="icon iconfont dex-message-icon-new ${iconClass}"
role="img"
aria-label="${iconLabel}"></i>
</span>
<div class="dex-message-content">
<div class="dex-message-title-box">
<span class="dex-message-title">${message}</span>
</div>
</div>
</div>
`;
notificationContainer.insertAdjacentHTML('beforeend', notificationHTML);
setTimeout(() => {
const notification = document.getElementById(notificationId);
if (notification) {
notification.style.animation = 'demoMessageOut 0.3s ease-in forwards';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}
}, 3800);
}
// CSS animasyonlarını ekleyen fonksiyon
function addNotificationStyles() {
if (document.querySelector('#demo-notification-styles')) return;
const style = document.createElement('style');
style.id = 'demo-notification-styles';
style.textContent = `
@keyframes demoMessageIn {
0% {
transform: translateY(-12px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes demoMessageOut {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(-10px);
opacity: 0;
}
}
@keyframes demoMessageQuickOut {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-8px);
}
}
.demo-notification-container {
transition: all 0.2s ease;
}
.demo-notification-container .dex-message-box {
pointer-events: auto;
width: auto;
max-width: 100%;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid rgba(196, 196, 196, 0.28);
background: #383838;
color: #eef1f5;
padding: 10px 12px;
display: flex;
align-items: center;
gap: 9px;
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35);
}
.demo-notification-container .dex-message-icon-circle-container {
display: inline-flex;
align-items: center;
justify-content: center;
align-self: center;
flex-shrink: 0;
width: 19px;
min-width: 19px;
height: 19px;
margin-top: 0;
}
.demo-notification-container .dex-message-icon-new {
font-size: 19px;
line-height: 1;
}
.demo-notification-container .dex-message-box.success .dex-message-icon-new {
color: #bcff2f;
}
.demo-notification-container .dex-message-box.error .dex-message-icon-new {
color: #fc46ab;
}
.demo-notification-container .dex-message-box.warning .dex-message-icon-new {
color: #f6b73c;
}
.demo-notification-container .dex-message-box.info .dex-message-icon-new {
color: #4f9bff;
}
.demo-notification-container .dex-message-content {
min-width: 0;
flex: 1;
}
.demo-notification-container .dex-message-title-box {
display: block;
width: 100%;
}
.demo-notification-container .dex-message-title {
display: block;
font-size: 13px;
line-height: 1.45;
font-weight: 500;
color: #eef1f5;
word-break: break-word;
}
.demo-notification-container .dex-message-box.success,
.demo-notification-container .dex-message-box.error,
.demo-notification-container .dex-message-box.warning,
.demo-notification-container .dex-message-box.info {
border-color: rgba(196, 196, 196, 0.28);
background: #383838;
}
`;
document.head.appendChild(style);
}
function setupDemoMode() {
debugLog('Demo trading mode initialized');
}
// Sayfa yüklendiğinde başlat
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDemoTrading);
} else {
initDemoTrading();
}
// URL değişikliklerini dinle (SPA'lar için)
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
if (!getTokenCAFromCurrentURLStrict() && demoPanelVisible) {
hideDemoPanel();
}
debugLog('URL changed, reinitializing demo trading...');
setTimeout(initDemoTrading, 1000);
}
}).observe(document, { subtree: true, childList: true });
})();