您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 V2EX 用户首页和评论列表中显示 $V2EX 代币持仓量,在评论中直接打赏 $V2EX
// ==UserScript== // @name V2EX Solana Token Assistant // @namespace http://tampermonkey.net/ // @version 1.6 // @description 在 V2EX 用户首页和评论列表中显示 $V2EX 代币持仓量,在评论中直接打赏 $V2EX // @match https://*.v2ex.com/member/* // @match https://v2ex.com/member/* // @match https://*.v2ex.com/t/* // @match https://v2ex.com/t/* // @require https://unpkg.com/@solana/web3.js@latest/lib/index.iife.min.js // @grant none // @run-at document-end // @license MIT // ==/UserScript== (async function () { 'use strict'; const TOKEN_MINT = "9raUVuzeWUk53co63M4WXLWPWE4Xc6Lpn7RS9dnkpump"; const RPC_URL = "https://solana-rpc.publicnode.com"; async function connectWallet() { const provider = window.phantom?.solana; if (!provider?.isPhantom) { return null; } try { const resp = await provider.connect(); return { provider, publicKey: new solanaWeb3.PublicKey(resp.publicKey.toString()) }; } catch (err) { return null; } } async function getAssociatedTokenAddress(walletPublicKey, mintPublicKey, rpcUrl) { const connection = new solanaWeb3.Connection(rpcUrl, "confirmed"); const TOKEN_PROGRAM_ID = new solanaWeb3.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); try { const response = await connection.getProgramAccounts(TOKEN_PROGRAM_ID, { filters: [ { dataSize: 165 }, { memcmp: { offset: 0, bytes: mintPublicKey.toBase58() } }, { memcmp: { offset: 32, bytes: walletPublicKey.toBase58() } }, ], }); return response.length > 0 ? response[0].pubkey.toBase58() : null; } catch (err) { console.log("查询 ATA 失败: " + err.message); } } function createTransferInstructionData(amount) { const data = new Uint8Array(9); data[0] = 3; const amountBigInt = BigInt(amount); for (let i = 0; i < 8; i++) { data[i + 1] = Number((amountBigInt >> BigInt(i * 8)) & BigInt(0xff)); } return data; } async function calculateAssociatedTokenAddress(walletPublicKey, mintPublicKey) { const TOKEN_PROGRAM_ID = new solanaWeb3.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); const ASSOCIATED_TOKEN_PROGRAM_ID = new solanaWeb3.PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); const [ata] = await solanaWeb3.PublicKey.findProgramAddress( [ walletPublicKey.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mintPublicKey.toBuffer(), ], ASSOCIATED_TOKEN_PROGRAM_ID ); return ata.toBase58(); } async function createAssociatedTokenAccountInstruction(payer, walletAddress, mint) { const ASSOCIATED_TOKEN_PROGRAM_ID = new solanaWeb3.PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); const TOKEN_PROGRAM_ID = new solanaWeb3.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); const ata = await calculateAssociatedTokenAddress(new solanaWeb3.PublicKey(walletAddress), new solanaWeb3.PublicKey(mint)); return new solanaWeb3.TransactionInstruction({ keys: [ { pubkey: new solanaWeb3.PublicKey(payer), isSigner: true, isWritable: true }, { pubkey: new solanaWeb3.PublicKey(ata), isSigner: false, isWritable: true }, { pubkey: new solanaWeb3.PublicKey(walletAddress), isSigner: false, isWritable: false }, { pubkey: new solanaWeb3.PublicKey(mint), isSigner: false, isWritable: false }, { pubkey: solanaWeb3.PublicKey.default, isSigner: false, isWritable: false }, { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, ], programId: ASSOCIATED_TOKEN_PROGRAM_ID, data: new Uint8Array(0), }); } async function transferSPLToken(toAddress, inputAmount) { const rpcUrl = RPC_URL; const connection = new solanaWeb3.Connection(rpcUrl, "confirmed"); const recipientAddress = toAddress; const mintAddress = TOKEN_MINT; const userInputAmount = inputAmount; try { const { provider, publicKey } = await connectWallet(); const amount = Math.floor(userInputAmount * Math.pow(10, 6)); let fromATA = await getAssociatedTokenAddress(publicKey, new solanaWeb3.PublicKey(mintAddress), rpcUrl); if (!fromATA) { alert("你暂未创建 $V2EX 代币地址"); return; } const instructions = []; let toATA = await getAssociatedTokenAddress(new solanaWeb3.PublicKey(recipientAddress), new solanaWeb3.PublicKey(mintAddress), rpcUrl); if (!toATA) { toATA = await calculateAssociatedTokenAddress(new solanaWeb3.PublicKey(recipientAddress), new solanaWeb3.PublicKey(mintAddress)); instructions.push( await createAssociatedTokenAccountInstruction(publicKey.toBase58(), recipientAddress, mintAddress) ); } instructions.push( new solanaWeb3.TransactionInstruction({ keys: [ { pubkey: new solanaWeb3.PublicKey(fromATA), isSigner: false, isWritable: true }, { pubkey: new solanaWeb3.PublicKey(toATA), isSigner: false, isWritable: true }, { pubkey: publicKey, isSigner: true, isWritable: false }, ], programId: new solanaWeb3.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), data: createTransferInstructionData(amount), }) ); const transaction = new solanaWeb3.Transaction(); instructions.forEach((instruction) => transaction.add(instruction)); const { blockhash } = await connection.getLatestBlockhash(); transaction.recentBlockhash = blockhash; transaction.feePayer = publicKey; const { signature } = await provider.signAndSendTransaction(transaction); alert('打赏成功!' + signature); } catch (err) { alert('打赏失败:' + err.message); } } async function getSplTokenAmountAndPubkey(owner, mint) { const url = "https://solana-rpc.publicnode.com"; const payload = { "jsonrpc": "2.0", "id": 1, "method": "getTokenAccountsByOwner", "params": [ owner, { "mint": mint }, { "encoding": "jsonParsed" } ] }; const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); const data = await res.json(); try { const value = data.result.value; if (value === null || value.length === 0) { return { amount: "0.0", pubkey: "" }; } else { let amount = value[0].account.data.parsed.info.tokenAmount.uiAmount; amount = amount == 0 ? "0.0" : amount; return { amount: amount, pubkey: value[0].pubkey }; } } catch (e) { return null; } } if (window.location.pathname.startsWith('/member/')) { const scripts = Array.from(document.querySelectorAll('script')); let address = null; for (const script of scripts) { const text = script.textContent.trim(); const match = text.match(/const\s+address\s*=\s*"([^"]+)"/); if (match) { address = match[1]; break; } } if (!address) { return; } const result = await getSplTokenAmountAndPubkey(address, TOKEN_MINT); if (result === null) { return; } const balance = result.amount; const linkAddress = result.pubkey ? address : result.pubkey; const newHtml = ` <div class="sep5"></div> <div class="flex-one-row" style="display: inline-flex; gap: 5px;"> <div class="badges"> <div class="badge" style="background-color: #000; color: #fff;">$V2EX</div> </div> <span>持有 </span> <a href="https://solscan.io/account/${linkAddress}" target="_blank">${balance}</a> </div> `; let targetElement = document.querySelector('a[href="/top/dau"]'); if (targetElement) { targetElement.insertAdjacentHTML('afterend', newHtml); } else { const span = [...document.querySelectorAll('span.gray')].find(el => el.textContent.includes('号会员,加入于') ); if (span) { span.insertAdjacentHTML('beforeend', newHtml); } } } else { document.querySelectorAll('div.thank_area').forEach(div => { if (/^thank_area_\w+$/.test(div.id)) { const aHodl = document.createElement('a'); aHodl.href = '#;'; aHodl.className = 'thank'; aHodl.innerHTML = '查看持仓'; aHodl.addEventListener('click', async (e) => { e.preventDefault(); const td = div.closest('td'); if (!td) { aHodl.innerHTML = '加载失败'; return; } const userLink = td.querySelector('a.dark[href^="/member/"]'); if (userLink) { aHodl.innerHTML = '加载中...'; try { const res = await fetch(userLink.href); const html = await res.text(); const match = html.match(/<script>\s*const address = "(.*?)";\s*<\/script>/); if (match && match[1]) { const result = await getSplTokenAmountAndPubkey(match[1], TOKEN_MINT); if (result === null) { aHodl.innerHTML = '加载失败'; } else { aHodl.innerHTML = `持有 ${result.amount}`; } } else { aHodl.innerHTML = '未绑定 Solana 钱包'; } } catch (err) { aHodl.innerHTML = '加载失败'; } } else { aHodl.innerHTML = '加载失败'; } }); div.insertAdjacentHTML('beforeend', ' '); div.insertAdjacentElement('beforeend', aHodl); const aTip = document.createElement('a'); aTip.href = '#;'; aTip.className = 'thank'; aTip.innerHTML = '打赏'; aTip.addEventListener('click', async (e) => { e.preventDefault(); const td = div.closest('td'); if (!td) { aTip.innerHTML = '加载失败'; return; } const userLink = td.querySelector('a.dark[href^="/member/"]'); if (userLink) { aTip.innerHTML = '加载中...'; try { const res = await fetch(userLink.href); const html = await res.text(); const match = html.match(/<script>\s*const address = "(.*?)";\s*<\/script>/); if (match && match[1]) { const userInputAmount = prompt('请输入要打赏的数量', '1'); if (userInputAmount !== null && userInputAmount.trim() !== "") { const number = Number(userInputAmount); if (Number.isInteger(number) && number > 0) { if (confirm("确认向 " + userLink.innerHTML + " 打赏 " + number + " $V2EX 吗?")) { aTip.innerHTML = '打赏中...'; await transferSPLToken(match[1], number); } } } aTip.innerHTML = '打赏'; } else { alert('对方未绑定 Solana 钱包'); aTip.innerHTML = '打赏'; } } catch (err) { alert('打赏失败:' + err.message); aTip.innerHTML = '打赏'; } } else { aTip.innerHTML = '加载失败'; } }); div.insertAdjacentHTML('beforeend', ' '); div.insertAdjacentElement('beforeend', aTip); } }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址