Gm and welcome to the new issue of Learn to Code or NGMI. Crypto enthusiasts will love this one.
Everybody knows that Ether is money. However, all around the web, prices are quoted in US Trash Token (a.k.a. US Dollar). In this tutorial, we will create a Chrome extension that will look for dollar amounts on the pages we browse and convert them to ETH.
The tutorial is too long for a single email, so it will come out in several parts.
Prerequisites
Required knowledge:
HTML - how is the page structured, what are HTML elements, basic types of elements. Recommended reading: https://www.codecademy.com/learn/learn-html/modules/learn-html-elements
JavaScript - variables, loops, objects, functions. Recommended course: https://www.codecademy.com/learn/introduction-to-javascript
Required tools:
Chromium-based browser - Chrome, Brave, Opera, etc.
Code editor - I like Visual Studio Code, it’s feature rich and relatively lightweight
Basic Algorithm
We have to make our code run when the webpage loads. The algorithm is pretty straightforward:
Find dollar amounts on the current web page
Fetch the price of Ether from a public API
Convert the amounts into Ether according to the exchange rate
Replace the amounts on the page
Before we start building the extension, let’s try out our approach using the Chrome developer console. This way we can verify that our code works before we dive into extension development.
Iterating on the code
The cool thing about building extensions is that you can iterate on your code in interactive mode, on real pages that the extension will be working on.
Let’s use ebay.com as our example page. Open up the browser, go to ebay.com, and open developer tools on the “Console” tab:
Ignore the initial errors in the console, those just mean that eBay devs don’t know what they’re doing 😜
You can try any manipulations with the web page in the console. For example, I can print the title of the current page:
OK, now let’s get to the meat of our project - how do we actually find and replace the dollar amounts?
Finding dollar amounts on the page
See also: Document Object Model
An HTML page is essentially a tree of objects - HTML elements. Each element has a tag (type), attributes, textual content, and nested (child) elements.
The most straightforward way to find the dollar amounts is to go through all elements and see which ones contain a dollar sign ($).
To get all elements of the page, we can use the document.getElementsByTagName function and pass it “*” as the tag, which means “match any tag on the page”. Let’s call it and save the result in a variable:
const allElems = document.getElementsByTagName('*');
Looks like eBay front page has 2249 HTML elements (this number may vary each time depending on the promos eBay decides to show on the front page, etc.). Now let’s filter down to only the ones that have the dollar sign.
To do that, we can iterate through elements and use their innerText property to get the text inside of them.
There are a couple of caveats, though. Firstly, “$” is a legitimate JavaScript token, so we don’t want to touch any <script> tags.
Another one is, “innerText” returns the text inside of the element and all of its child elements. Consider a fragment like this:
<div>
<p>Price: <span>$420.69</span></p>
</div>
If we check innerText on <div>
, it will show “Price: $420.69”. Then, if we want to replace the price with the appropriate ETH amount, the result will be
<div>
Price: 0.102 ETH
</div>
So, by replacing innerText, we will have wiped out all the nested HTML markup. If you run this on the collection of all elements of the page, the first one it’ll hit will be the <html> element, i.e. the root. So you will wipe out all of the markup on the page and convert it to plain text! Ouch.
To avoid doing this, we will look for text nodes, which are at the bottom of the document object model tree. Therefore we will:
filter elements from
allElems
to exclude any script tags - we can use the array filter functionget all child text nodes that contain “$” from those elements and put them in a new array - we can use the flatMap function on the elements and the filter function on their child nodes (explained below); to get the text of the node, we use the textContent property
document.getElementsByTagName
returns an HTMLCollection, so we will need to convert it into an array first.
const elemsArray = Array.from(allElems); // Convert to array
const elemsNoScripts = elemsArray.filter(e =>
e.tagName !== 'SCRIPT'); // Filter out script tags
const textNodes = elemsNoScripts.flatMap(e =>
Array.from(e.childNodes).filter(n =>
n.nodeType === Node.TEXT_NODE && // Filter out non-text nodes
n.textContent.indexOf('$') >= 0));
The flatMap
expression may need a bit more explanation. flatMap
works in the following way:
go through the array
apply function to each element
if function returned an array for some elements, flatten them into a single array
The function that we run on each element
e => Array.from(e.childNodes).filter(n =>
n.nodeType === Node.TEXT_NODE &&
n.textContent.indexOf('$') >= 0)
simply takes all the element’s child nodes and filters out the non-text ones (entity, comment, etc.) and the ones that don’t have a “$” in their text, then returns the remaining ones.
Flattening a nested array means taking all elements of the subarrays and putting them all into a single array. Example: [[A, B, C], D, [E, F]] → [A, B, C, D, E, F]
What this means for our elemsNoScripts.flatMap
call is that even if the function returns multiple text nodes for some elements, the flatMap
method will still return all nodes in a single flat array.
We’re down to just 46 text nodes. Not bad! If you hover your mouse pointer over the nodes in the console, they should highlight on the page, so you can verify that they indeed contain the dollar sign.
Fetching Ether price
To get ETH price, we will use the free API from cryptocompare.com. The full unauthenticated URL is
https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD
Full documentation is available here. If you prefer to see prices denominated in another crypto coin, you can substitute ETH for that asset ticker to get its price.
To fetch it programmatically, we will use the built-in fetch function. Note: fetch is async, so it will return a Promise. To get the actual result, we will need to use the await keyword.
const apiUrl = 'https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD';
const response = await fetch(apiUrl);
To get the response body that contains the actual price, we need to call the json method on the response object (another async function).
const responseBody = await response.json();
As you can see, the response object has just one field “USD”, which contains the price.
const ethPrice = responseBody['USD'];
Replacing dollar amounts with Ether
Having found all the text nodes containing dollar amounts, we can now use their textContent
property to replace $ with Ξ amounts.
We need to find the pattern $XXX in the text and replace it with the Ether equivalent. The proper way to find and replace a pattern like that is a regular expression. However, that is a more advanced subject that we’ll leave for another lesson.
The algorithm we’re going to use is pretty simple:
Find all occurrences of $ in the string
After each $, find the next space - this is the end of the number
Parse the number between the $ and the space; if it is not a number, skip
Multiply the number by to the exchange rate
Replace the old number with the new one
Let’s take an example text string “The price is $1000 today” and go through the steps
Find $ in the string using the indexOf method - it is the 14th character of the string, so the index is 13 (in JavaScript, we always count from 0 when indexing strings and arrays).
Find the next space - it is the 19th character, so the index will be 18
Parse the number between the starting at index 14 and ending at 17
Multiply by exchange rate, e.g. if 1 ETH = $3200, then $1000 → 0.3125 ETH
Replace the old number with the new one. Strings are immutable in JavaScript, so the existing string object cannot be changed. Therefore, we just create a new string using everything that comes before and after the price using the substring method: “The price is ” + “Ξ0.3125” + “ today”
Repeat until there are no more dollar signs in the string
Let’s try this in code. We will use a for..of loop over the elements we previously found on the page and execute the above steps:
for (const node of textNodes) {
let text = node.textContent;
for (let dollarPos = text.indexOf('$'); dollarPos >= 0; dollarPos = text.indexOf('$')) {
const spacePos = text.indexOf(' ', dollarPos);
const numberEndPos = spacePos >= 0 ? spacePos : text.length;
const dollarText = text.substring(dollarPos + 1, numberEndPos);
const dollarValue = Number.parseFloat(dollarText);
if (Number.isNaN(dollarValue)) {
break;
}
const ethValue = dollarValue / window.ethPrice;
const newText = text.substring(0, dollarPos) + 'Ξ' + ethValue.toFixed(4) + text.substring(numberEndPos);
text = newText;
}
node.textContent = text;
}
Run the code and voila! eBay site is showing prices in ETH instead of US trash tokens.
Ok, so there’s a lot going on in the code snippet above, let’s break it down line by line:
for (const node of textNodes) {
Iterating through the array of the text nodes we found earlier. Using const
instead of let
to avoid accidentally reassigning the variable.
let text = node.textContent;
Creating a new variable for node text that we are going to process.
for (let dollarPos = text.indexOf('$'); dollarPos >= 0; dollarPos = text.indexOf('$')) {
Here, we use the “classic” for loop to run the code inside the curly braces until the text
variable contains no more $ signs. There are three expressions inside the parentheses:
The left-most statement (before the first semicolon) runs run before the loop starts, initializing the
dollarPos
variableThe expression is the middle is the exit condition - the loop will stop when it is false (the $ sign is not found)
The right-most statement runs after each loop iteration, updating the
dollarPos
variable to make sure the exit condition is evaluated correctly
const spacePos = text.indexOf(' ', dollarPos);
Finding the next space *after* the dollar sign (after the index stored in dollarPos
).
const numberEndPos = spacePos >= 0 ? spacePos : text.length;
So where does the number actually end? If there is a space and more text after the number, it ends at the space. Otherwise, it ends at the end of the string - text.length
.
const dollarText = text.substring(dollarPos + 1, numberEndPos);
const dollarValue = Number.parseFloat(dollarText);
if (Number.isNaN(dollarValue)) {
break;
}
The substring
call gives us the textual representation of the number (e.g. “1000”). However, to be able to perform arithmetical operations, we need to convert it to the numerical (a real number) representation using Number.parseFloat
. If for some reason, the substring is not a number (e.g. in the case of a stock ticker like $MSFT), parseFloat
will return a special value NaN. If that is the case, exit the inner for
loop so that the outer for..of
loop can move on to the next element.
const ethValue = dollarValue / ethPrice;
Calculating the ETH amount by dividing the dollar amount by the price.
const newText = text.substring(0, dollarPos) + 'Ξ' + ethValue.toFixed(4) + text.substring(numberEndPos);
text = newText;
}
Now, we need to compile the new string that will be the text of our element and update the text
variable. To obtain it, we concatenate several substrings:
everything until the dollar sign (“The price is ”)
The symbol for Ether - “Ξ”
ETH amount (“0.3125”) formatted with 4 decimal points (since most items on the web will cost fractions of an ETH)
Everything after the end of the number from the initial string (“ today”)
node.textContent = text;
}
Finally, after all dollar amounts in the string have been replaced, we assign the updated, processed text string to the node’s textContent
property.
Part 1 Conclusion
In this first part of the tutorial, we’ve learned how to find and manipulate elements on the page using the Chrome developer console, and also how to get an asset price from a public API.
In the next part, we will put the code we just tested in the console into an extension, so that it can run automatically when we navigate to a page. We will learn about organizing code and packaging extensions so that they can be published to the Chrome Web Store if desired.
Hope you’ve enjoyed this tutorial. If you have any questions or feedback, please post in the comments or hit me up on Twitter.
Bit of competition in the jungle on this subject as there are a few coding stacks now.
Enjoyed this one, unique material & well explained!