Created
September 10, 2021 16:21
-
-
Save jfloss1/9e97dc016c510d098ea02eec17d9b342 to your computer and use it in GitHub Desktop.
Tampermonkey Script for MarketCap.Cash
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name MarketCap.Cash Unofficial Features | |
// @namespace https://github.com/jfloss1 | |
// @version 1 | |
// @description Add wallet connection support + token balance and totals | |
// @author John Floss | |
// @match https://www.marketcap.cash/ | |
// @icon https://www.google.com/s2/favicons?domain=marketcap.cash | |
// @require https://cdn.ethers.io/lib/ethers-5.2.umd.min.js | |
// @grant none | |
// @run-at document-start | |
// ==/UserScript== | |
let ethereum = window.ethereum; | |
let ethers = window.ethers; | |
let provider; | |
let cryptoData = {}; | |
const ERC20ABI = [ | |
// Some details about the token | |
"function name() view returns (string)", | |
"function symbol() view returns (string)", | |
"function decimals() view returns (uint8)", | |
// Get the account balance | |
"function balanceOf(address) view returns (uint)", | |
// An event triggered whenever anyone transfers to someone else | |
"event Transfer(address indexed from, address indexed to, uint amount)" | |
]; | |
let contracts = [] | |
function sleep(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
function isIframe() { | |
return window !== top | |
} | |
async function MetaMaskExists() { | |
if (typeof ethereum !== 'undefined') { | |
console.log('MetaMask is installed!'); | |
let failCount = 0; | |
while (ethereum.networkVersion == null && failCount < 20) { | |
await sleep(100); | |
failCount++ | |
} | |
if (ethereum.networkVersion == "10000") { | |
provider = new ethers.providers.Web3Provider(window.ethereum) | |
window.provider = provider; | |
return true | |
} | |
console.log("Invalid networkVersion: " + ethereum.networkVersion + "; expected 10000; this only works with SmartBCH"); | |
} | |
return false | |
} | |
async function MetaMaskIsConnected() { | |
let isConnected = ethereum.selectedAddress != null; | |
let failCount = 0; | |
while (!isConnected && failCount < 10) { | |
await sleep(200); | |
failCount++; | |
isConnected = ethereum.selectedAddress != null | |
} | |
console.log("MetaMask Connected: " + isConnected); | |
return isConnected; | |
} | |
function initMetaMaskListener() { | |
ethereum.on('accountsChanged', function (accounts) { | |
console.log("ACCOUNTS UPDATED"); | |
updateMetaMaskConnectButton(); | |
updatePriceTable(); | |
}); | |
} | |
function disconnectMetaMask() { | |
console.log("TODO: Disconect MetaMask"); | |
} | |
function requestConnectMetaMask() { | |
ethereum.request({ method: 'eth_requestAccounts' }); | |
} | |
function getShortenedSelectedAddress() { | |
if (ethereum.selectedAddress == null) return ""; | |
return ethereum.selectedAddress.substring(0,5) + "..." + ethereum.selectedAddress.substring(ethereum.selectedAddress.length - 4, ethereum.selectedAddress); | |
} | |
async function getMetaMaskAccount() { | |
const accounts = await ethereum.request({ method: 'eth_requestAccounts' }); | |
const account = accounts[0] | |
} | |
function addMetaMaskConnectButton() { | |
let header = document.getElementsByTagName("main")[0].previousElementSibling; | |
if (header != null && header.children[0].children[0].children.length == 3) { | |
let button_AddToken = header.children[0].children[0].children[2]; | |
let newButton_ConnectMetaMask = button_AddToken.cloneNode(true); | |
newButton_ConnectMetaMask.children[0].innerText = "Connect MetaMask"; | |
newButton_ConnectMetaMask.children[0].type = ""; | |
newButton_ConnectMetaMask.children[0].addEventListener('click', requestConnectMetaMask); | |
button_AddToken.parentElement.insertBefore(newButton_ConnectMetaMask, button_AddToken); | |
} | |
} | |
function updateMetaMaskConnectButton() { | |
let header = document.getElementsByTagName("main")[0].previousElementSibling; | |
if (header != null && header.children[0].children[0].children.length == 4) { | |
let button_ConnectMetaMask = header.children[0].children[0].children[2]; | |
if (button_ConnectMetaMask.children[0].innerText == "Connect MetaMask") { | |
button_ConnectMetaMask.children[0].removeEventListener('click', requestConnectMetaMask); | |
button_ConnectMetaMask.children[0].addEventListener('click', disconnectMetaMask); | |
button_ConnectMetaMask.children[0].innerText = getShortenedSelectedAddress(); | |
} else { | |
button_ConnectMetaMask.children[0].removeEventListener('click', disconnectMetaMask); | |
button_ConnectMetaMask.children[0].addEventListener('click', requestConnectMetaMask); | |
button_ConnectMetaMask.children[0].innerText = "Connect MetaMask"; | |
} | |
} | |
} | |
function getContract(contractAddress) { | |
if (contracts[contractAddress] == null) { | |
contracts[contractAddress] = new ethers.Contract(contractAddress, ERC20ABI, provider); | |
} | |
return contracts[contractAddress]; | |
} | |
async function getAccountBalance() { | |
return ethers.utils.formatEther(await provider.getBalance(ethereum.selectedAddress)); | |
} | |
async function getTokenBalance(contractAddress, walletAddress) { | |
walletAddress = walletAddress != null ? walletAddress : ethereum.selectedAddress; | |
if (contractAddress == null) { | |
return await getAccountBalance(); | |
} else { | |
let contract = getContract(contractAddress); | |
if (contract) { | |
let balance = await contract.balanceOf(walletAddress); | |
let decimals = await contract.decimals(); | |
return ethers.utils.formatUnits(balance,decimals); | |
} | |
} | |
return null; | |
} | |
function roundBalance(balance) { | |
let decimals = 0; | |
balance = balance == null || isNaN(balance) ? 0 : balance; | |
let b = (balance + "").split("."); | |
if (b.length >= 8) decimals = 2; | |
else decimals = 9 - b.length; | |
let output = parseFloat(balance).toFixed(1 + decimals); | |
return output | |
} | |
function getTokenData(tokenSymbol) { | |
return cryptoData[tokenSymbol] | |
} | |
let hasFinalRow = false; | |
async function updatePriceTable(account) { | |
account = account != null ? account : ethereum.selectedAddress; | |
if (account == null) { | |
let table = document.getElementsByTagName("table")[0]; | |
if (table.tHead.children[0].children.length > 7) { //kill table changes if they exist | |
let headerPrice = table.tHead.children[0].children[4]; | |
headerPrice.previousElementSibling.remove(); // Balance | |
headerPrice.previousElementSibling.remove(); // Total | |
let rows = table.tBodies[0].children; | |
for (let i = 0; i < rows.length; i++) { | |
let rowElementPrice = rows[i].children[4]; | |
rowElementPrice.previousElementSibling.remove(); // Balance | |
rowElementPrice.previousElementSibling.remove(); // Total | |
} | |
rows[rows.length - 1].remove(); // balance total row | |
} | |
} else { | |
let table = document.getElementsByTagName("table")[0]; | |
let headerPrice = table.tHead.children[0].children[2]; | |
let newHeaderBalance = headerPrice.cloneNode(true); | |
newHeaderBalance.innerText = "Balance (T)"; | |
headerPrice.parentElement.insertBefore(newHeaderBalance, headerPrice); | |
let newHeaderTotal = headerPrice.cloneNode(true); | |
newHeaderTotal.innerText = "Total ($)"; | |
headerPrice.parentElement.insertBefore(newHeaderTotal, headerPrice); | |
let balanceTotal = 0; | |
let complete = 0; | |
let rows = table.tBodies[0].children; | |
for (let i = 0; i < rows.length; i++) { | |
setTimeout(async function(row) { | |
let rowElementTokenSymbol = row.children[1].children[0].children[1].children[1]; | |
let tokenSymbol = rowElementTokenSymbol.innerText; | |
let tokenData = getTokenData(tokenSymbol); | |
if (tokenData) { | |
let rowElementPrice = row.children[2]; | |
let newRowElementBalance = rowElementPrice.cloneNode(true); | |
newRowElementBalance.children[0].innerText = ""; | |
rowElementPrice.parentElement.insertBefore(newRowElementBalance, rowElementPrice); | |
let newRowElementTotal = rowElementPrice.cloneNode(true); | |
newRowElementTotal.children[0].innerText = ""; | |
rowElementPrice.parentElement.insertBefore(newRowElementTotal, rowElementPrice); | |
let balance = await getTokenBalance(tokenData.address); | |
let total = parseFloat(balance * tokenData.price).toFixed(2) | |
newRowElementBalance.children[0].innerText = parseFloat(roundBalance(balance)).toLocaleString('en-US', {maximumFractionDigits:6}); | |
newRowElementTotal.children[0].innerText = '$' + (total).toLocaleString(); | |
balanceTotal += parseFloat(total); | |
complete++; | |
} | |
},0, rows[i]); | |
} | |
if (!hasFinalRow) { | |
hasFinalRow = true; | |
while (complete < rows.length) { | |
await sleep(100); | |
} | |
let finalRow = document.createElement("tr"); | |
for (let i = 0; i < table.tHead.children[0].children.length; i++) { | |
finalRow.innerHTML += "<td class=\"px-4 py-4 whitespace-nowrap\"><div class=\"text-sm text-gray-900\"></div></td>" | |
} | |
finalRow.children[3].classList.add("text-right"); | |
finalRow.children[3].innerHTML = "<div class=\"text-lg text-bold text-gray-900 font-bold\">$" + balanceTotal.toLocaleString() + "</div>" | |
table.tBodies[0].appendChild(finalRow); | |
} | |
} | |
} | |
let replaced_pRTLPCB1 = false; | |
(function() { | |
// intercepting the firebase data call so we can get the token data too | |
const observedElement = document.body; | |
const observer = new MutationObserver(function(o) { | |
if (replaced_pRTLPCB1) return; | |
for (let a = 0; a < o.length; a++) { | |
let obs = o[a] | |
if (obs.addedNodes.length > 0) { | |
for (let b = 0; b < obs.addedNodes.length; b++) { | |
let addedNode = obs.addedNodes[b]; | |
if (addedNode.src != null && addedNode.src.startsWith("https://s-usc1c-nss-205.firebaseio.com/")) { | |
try { | |
if (pRTLPCB1 && !replaced_pRTLPCB1) { | |
replaced_pRTLPCB1 = true; | |
let original_pRTLPCB1 = pRTLPCB1; | |
pRTLPCB1 = function(a,b) { | |
if (a == 3) { | |
if (b != null && b.length > 0) { | |
cryptoData = b[0].d.b.d; | |
} else { | |
console.log("MISSING cryptoData!"); | |
console.log(a); | |
console.log(b); | |
} | |
} | |
original_pRTLPCB1(a,b) | |
} | |
} | |
} catch (e) {} | |
} | |
} | |
} | |
} | |
}); | |
observer.observe(observedElement, {subtree: true, childList: true}); | |
})(); | |
let interval_MainObserver = setInterval(function() { | |
// watching for the table so we can alter it | |
const observedElement = document.getElementsByTagName("main")[0]; | |
if (observedElement) { | |
observedElement.style.maxWidth = "90rem"; | |
clearInterval(interval_MainObserver); | |
const observer = new MutationObserver(function(o) { | |
for (let a = 0; a < o.length; a++) { | |
let obs = o[a] | |
if (obs.addedNodes.length > 0) { | |
for (let b = 0; b < obs.addedNodes.length; b++) { | |
let addedNode = obs.addedNodes[b]; | |
if (addedNode.tagName && addedNode.tagName == "DIV" && addedNode.innerHTML.indexOf("<table ") != -1) { | |
updatePriceTable(); | |
} | |
} | |
} | |
} | |
}); | |
observer.observe(observedElement, {subtree: true, childList: true}); | |
} | |
}, 10); | |
(function() { | |
if (!isIframe()) { | |
if (MetaMaskExists()) { | |
initMetaMaskListener(); | |
MetaMaskIsConnected().then((isConnected) => { | |
if (isConnected) { | |
addMetaMaskConnectButton(); | |
updateMetaMaskConnectButton(); | |
} else { | |
addMetaMaskConnectButton(); | |
} | |
}); | |
} | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment