Skip to content

Instantly share code, notes, and snippets.

@jfloss1
Created September 10, 2021 16:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jfloss1/9e97dc016c510d098ea02eec17d9b342 to your computer and use it in GitHub Desktop.
Save jfloss1/9e97dc016c510d098ea02eec17d9b342 to your computer and use it in GitHub Desktop.
Tampermonkey Script for MarketCap.Cash
// ==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