您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
GMGN解除sol链gas最低0.0001的限制,悬浮图标常驻,点击打开/关闭面板
// ==UserScript== // @name GMGN Fee Unlock // @namespace https://greasyfork.org/zh-CN/scripts/547048-gmgn-fee-unlock // @version 1.0.2 // @description GMGN解除sol链gas最低0.0001的限制,悬浮图标常驻,点击打开/关闭面板 // @match https://gmgn.ai/* // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'GMGN_FEE_SETTINGS'; const DRAG_TAB_KEY = 'GMGN_FEE_TAB_POS'; const DRAG_PANEL_KEY = 'GMGN_FEE_PANEL_POS'; const defaultState = { enabled: true, feePriority: '0.00000001', feeBribe: '0.00000001', hidden: false, dragLocked: true }; const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); const state = { ...defaultState, ...stored }; const log = (...a) => console.log('%c[GMGN-FEE]', 'color:#7ad39a;font-weight:700', ...a); function saveState() { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } function normalizeText(s) { return (s || '').replace(/\s+/g, ' ').toLowerCase(); } // —— 新增:判断是否是我们自己面板的控件 —— // function isOurPanelInput(input) { if (!input) return false; if (input.id === 'fee-priority' || input.id === 'fee-bribe') return true; if (input.closest && input.closest('#gmgn-fee-panel')) return true; if (input.closest && input.closest('#fee-tab')) return true; return false; } function detectFeeType(input) { // 根据附近文本区分优先费 / 贿赂费(只用于页面上的真实输入框) let cur = input; for (let i = 0; i < 6 && cur; i++, cur = cur.parentElement) { const txt = normalizeText(cur.textContent || ''); if (!txt) continue; if (txt.includes('priority') || txt.includes('优先')) return 'priority'; if (txt.includes('bribe') || txt.includes('贿赂')) return 'bribe'; } return null; } function getWantFor(input) { const type = detectFeeType(input); if (type === 'priority') return state.feePriority; if (type === 'bribe') return state.feeBribe; return state.feePriority; // 默认走优先费 } const patched = new WeakSet(); function patchFeeInput(input) { if (!input || patched.has(input)) return; // —— 关键修复:忽略我们自己的面板输入框 —— // if (isOurPanelInput(input)) return; const type = detectFeeType(input); if (!type) return; try { input.removeAttribute('min'); input.setAttribute('step', 'any'); input.setAttribute('inputmode', 'decimal'); input.setAttribute('pattern', '[0-9]*(\\.[0-9]+)?'); input.setAttribute('aria-valuemin', '0'); input.closest('.chakra-numberinput')?.setAttribute('data-tm-fee', '1'); function enforce() { if (!state.enabled) return; try { const want = getWantFor(input); const now = input.value || input.getAttribute('aria-valuenow') || ''; const nowN = parseFloat(String(now || '0')); const wantN = parseFloat(String(want)); if (isNaN(nowN) || nowN === 0 || (nowN >= 0.0001 && wantN < 0.0001) || nowN !== wantN) { const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); if (desc && desc.set) { try { desc.set.call(input, want); } catch (e) {} } else input.value = want; input.setAttribute('aria-valuenow', want); input.setAttribute('aria-valuetext', want); input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); const wrap = input.closest('.chakra-numberinput'); if (wrap) wrap.setAttribute('value', want); log(`enforce ${type} →`, want); } } catch (e) {} } const handler = () => { if (!state.enabled) return setTimeout(enforce, 8); }; input.addEventListener('input', handler, true); input.addEventListener('change', handler, true); input.addEventListener('blur', handler, true); const attrObs = new MutationObserver(() => { if (state.enabled) setTimeout(enforce, 6); }); attrObs.observe(input, { attributes: true, attributeFilter: ['aria-valuemin', 'value', 'aria-valuenow', 'aria-valuetext'] }); enforce(); patched.add(input); input.__feeFix__attrObserver = attrObs; } catch (e) { console.warn('[GMGN-FEE] patch error', e); } } function scanAndPatch(root = document) { try { const inputs = Array.from((root || document).querySelectorAll('input')); for (const inp of inputs) { if (isOurPanelInput(inp)) continue; // —— 关键修复:扫描阶段也忽略面板输入 —— // patchFeeInput(inp); } } catch (e) { console.warn('[GMGN-FEE] scan error', e); } } function autoApply(root = document) { scanAndPatch(root); } const mo = new MutationObserver(muts => { if (!state.enabled) return; for (const m of muts) { if (m.type === 'childList' && m.addedNodes.length) { for (const n of m.addedNodes) if (n.nodeType === 1) autoApply(n); } else if (m.type === 'attributes' && m.target instanceof HTMLInputElement) { if (isOurPanelInput(m.target)) continue; // —— 关键修复:忽略面板输入 —— // patchFeeInput(m.target); } } }); function isInteractiveElement(el) { if (!el) return false; const tag = el.tagName && el.tagName.toLowerCase(); if (!tag) return false; if (['input', 'button', 'select', 'textarea', 'label', 'a'].includes(tag)) return true; if (el.closest && el.closest('input,button,select,textarea,a,label')) return true; return false; } function mountPanel() { if (document.getElementById('gmgn-fee-panel')) return; const wrap = document.createElement('div'); wrap.id = 'gmgn-fee-panel'; Object.assign(wrap.style, { position: 'fixed', right: '14px', bottom: '80px', zIndex: 99999999, background: '#0f1115', color: '#e6f3ea', padding: '10px', borderRadius: '10px', border: '1px solid rgba(255,255,255,0.04)', fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Arial', fontSize: '13px', width: '15%', minWidth: '165px', maxWidth: '240px', boxShadow: '0 6px 18px rgba(0,0,0,.4)', display: state.hidden ? 'none' : 'block', transform: 'none' }); wrap.innerHTML = ` <div style="font-weight:700;margin-bottom:8px;">Fee Unlock</div> <div style="display:flex;align-items:center;gap:16px;margin-bottom:8px;"> <label style="display:flex;align-items:center;gap:6px;cursor:pointer;"> <input id="fee-enabled" type="checkbox" ${state.enabled ? 'checked' : ''}> <span style="font-size:12px">自动保持(防回弹)</span> </label> <label style="display:flex;align-items:center;gap:6px;cursor:pointer;"> <input id="drag-locked" type="checkbox" ${state.dragLocked ? 'checked' : ''}> <span style="font-size:12px">拖动锁定</span> </label> </div> <div style="display:grid;grid-template-columns:64px 1fr;gap:6px;align-items:center;margin-bottom:8px;"> <div style="font-size:12px">优先费</div> <input id="fee-priority" value="${state.feePriority}" style="padding:6px;border-radius:6px;background:#151821;border:1px solid #333;color:#e6f3ea;width:100%;box-sizing:border-box;"> <div style="font-size:12px">贿赂费</div> <input id="fee-bribe" value="${state.feeBribe}" style="padding:6px;border-radius:6px;background:#151821;border:1px solid #333;color:#e6f3ea;width:100%;box-sizing:border-box;"> </div> <div style="display:flex;gap:8px;"> <button id="fee-apply" style="flex:1;padding:8px;border-radius:8px;background:#81D69D;color:white;border:none;cursor:pointer">立即应用</button> <button id="fee-hide" style="padding:8px;border-radius:8px;background:#242630;color:#e6f3ea;border:none;cursor:pointer">隐藏</button> </div> `; // 悬浮 tab const tab = document.createElement('div'); tab.id = 'fee-tab'; tab.textContent = 'Fee Unlock'; Object.assign(tab.style, { position: 'fixed', zIndex: 99999999, background: '#21302D', color: '#81D69D', padding: '3.25px 5px', borderRadius: '6px', fontSize: '12px', cursor: 'pointer', display: 'block' }); const savedTabPos = JSON.parse(localStorage.getItem(DRAG_TAB_KEY) || '{}'); if (savedTabPos.right) tab.style.right = savedTabPos.right; else tab.style.right = '20px'; if (savedTabPos.bottom) tab.style.bottom = savedTabPos.bottom; else tab.style.bottom = '20px'; // load saved panel ratio position const savedPanelPos = JSON.parse(localStorage.getItem(DRAG_PANEL_KEY) || '{}'); if (savedPanelPos.ratioLeft && savedPanelPos.ratioTop) { wrap.dataset.ratioLeft = savedPanelPos.ratioLeft; wrap.dataset.ratioTop = savedPanelPos.ratioTop; wrap.style.left = (window.innerWidth * parseFloat(wrap.dataset.ratioLeft)) + 'px'; wrap.style.top = (window.innerHeight * parseFloat(wrap.dataset.ratioTop)) + 'px'; wrap.style.right = 'auto'; wrap.style.bottom = 'auto'; } else { wrap.style.right = '14px'; wrap.style.bottom = '80px'; wrap.style.left = ''; wrap.style.top = ''; } document.body.appendChild(wrap); document.body.appendChild(tab); const elEnabled = wrap.querySelector('#fee-enabled'); const elPriority = wrap.querySelector('#fee-priority'); const elBribe = wrap.querySelector('#fee-bribe'); const elApply = wrap.querySelector('#fee-apply'); const elHide = wrap.querySelector('#fee-hide'); const elDragLocked = wrap.querySelector('#drag-locked'); // small CSS for checkbox visuals const style = document.createElement('style'); style.innerHTML = `input[type=checkbox] { accent-color: #ffffff; width: 14px; height: 14px; }`; document.head.appendChild(style); elEnabled.addEventListener('change', e => { state.enabled = !!e.target.checked; saveState(); if (state.enabled) autoApply(); }); // 单独保存,不相互覆盖 elPriority.addEventListener('input', e => { state.feePriority = e.target.value; saveState(); }); elBribe.addEventListener('input', e => { state.feeBribe = e.target.value; saveState(); }); elApply.addEventListener('click', () => autoApply()); elHide.addEventListener('click', () => { wrap.style.display = 'none'; tab.style.display = 'block'; state.hidden = true; saveState(); }); tab.addEventListener('click', (ev) => { // If tab was just dragged, tabMoved logic will prevent toggle (implemented below) if (tab._moved) { tab._moved = false; ev.stopImmediatePropagation(); return; } wrap.style.display = wrap.style.display === 'none' ? 'block' : 'none'; state.hidden = wrap.style.display === 'none'; saveState(); }); elDragLocked.addEventListener('change', e => { state.dragLocked = !!e.target.checked; saveState(); }); // ---------- Dragging logic ---------- let dragPanel = false, oxPanel = 0, oyPanel = 0; let panelMoved = false; let startX = 0, startY = 0; function isInteractiveElement(el) { if (!el) return false; const tag = el.tagName && el.tagName.toLowerCase(); if (!tag) return false; if (['input', 'button', 'select', 'textarea', 'label', 'a'].includes(tag)) return true; if (el.closest && el.closest('input,button,select,textarea,a,label')) return true; return false; } wrap.addEventListener('mousedown', (ev) => { if (state.dragLocked) return; if (isInteractiveElement(ev.target)) return; const rect = wrap.getBoundingClientRect(); if (!wrap.style.left || wrap.style.left === '') { wrap.style.left = rect.left + 'px'; } if (!wrap.style.top || wrap.style.top === '') { wrap.style.top = rect.top + 'px'; } wrap.style.right = 'auto'; wrap.style.bottom = 'auto'; dragPanel = true; oxPanel = ev.clientX - parseFloat(wrap.style.left || rect.left); oyPanel = ev.clientY - parseFloat(wrap.style.top || rect.top); panelMoved = false; startX = ev.clientX; startY = ev.clientY; wrap.style.cursor = 'grabbing'; ev.preventDefault(); }); function onDocumentMouseMoveForPanel(ev) { if (!dragPanel) return; const newLeft = ev.clientX - oxPanel; const newTop = ev.clientY - oyPanel; const clampedLeft = Math.max(0, Math.min(window.innerWidth - wrap.offsetWidth, newLeft)); const clampedTop = Math.max(0, Math.min(window.innerHeight - wrap.offsetHeight, newTop)); wrap.style.left = clampedLeft + 'px'; wrap.style.top = clampedTop + 'px'; wrap.style.right = 'auto'; wrap.style.bottom = 'auto'; if (!panelMoved && (Math.abs(ev.clientX - startX) > 3 || Math.abs(ev.clientY - startY) > 3)) panelMoved = true; } function onDocumentMouseUpForPanel() { if (!dragPanel) return; dragPanel = false; wrap.style.cursor = 'default'; const rect = wrap.getBoundingClientRect(); wrap.dataset.ratioLeft = (rect.left / window.innerWidth).toString(); wrap.dataset.ratioTop = (rect.top / window.innerHeight).toString(); localStorage.setItem(DRAG_PANEL_KEY, JSON.stringify({ ratioLeft: wrap.dataset.ratioLeft, ratioTop: wrap.dataset.ratioTop })); } document.addEventListener('mousemove', onDocumentMouseMoveForPanel); document.addEventListener('mouseup', onDocumentMouseUpForPanel); wrap.addEventListener('click', (ev) => { if (panelMoved) { ev.stopImmediatePropagation(); ev.preventDefault(); panelMoved = false; } }, true); // ---------- Tab dragging ---------- let dragTab = false, oxTab = 0, oyTab = 0, tabMoved = false; tab.addEventListener('mousedown', (ev) => { if (state.dragLocked) return; dragTab = true; oxTab = ev.clientX - (tab.getBoundingClientRect().left); oyTab = ev.clientY - (tab.getBoundingClientRect().top); tab.style.cursor = 'grabbing'; tabMoved = false; tab._startX = ev.clientX; tab._startY = ev.clientY; ev.preventDefault(); }); function onDocumentMouseMoveForTab(ev) { if (!dragTab) return; const newLeft = ev.clientX - oxTab; const newTop = ev.clientY - oyTab; const right = Math.max(0, Math.min(window.innerWidth - tab.offsetWidth, window.innerWidth - (newLeft + tab.offsetWidth))); const bottom = Math.max(0, Math.min(window.innerHeight - tab.offsetHeight, window.innerHeight - (newTop + tab.offsetHeight))); tab.style.right = right + 'px'; tab.style.bottom = bottom + 'px'; tab.style.left = 'auto'; tab.style.top = 'auto'; if (!tabMoved && (Math.abs(ev.clientX - tab._startX) > 3 || Math.abs(ev.clientY - tab._startY) > 3)) tabMoved = true; if (tabMoved) tab._moved = true; } function onDocumentMouseUpForTab() { if (!dragTab) return; dragTab = false; tab.style.cursor = 'pointer'; localStorage.setItem(DRAG_TAB_KEY, JSON.stringify({ right: tab.style.right, bottom: tab.style.bottom })); setTimeout(() => { tab._moved = false; }, 300); } document.addEventListener('mousemove', onDocumentMouseMoveForTab); document.addEventListener('mouseup', onDocumentMouseUpForTab); tab.addEventListener('click', function (ev) { if (tabMoved || this._moved) { ev.stopImmediatePropagation(); ev.preventDefault(); tabMoved = false; this._moved = false; } }, true); function initPanelPosition() { const p = JSON.parse(localStorage.getItem(DRAG_PANEL_KEY) || '{}'); if (p.ratioLeft && p.ratioTop) { const left = Math.round(window.innerWidth * parseFloat(p.ratioLeft)); const top = Math.round(window.innerHeight * parseFloat(p.ratioTop)); wrap.style.left = left + 'px'; wrap.style.top = top + 'px'; wrap.style.right = 'auto'; wrap.style.bottom = 'auto'; wrap.dataset.ratioLeft = p.ratioLeft; wrap.dataset.ratioTop = p.ratioTop; } else { wrap.style.left = ''; wrap.style.top = ''; wrap.style.right = '14px'; wrap.style.bottom = '80px'; delete wrap.dataset.ratioLeft; delete wrap.dataset.ratioTop; } } initPanelPosition(); window.addEventListener('resize', () => { if (wrap.dataset.ratioLeft && wrap.dataset.ratioTop) { wrap.style.left = Math.round(window.innerWidth * parseFloat(wrap.dataset.ratioLeft)) + 'px'; wrap.style.top = Math.round(window.innerHeight * parseFloat(wrap.dataset.ratioTop)) + 'px'; wrap.style.right = 'auto'; wrap.style.bottom = 'auto'; } }); requestAnimationFrame(() => { wrap.style.willChange = 'opacity'; void wrap.getBoundingClientRect(); wrap.style.willChange = ''; }); } function start() { setTimeout(() => { try { mountPanel(); } catch (e) { console.warn(e); } autoApply(document); try { mo.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['aria-valuemin', 'aria-valuenow', 'value', 'min'] }); } catch (e) { console.warn('[GMGN-FEE] mo start error', e); } setInterval(() => { if (state.enabled) scanAndPatch(document); }, 1000); }, 2000); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start(); })();