xic-presale/assets/wallet-connector.js

830 lines
31 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* XIC Multi-Wallet Connector v2.1
* 支持MetaMask、TronLink、OKX Wallet、Trust Wallet、TokenPocket、Bitget Wallet、Phantom
* 修复MetaMask 连接失败、缓存问题、移动端兼容
*/
(function () {
'use strict';
// ─── 样式 ─────────────────────────────────────────────────────────────────────
const STYLES = `
@keyframes xicFadeIn { from { opacity:0; transform:translateY(-10px) scale(0.97); } to { opacity:1; transform:translateY(0) scale(1); } }
@keyframes xicOverlayIn { from { opacity:0; } to { opacity:1; } }
@keyframes xicToastIn { from { opacity:0; transform:translateX(-50%) translateY(20px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }
@keyframes xicSpin { from { transform:rotate(0deg); } to { transform:rotate(360deg); } }
#xic-wallet-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.75);
backdrop-filter: blur(6px);
z-index: 999998;
align-items: center;
justify-content: center;
animation: xicOverlayIn 0.25s ease;
}
#xic-wallet-overlay.open { display: flex; }
#xic-wallet-modal {
background: linear-gradient(145deg, #1a1b2e, #16213e);
border: 1px solid rgba(99,102,241,0.3);
border-radius: 20px;
padding: 28px;
width: 420px;
max-width: calc(100vw - 32px);
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 25px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.05);
animation: xicFadeIn 0.3s cubic-bezier(0.34,1.56,0.64,1);
font-family: 'Plus Jakarta Sans', 'Inter', -apple-system, sans-serif;
}
#xic-wallet-modal::-webkit-scrollbar { width: 4px; }
#xic-wallet-modal::-webkit-scrollbar-track { background: transparent; }
#xic-wallet-modal::-webkit-scrollbar-thumb { background: rgba(99,102,241,0.4); border-radius: 2px; }
.xic-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.xic-modal-title {
font-size: 20px;
font-weight: 700;
color: #fff;
letter-spacing: -0.3px;
}
.xic-modal-subtitle {
font-size: 13px;
color: rgba(255,255,255,0.5);
margin-bottom: 20px;
}
#xic-close-btn {
background: rgba(255,255,255,0.08);
border: none;
color: rgba(255,255,255,0.7);
width: 32px;
height: 32px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
#xic-close-btn:hover { background: rgba(255,255,255,0.15); color: #fff; }
.xic-wallet-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 16px;
}
.xic-wallet-btn {
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 14px;
padding: 14px 12px;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
transition: all 0.2s;
position: relative;
text-align: center;
}
.xic-wallet-btn:hover {
background: rgba(99,102,241,0.15);
border-color: rgba(99,102,241,0.4);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(99,102,241,0.2);
}
.xic-wallet-btn:active { transform: translateY(0); }
.xic-wallet-btn.loading { pointer-events: none; opacity: 0.7; }
.xic-wallet-btn.detected {
border-color: rgba(16,185,129,0.4);
background: rgba(16,185,129,0.06);
}
.xic-wallet-icon {
width: 44px;
height: 44px;
border-radius: 12px;
overflow: hidden;
flex-shrink: 0;
}
.xic-wallet-icon svg, .xic-wallet-icon img { width: 100%; height: 100%; }
.xic-wallet-name {
font-size: 13px;
font-weight: 600;
color: #fff;
line-height: 1.2;
}
.xic-wallet-desc {
font-size: 11px;
color: rgba(255,255,255,0.45);
line-height: 1.3;
}
.xic-wallet-network {
font-size: 10px;
font-weight: 600;
padding: 2px 8px;
border-radius: 20px;
background: rgba(99,102,241,0.2);
color: rgba(99,102,241,0.9);
letter-spacing: 0.3px;
}
.xic-detected-dot {
position: absolute;
top: 10px;
right: 10px;
width: 8px;
height: 8px;
background: #10b981;
border-radius: 50%;
box-shadow: 0 0 6px #10b981;
}
.xic-spinner {
width: 20px;
height: 20px;
border: 2px solid rgba(255,255,255,0.2);
border-top-color: #fff;
border-radius: 50%;
animation: xicSpin 0.8s linear infinite;
margin: 0 auto;
}
.xic-modal-footer {
text-align: center;
font-size: 12px;
color: rgba(255,255,255,0.35);
padding-top: 12px;
border-top: 1px solid rgba(255,255,255,0.06);
}
.xic-modal-footer a {
color: rgba(99,102,241,0.8);
text-decoration: none;
}
.xic-modal-footer a:hover { color: #818cf8; text-decoration: underline; }
.xic-install-hint {
background: rgba(245,158,11,0.1);
border: 1px solid rgba(245,158,11,0.25);
border-radius: 12px;
padding: 12px 14px;
margin-top: 12px;
font-size: 12px;
color: rgba(255,255,255,0.7);
display: none;
}
.xic-install-hint.show { display: block; }
.xic-install-hint strong { color: #fbbf24; }
.xic-install-hint a {
color: #fbbf24;
font-weight: 600;
text-decoration: none;
}
.xic-install-hint a:hover { text-decoration: underline; }
.xic-connected-banner {
background: linear-gradient(135deg, rgba(16,185,129,0.15), rgba(5,150,105,0.1));
border: 1px solid rgba(16,185,129,0.3);
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 16px;
display: none;
align-items: center;
gap: 10px;
}
.xic-connected-banner.show { display: flex; }
.xic-connected-dot {
width: 10px;
height: 10px;
background: #10b981;
border-radius: 50%;
box-shadow: 0 0 8px #10b981;
flex-shrink: 0;
}
.xic-connected-text { flex: 1; }
.xic-connected-label { font-size: 11px; color: rgba(255,255,255,0.5); }
.xic-connected-addr { font-size: 13px; font-weight: 600; color: #10b981; font-family: monospace; }
.xic-disconnect-btn {
background: rgba(239,68,68,0.15);
border: 1px solid rgba(239,68,68,0.3);
color: #f87171;
border-radius: 8px;
padding: 4px 10px;
font-size: 11px;
cursor: pointer;
transition: all 0.2s;
}
.xic-disconnect-btn:hover { background: rgba(239,68,68,0.25); }
`;
// ─── 钱包配置 ─────────────────────────────────────────────────────────────────
const WALLETS = [
{
id: 'metamask',
name: 'MetaMask',
desc: '最流行的以太坊钱包',
network: 'ETH/BSC',
installUrl: 'https://metamask.io/download/',
icon: `<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="40" height="40" rx="10" fill="#F6851B"/><path d="M32 10L22.5 17L24.3 13L32 10Z" fill="#E2761B"/><path d="M8 10L17.4 17.1L15.7 13L8 10Z" fill="#E4761B"/><path d="M28.7 26.5L26.1 30.5L31.4 32L33 26.6L28.7 26.5Z" fill="#E4761B"/><path d="M7 26.6L8.6 32L13.9 30.5L11.3 26.5L7 26.6Z" fill="#E4761B"/><path d="M13.6 21.5L12 24L17.2 24.3L17 18.8L13.6 21.5Z" fill="#E4761B"/><path d="M26.4 21.5L22.9 18.7L22.8 24.3L28 24L26.4 21.5Z" fill="#E4761B"/><path d="M13.9 30.5L16.8 29L14.3 26.6L13.9 30.5Z" fill="#E4761B"/><path d="M23.2 29L26.1 30.5L25.7 26.6L23.2 29Z" fill="#E4761B"/></svg>`,
detect: () => {
// 检测 MetaMaskwindow.ethereum.isMetaMask 或 window.ethereum providers 数组
if (window.ethereum) {
if (window.ethereum.isMetaMask) return true;
if (window.ethereum.providers) {
return window.ethereum.providers.some(p => p.isMetaMask);
}
}
return false;
},
connect: connectMetaMask,
},
{
id: 'tronlink',
name: 'TronLink',
desc: 'TRON 官方钱包',
network: 'TRON',
installUrl: 'https://www.tronlink.org/',
icon: `<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="40" height="40" rx="10" fill="#EF0027"/><path d="M20 8L32 15V25L20 32L8 25V15L20 8Z" fill="#FF3355" opacity="0.3"/><path d="M20 10L30 16.5V23.5L20 30L10 23.5V16.5L20 10Z" fill="white" opacity="0.9"/><path d="M20 14L26 17.5V24.5L20 28L14 24.5V17.5L20 14Z" fill="#EF0027"/></svg>`,
detect: () => !!(window.tronLink || (window.tronWeb && window.tronWeb.ready)),
connect: connectTronLink,
},
{
id: 'okx',
name: 'OKX Wallet',
desc: 'OKX 多链钱包',
network: 'TRON/ETH',
installUrl: 'https://www.okx.com/web3',
icon: `<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="40" height="40" rx="10" fill="#000"/><rect x="10" y="10" width="8" height="8" rx="1" fill="white"/><rect x="22" y="10" width="8" height="8" rx="1" fill="white"/><rect x="16" y="16" width="8" height="8" rx="1" fill="white"/><rect x="10" y="22" width="8" height="8" rx="1" fill="white"/><rect x="22" y="22" width="8" height="8" rx="1" fill="white"/></svg>`,
detect: () => !!(window.okxwallet || (window.ethereum && window.ethereum.isOkxWallet)),
connect: connectOKX,
},
{
id: 'trust',
name: 'Trust Wallet',
desc: '币安官方钱包',
network: 'TRON/ETH',
installUrl: 'https://trustwallet.com/download',
icon: `<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="40" height="40" rx="10" fill="#3375BB"/><path d="M20 8L30 12V20C30 25.5 25.5 30.5 20 32C14.5 30.5 10 25.5 10 20V12L20 8Z" fill="white"/><path d="M16 20L18.5 22.5L24 17" stroke="#3375BB" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
detect: () => !!(window.trustwallet || (window.ethereum && window.ethereum.isTrust)),
connect: connectTrust,
},
{
id: 'tokenpocket',
name: 'TokenPocket',
desc: '多链 DeFi 钱包',
network: 'TRON/ETH',
installUrl: 'https://www.tokenpocket.pro/',
icon: `<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="40" height="40" rx="10" fill="#2980FE"/><rect x="10" y="12" width="20" height="4" rx="2" fill="white"/><rect x="10" y="18" width="14" height="4" rx="2" fill="white"/><rect x="10" y="24" width="17" height="4" rx="2" fill="white"/></svg>`,
detect: () => !!(window.tokenpocket || (window.ethereum && window.ethereum.isTokenPocket)),
connect: connectTokenPocket,
},
{
id: 'bitget',
name: 'Bitget Wallet',
desc: 'Bitget 交易所钱包',
network: 'TRON/ETH',
installUrl: 'https://web3.bitget.com/zh-CN/wallet-download',
icon: `<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="40" height="40" rx="10" fill="#00F0FF"/><path d="M12 20C12 15.6 15.6 12 20 12C22.2 12 24.2 12.9 25.7 14.3L28 12C26 10.1 23.1 9 20 9C13.9 9 9 13.9 9 20C9 26.1 13.9 31 20 31C23.1 31 26 29.9 28 28L25.7 25.7C24.2 27.1 22.2 28 20 28C15.6 28 12 24.4 12 20Z" fill="#1A1A2E"/></svg>`,
detect: () => !!(window.bitkeep || (window.ethereum && window.ethereum.isBitKeep)),
connect: connectBitget,
},
{
id: 'phantom',
name: 'Phantom',
desc: 'Solana & 多链钱包',
network: 'SOL/ETH',
installUrl: 'https://phantom.app/download',
icon: `<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="40" height="40" rx="10" fill="#AB9FF2"/><path d="M20 9C14 9 9 14 9 20C9 26 14 31 20 31C26 31 31 26 31 20C31 14 26 9 20 9ZM20 27C16.1 27 13 23.9 13 20C13 16.1 16.1 13 20 13C23.9 13 27 16.1 27 20C27 23.9 23.9 27 20 27Z" fill="white"/><circle cx="17" cy="19" r="2" fill="white"/><circle cx="23" cy="19" r="2" fill="white"/></svg>`,
detect: () => !!(window.phantom),
connect: connectPhantom,
},
];
// ─── 状态 ─────────────────────────────────────────────────────────────────────
let currentWallet = null;
let currentAddress = null;
let modalEl = null;
let overlayEl = null;
let isInjected = false;
let installHintEl = null;
let connectedBannerEl = null;
// ─── 工具函数 ─────────────────────────────────────────────────────────────────
function shortenAddr(addr) {
if (!addr) return '';
if (addr.length > 20) return addr.slice(0, 8) + '...' + addr.slice(-6);
return addr;
}
function showToast(msg, type = 'info') {
const colors = { info: '#6366f1', success: '#10b981', error: '#ef4444', warn: '#f59e0b' };
// 移除已有 toast
document.querySelectorAll('.xic-toast').forEach(t => t.remove());
const toast = document.createElement('div');
toast.className = 'xic-toast';
toast.style.cssText = `
position:fixed; bottom:28px; left:50%; transform:translateX(-50%);
background:${colors[type] || colors.info}; color:#fff;
padding:12px 24px; border-radius:12px; font-size:14px; font-weight:600;
z-index:9999999; box-shadow:0 8px 32px rgba(0,0,0,0.4);
font-family:'Plus Jakarta Sans','Inter',sans-serif;
animation: xicToastIn 0.3s ease;
pointer-events:none; white-space:nowrap; max-width:90vw;
`;
toast.textContent = msg;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 4000);
}
function getEthProvider() {
// 获取 MetaMask provider处理多钱包共存的情况
if (!window.ethereum) return null;
if (window.ethereum.isMetaMask) return window.ethereum;
// 多钱包共存时,从 providers 数组中找 MetaMask
if (window.ethereum.providers) {
const mm = window.ethereum.providers.find(p => p.isMetaMask);
if (mm) return mm;
}
return null;
}
// ─── 钱包连接函数 ─────────────────────────────────────────────────────────────
async function connectMetaMask() {
const provider = getEthProvider();
if (!provider) {
// 检测是否是移动端
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
if (isMobile) {
// 移动端:尝试通过 MetaMask 深链接打开
console.log("[XIC] MetaMask mobile deeplink blocked");
throw new Error('正在跳转到 MetaMask App...');
}
// 桌面端:显示安装提示
showInstallHint('metamask');
throw new Error('MetaMask 未安装');
}
try {
// 请求账户授权
const accounts = await provider.request({ method: 'eth_requestAccounts' });
if (!accounts || accounts.length === 0) {
throw new Error('用户拒绝了连接请求');
}
const address = accounts[0];
// 获取当前链信息
let chainId = null;
try {
chainId = await provider.request({ method: 'eth_chainId' });
} catch (e) {}
console.log('[XIC Wallet] MetaMask 连接成功:', address, 'chainId:', chainId);
return { address, type: 'eth', chainId };
} catch (err) {
if (err.code === 4001) {
throw new Error('用户取消了连接');
}
if (err.code === -32002) {
throw new Error('MetaMask 正在等待确认,请打开 MetaMask 扩展');
}
throw err;
}
}
async function connectTronLink() {
// 方式1新版 TronLink API
if (window.tronLink) {
try {
const res = await window.tronLink.request({ method: 'tron_requestAccounts' });
if (res && (res.code === 200 || res.code === 4000)) {
await new Promise(r => setTimeout(r, 500));
const addr = window.tronWeb?.defaultAddress?.base58;
if (addr) return { address: addr, type: 'tron' };
}
} catch (e) {
console.warn('[XIC Wallet] TronLink new API failed:', e);
}
}
// 方式2旧版 tronWeb
if (window.tronWeb && window.tronWeb.ready) {
const addr = window.tronWeb.defaultAddress?.base58;
if (addr) return { address: addr, type: 'tron' };
}
// 未安装
showInstallHint('tronlink');
throw new Error('请先安装 TronLink 钱包');
}
async function connectOKX() {
// OKX TRON
if (window.okxwallet?.tronLink) {
try {
const res = await window.okxwallet.tronLink.request({ method: 'tron_requestAccounts' });
if (res?.code === 200) {
const addr = window.okxwallet.tronLink.tronWeb?.defaultAddress?.base58;
if (addr) return { address: addr, type: 'tron' };
}
} catch (e) {}
}
// OKX ETH
if (window.okxwallet) {
try {
const accounts = await window.okxwallet.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
} catch (e) {}
}
// 通用 ethereumOKX 注入)
if (window.ethereum?.isOkxWallet) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
}
showInstallHint('okx');
throw new Error('请先安装 OKX Wallet');
}
async function connectTrust() {
if (window.trustwallet) {
try {
const accounts = await window.trustwallet.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
} catch (e) {}
}
if (window.ethereum?.isTrust) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
}
// 移动端深链接
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
if (isMobile) {
console.log("[XIC] Trust Wallet deeplink blocked");
throw new Error('正在跳转到 Trust Wallet...');
}
showInstallHint('trust');
throw new Error('请先安装 Trust Wallet');
}
async function connectTokenPocket() {
if (window.tokenpocket) {
try {
const accounts = await window.tokenpocket.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
} catch (e) {}
}
if (window.ethereum?.isTokenPocket) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
}
showInstallHint('tokenpocket');
throw new Error('请先安装 TokenPocket');
}
async function connectBitget() {
if (window.bitkeep?.ethereum) {
try {
const accounts = await window.bitkeep.ethereum.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
} catch (e) {}
}
if (window.ethereum?.isBitKeep) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
}
showInstallHint('bitget');
throw new Error('请先安装 Bitget Wallet');
}
async function connectPhantom() {
if (window.phantom?.ethereum) {
try {
const accounts = await window.phantom.ethereum.request({ method: 'eth_requestAccounts' });
if (accounts?.[0]) return { address: accounts[0], type: 'eth' };
} catch (e) {}
}
if (window.phantom?.solana) {
try {
const resp = await window.phantom.solana.connect();
if (resp?.publicKey) return { address: resp.publicKey.toString(), type: 'sol' };
} catch (e) {}
}
showInstallHint('phantom');
throw new Error('请先安装 Phantom 钱包');
}
// ─── 安装提示 ─────────────────────────────────────────────────────────────────
function showInstallHint(walletId) {
const wallet = WALLETS.find(w => w.id === walletId);
if (!wallet || !installHintEl) return;
installHintEl.innerHTML = `
<strong>⚠️ ${wallet.name} 未安装</strong><br>
请先安装 ${wallet.name} 钱包扩展,然后刷新页面重试。<br>
<a href="${wallet.installUrl}" target="_blank" rel="noopener">👉 点击下载 ${wallet.name}</a>
`;
installHintEl.classList.add('show');
}
// ─── 连接成功处理 ─────────────────────────────────────────────────────────────
function onConnected(address, wallet) {
currentAddress = address;
currentWallet = wallet;
// 更新 modal 内的已连接横幅
if (connectedBannerEl) {
connectedBannerEl.querySelector('.xic-connected-addr').textContent = shortenAddr(address);
connectedBannerEl.querySelector('.xic-connected-label').textContent = `已连接 ${wallet.name}`;
connectedBannerEl.classList.add('show');
}
// 更新导航栏按钮
updateNavbarBtn(address);
// 关闭弹窗
setTimeout(() => closeModal(), 800);
// 显示成功提示
showToast(`${wallet.name} 连接成功:${shortenAddr(address)}`, 'success');
// 监听账户变更
if (wallet.id === 'metamask') {
const provider = getEthProvider();
if (provider) {
provider.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
onDisconnected();
} else {
currentAddress = accounts[0];
updateNavbarBtn(accounts[0]);
}
});
}
}
}
function onDisconnected() {
currentAddress = null;
currentWallet = null;
if (connectedBannerEl) connectedBannerEl.classList.remove('show');
updateNavbarBtn(null);
showToast('钱包已断开连接', 'warn');
}
function updateNavbarBtn(address) {
const btn = findConnectBtn();
if (!btn) return;
if (address) {
btn.textContent = shortenAddr(address);
btn.style.cssText = `
background: linear-gradient(135deg, #10b981, #059669) !important;
color: #fff !important;
border: none !important;
border-radius: 10px !important;
padding: 10px 18px !important;
font-weight: 700 !important;
cursor: pointer !important;
font-size: 13px !important;
`;
} else {
btn.textContent = 'Connect Wallet';
btn.style.cssText = '';
}
}
// ─── 渲染钱包列表 ─────────────────────────────────────────────────────────────
function renderWalletGrid() {
const grid = modalEl.querySelector('.xic-wallet-grid');
if (!grid) return;
grid.innerHTML = '';
WALLETS.forEach(wallet => {
const detected = wallet.detect();
const btn = document.createElement('button');
btn.className = 'xic-wallet-btn' + (detected ? ' detected' : '');
btn.innerHTML = `
${detected ? '<span class="xic-detected-dot"></span>' : ''}
<div class="xic-wallet-icon">${wallet.icon}</div>
<div class="xic-wallet-name">${wallet.name}</div>
<div class="xic-wallet-desc">${wallet.desc}</div>
<div class="xic-wallet-network">${wallet.network}</div>
`;
btn.addEventListener('click', () => handleWalletClick(wallet, btn));
grid.appendChild(btn);
});
}
async function handleWalletClick(wallet, btn) {
// 隐藏安装提示
if (installHintEl) installHintEl.classList.remove('show');
// 显示加载状态
const originalHTML = btn.innerHTML;
btn.classList.add('loading');
btn.innerHTML = `<div class="xic-spinner"></div><div class="xic-wallet-name" style="margin-top:8px">连接中...</div>`;
try {
const result = await wallet.connect();
onConnected(result.address, wallet);
} catch (err) {
console.error('[XIC Wallet] 连接失败:', err);
// 恢复按钮状态
btn.classList.remove('loading');
btn.innerHTML = originalHTML;
const msg = err.message || '连接失败';
if (!msg.includes('未安装') && !msg.includes('Not installed')) {
showToast('❌ ' + msg, 'error');
}
}
}
// ─── 创建弹窗 ─────────────────────────────────────────────────────────────────
function createModal() {
// 注入样式
if (!document.getElementById('xic-wallet-styles')) {
const style = document.createElement('style');
style.id = 'xic-wallet-styles';
style.textContent = STYLES;
document.head.appendChild(style);
}
// 创建遮罩层
overlayEl = document.createElement('div');
overlayEl.id = 'xic-wallet-overlay';
// 创建弹窗
modalEl = document.createElement('div');
modalEl.id = 'xic-wallet-modal';
modalEl.innerHTML = `
<div class="xic-modal-header">
<div class="xic-modal-title">连接钱包</div>
<button id="xic-close-btn">✕</button>
</div>
<div class="xic-modal-subtitle">选择您的钱包以继续购买 XIC</div>
<div class="xic-connected-banner">
<div class="xic-connected-dot"></div>
<div class="xic-connected-text">
<div class="xic-connected-label">已连接</div>
<div class="xic-connected-addr"></div>
</div>
<button class="xic-disconnect-btn">断开</button>
</div>
<div class="xic-wallet-grid"></div>
<div class="xic-install-hint"></div>
<div class="xic-modal-footer">
没有钱包?
<a href="https://metamask.io/download/" target="_blank" rel="noopener">下载 MetaMask</a>
<a href="https://www.tronlink.org/" target="_blank" rel="noopener">下载 TronLink</a>
</div>
`;
overlayEl.appendChild(modalEl);
document.body.appendChild(overlayEl);
// 获取引用
connectedBannerEl = modalEl.querySelector('.xic-connected-banner');
installHintEl = modalEl.querySelector('.xic-install-hint');
// 关闭按钮
modalEl.querySelector('#xic-close-btn').addEventListener('click', closeModal);
// 断开连接按钮
modalEl.querySelector('.xic-disconnect-btn').addEventListener('click', () => {
onDisconnected();
closeModal();
});
// 点击遮罩关闭
overlayEl.addEventListener('click', (e) => {
if (e.target === overlayEl) closeModal();
});
// ESC 关闭
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeModal();
});
// 渲染钱包列表
renderWalletGrid();
}
// ─── 弹窗控制 ─────────────────────────────────────────────────────────────────
function openModal() {
if (!overlayEl) createModal();
overlayEl.classList.add('open');
// 刷新检测状态
renderWalletGrid();
// 如果已连接,显示已连接状态
if (currentAddress && connectedBannerEl) {
connectedBannerEl.querySelector('.xic-connected-addr').textContent = shortenAddr(currentAddress);
connectedBannerEl.querySelector('.xic-connected-label').textContent = `已连接 ${currentWallet?.name || ''}`;
connectedBannerEl.classList.add('show');
}
if (installHintEl) installHintEl.classList.remove('show');
}
function closeModal() {
if (overlayEl) overlayEl.classList.remove('open');
}
// ─── 找到连接按钮 ─────────────────────────────────────────────────────────────
function findConnectBtn() {
const selectors = [
'button[class*="wallet"]',
'button[class*="connect"]',
'button[class*="Connect"]',
];
// 先用文本匹配
const allBtns = document.querySelectorAll('button');
for (const btn of allBtns) {
const text = (btn.textContent || '').trim();
if (
text === 'Select Wallet' ||
text === 'Connect Wallet' ||
text === 'Connect' ||
text.startsWith('Connecting') ||
text === 'Change wallet'
) {
return btn;
}
}
// 再用 class 匹配
for (const sel of selectors) {
const el = document.querySelector(sel);
if (el) return el;
}
return null;
}
// ─── 注入拦截器 ───────────────────────────────────────────────────────────────
function injectInterceptor() {
const btn = findConnectBtn();
if (btn && !btn.dataset.xicInjected) {
// 克隆按钮移除原有监听器
const newBtn = btn.cloneNode(true);
newBtn.dataset.xicInjected = 'true';
newBtn.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
openModal();
});
btn.parentNode.replaceChild(newBtn, btn);
isInjected = true;
console.log('[XIC Wallet] v2.1 多钱包连接器已注入');
}
}
// ─── 暴露全局 API ─────────────────────────────────────────────────────────────
window.XICWallet = {
open: openModal,
close: closeModal,
getAddress: () => currentAddress,
getWallet: () => currentWallet,
isConnected: () => !!currentAddress,
disconnect: onDisconnected,
};
// ─── 初始化 ───────────────────────────────────────────────────────────────────
function init() {
// 使用 MutationObserver 持续监听 DOM
const observer = new MutationObserver(() => {
if (!isInjected) injectInterceptor();
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
injectInterceptor();
} else {
document.addEventListener('DOMContentLoaded', () => {
observer.observe(document.body, { childList: true, subtree: true });
injectInterceptor();
});
}
// 多次重试确保注入成功React 渲染后)
[500, 1000, 1500, 2500, 4000].forEach(delay => {
setTimeout(() => {
if (!isInjected) injectInterceptor();
}, delay);
});
}
init();
})();