Ethereum took the web and cryptocurrency world by storm in 2017. In the span of one year, the price of 1 Ether skyrocketed from $8.24 USD to $750.00 USD – and for good reason. If you need convincing on the nature of decentralization, just watch this video featuring Ethereum founder Vitalik Buterin and AngelList CEO Naval Ravikant and come back in 30 minutes.
However, if you’ve found this article, I’m guessing you’re already a believer and want to learn more about interacting with the Ethereum main network. In my case, I wanted to find a way to programmatically send amounts of Ether between different addresses based on external factors (think IFTTT for sending cryptocurrency.)
Whatever your reasoning, in this article, we’re going to go over how to use Node.js and the quickly-evolving Web3.js library to submit ETH transactions to the Ethereum network. We’ll start off with test transactions first to learn the basics and make sure everything is working as expected. Then, once you’re comfortable, we can flip the switch and move real ETH around the Ethereum mainnet.
There’s a lot to learn here, especially if you’re new to this space like I am, so I’ll try to break down the terms to the best of my understanding. Let’s get started!
Creating a Test Ethereum Wallet
We’re going to get started using some test Ether rather than actual money while we’re learning about this whole process, so the first thing we’ll need to do is create a new Ethereum wallet address that will be able to hold our test funds.
To do this, first visit https://www.myetherwallet.com
Next, open up the dropdown at the top right corner and change the Network selection to Rinkeby – infura.io. This change will make sure that the new wallet address that we are creating is going to exist on the Ethereum test network rather than the live network.
Note: Even though we’re only working with test funds, I’m going to recommend that you take the proper precautions to secure your wallet information so as to practice the best habits for when you do work with real funds. You do not need to be entirely secure with the test network, as there is no consequence to someone accessing your wallet information, but it’s a good idea to get used to the methods of storing your wallet data securely.
Now, select the New Wallet tab. You should see a screen that says Create New Wallet.
You’ll be prompted to enter a password to associate with your new test wallet. Enter it in the text box on the screen and be sure to store it somewhere secure where you’ll remember it.
After entering your password, you’ll be presented with a screen which allows you to download the wallet keystore. This is essentially a file that will allow you to restore your new test wallet if you do end up losing your password. Download it and store it somewhere safe. Then, acknowledge the warning on the screen and click the red “I understand. Continue.” button.
On the next screen, you’ll be presented with your new wallet’s private key. Copy this key to your clipboard and store it somewhere safe.
You may also wish to print out a paper wallet, which is literally a piece of paper that contains this information so it doesn’t exist on your computer anywhere. If you have any serious funds involved, you can store this paper wallet in a safe, make several copies of it, hide it in your walls etc. I’m going to skip this step for the test wallet, but this is a good idea for actual funds.
When you’re ready, click Save your Address.
The next screen will allow you to unlock your wallet to see your public wallet address. For the test network, we can use our private key from the previous step to unlock the wallet and view the public address.
To do this, under the section which reads “How would you like to access your wallet?” choose Private Key, paste your Private Key into the text box on the screen and click Unlock.
Your new wallet’s address will be displayed on the screen. It will start with “0x” and should look something like this: 0x15ECf4401CF9Dd4e74C1B7b44D105bb66e097BBA
Copy this address to your clipboard and store it somewhere safe. You’ve successfully generated a new test wallet!
To summarize, you should now have 4 or 5 pieces of data stored somewhere safely:
- Your wallet password
- Your wallet keystore
- Your wallet private key
- Your wallet public address
- (optional) Your printed paper-wallet
Sweet! Now let’s go get some test money to play with.
Funding your Test Wallet with Test Ether
Since we’re using the test network, there are specific websites, called faucets, that will happily issue you some test tokens to play around with. We’re going to use the Rinkeby Faucet to request that 3 ETH be sent to our new test wallet. I wish it were real ETH (it’d be worth more than $3,000 at the time of writing,) but alas, funny money will have to do for now.
Visit the Rinkeby Faucet site here: https://www.rinkeby.io/#faucet
The Rinkeby Faucet has a protection in place to help prevent abuse of the system: they will only issue test Ether to public wallet addresses that have been associated with a post on a social network.
The easiest way to obtain your test Ether is by using Twitter to send out a tweet containing your public wallet address. I’ve created a mini-tool below to allow you to generate a tweet that works as expected.
After you’ve sent out your tweet, you should copy the link to the tweet and paste your link in the Rinkeby Authenticated Faucet input box. The link should look something like this:
Then, from the “Give me Ether” dropdown, select the option that reads “3 Ethers / 8 Hours”
If all goes well, in a moment you will see a message that says “Funding request accepted for username@twitter to wallet address“. The test network may take a few minutes to confirm your transaction, but you should be able to verify that everything worked as expected pretty quickly.
To verify that the test Ethers made it to your wallet, go back to https://www.myetherwallet.com and click on the “View Wallet Info” tab. Since MyEtherWallet does not store any of your sensitive data, you’ll need to unlock your wallet again by entering your Wallet Private Key just as you did before.
It works! Your wallet should now have an account balance of 3 RINKEBY ETH
Due to the decentralized nature of Ethereum, you can also view your account balance on EtherScan by visiting the following link, swapping out the placeholder address with your own Public Wallet Address:
https://rinkeby.etherscan.io/address/0x_your_public_wallet_address
Sending Your Test Ether to Another Ethereum Wallet
Now that you’ve created your test wallet and added some test Ethers to it, it’s time to get into the nitty gritty of it all. How can we write some code that will allow us to programmatically send our test Ether to another public Ethereum wallet address?
For the purposes of this tutorial, your objective is to send some test Ether to my own Ethereum test wallet. My public Ethereum test wallet address on the Rinkeby testnet is 0xeF19E7Ec9eE90a0426c60E74aFCc504C02513E11
First things first: you’ll need to create a new free account over at Infura, which is a service that acts as a remote Ethereum node, making it simple to interact with the Ethereum testnet and mainnet. You can sign up here: https://infura.io/signup
Next, open up your terminal and clone the starter repository to a directory that you can get to easily. Make sure you don’t do this in a Dropbox folder, since we’ll be storing some sensitive information and also pulling a bunch of dependencies with Node.
git clone git@github.com:davekiss/web3-node-tutorial.git
Now, make your way into the new directory with cd web3-node-tutorial
and install the app dependencies by running npm install
or yarn
After a minute or so, your app dependencies will be ready to go, so now we can create a configuration file which will hold all of your sensitive information so it can be used in your app, but without placing it directly in your code. This is called an environment variable file.
Create it by running touch .env
and then open it up for editing in your favorite code editor.
There are four different variables that we’ll want to define in this file: the access token provided by Infura which provides access to the Ethereum node, your public wallet address, your wallet private key, and the destination wallet address.
INFURA_ACCESS_TOKEN=xxxxxx
# Rinkeby Testnet
WALLET_ADDRESS=xxxxxx
WALLET_PRIVATE_KEY=xxxxxx
# Destination
DESTINATION_WALLET_ADDRESS=xxxxxx
You’ll want to fill this file in with your own unique tokens and keys. You don’t need to place the values in quotes or anything, you can just paste them right after the = sign
Now, we are ready to switch over to the index.js
file and start coding. Let’s start by requiring the .env file that we just created:
require('dotenv').config()
We’ll also need to require a few other dependencies that we’ll be using along the way, including the Ethereum Web3.js library.
const Web3 = require('web3')
const axios = require('axios')
const EthereumTx = require('ethereumjs-tx')
const log = require('ololog').configure({ time: true })
const ansi = require('ansicolor').nice
Cool! We’re ready to define the network that we’d like to connect to. We’re going to use Infura to connect to the test net, so let’s save the testnet url to a variable and append your testnet access token. You can do that like so:
const testnet = `https://rinkeby.infura.io/${process.env.INFURA_ACCESS_TOKEN}`
Note that all of your environment variables that were defined in the .env file are now accessible in the process.env
object.
Web3 will need to know the link to the network that we’re trying to connect to, so let’s set that up now:
const web3 = new Web3( new Web3.providers.HttpProvider(testnet) )
We can also tell Web3 what our public wallet address is so that Web3 knows to always reference that wallet address during future interactions:
web3.eth.defaultAccount = process.env.WALLET_ADDRESS
Great, now let’s also define the amount of ETH that we’d like to send in this transaction. I’m going to use a small amount to start:
const amountToSend = 0.001
That’s about it for the configuration! Let’s write the main function that will execute whenever we run the Node.js program.
const main = async () => {
// our code goes here
};
main()
At this point, your code should look something like this:
require('dotenv').config()
const Web3 = require('web3')
const axios = require('axios')
const EthereumTx = require('ethereumjs-tx')
const log = require('ololog').configure({ time: true })
const ansi = require('ansicolor').nice
const testnet = `https://rinkeby.infura.io/${process.env.INFURA_ACCESS_TOKEN}`
const web3 = new Web3( new Web3.providers.HttpProvider(networkToUse) )
web3.eth.defaultAccount = process.env.WALLET_ADDRESS
const amountToSend = 0.00100000
const main = async () => {
// our code goes here
}
main()
Let’s define what it is we’re looking to do here.
- Find out what the balance of our wallet is to make sure we can afford to send the defined amount.
- Get the nonce to use for each individual transaction.
- Find the current gas prices required to power our transaction through the network.
- Build the transaction and sign it using our wallet private key
- Submit the transaction and view the details on Etherscan.
That sounds like a good summary to me. Let’s start by using Web3 to fetch the current balance of your wallet.
let myBalanceWei = web3.eth.getBalance(web3.eth.defaultAccount).toNumber()
let myBalance = web3.fromWei(myBalanceWei, 'ether')
log(`Your wallet balance is currently ${myBalance} ETH`.green)
Now, let’s figure out the nonce to use in association with this transaction. A nonce (Number used Once) is used to help make sure this transaction cannot be replicated again.
let nonce = web3.eth.getTransactionCount(web3.eth.defaultAccount)
log(`The outgoing transaction count for your wallet address is: ${nonce}`.magenta)
We’ll have to write a quick async function in order to fetch the latest gas prices for the Ethereum network. I’m going to use the API provided by https://ethgasstation.info
/**
* Fetch the current transaction gas prices from https://ethgasstation.info/
*
* @return {object} Gas prices at different priorities
*/
const getCurrentGasPrices = async () => {
let response = await axios.get('https://ethgasstation.info/json/ethgasAPI.json')
let prices = {
low: response.data.safeLow / 10,
medium: response.data.average / 10,
high: response.data.fast / 10
}
console.log("\r\n")
log (`Current ETH Gas Prices (in GWEI):`.cyan)
console.log("\r\n")
log(`Low: ${prices.low} (transaction completes in < 30 minutes)`.green)
log(`Standard: ${prices.medium} (transaction completes in < 5 minutes)`.yellow)
log(`Fast: ${prices.high} (transaction completes in < 2 minutes)`.red)
console.log("\r\n")
return prices
}
We can call this newly written method like so:
let gasPrices = await getCurrentGasPrices()
Time to build the transaction.
let details = {
"to": process.env.DESTINATION_WALLET_ADDRESS,
"value": web3.toHex( web3.toWei(amountToSend, 'ether') ),
"gas": 21000,
"gasPrice": gasPrices.low * 1000000000, // converts the gwei price to wei
"nonce": nonce,
"chainId": 4 // EIP 155 chainId - mainnet: 1, rinkeby: 4
}
const transaction = new EthereumTx(details)
Sign it:
transaction.sign( Buffer.from(process.env.WALLET_PRIVATE_KEY, 'hex') )
Serialize it so that we can send it to the Infura node.
const serializedTransaction = transaction.serialize()
We’re ready! Time to send the transaction to the network for confirmation.
const transactionId = web3.eth.sendRawTransaction('0x' + serializedTransaction.toString('hex') )
Now we’ll add just a few lines to inspect the results of the transaction submission and exit the program.
const url = `https://rinkeby.etherscan.io/tx/${transactionId}`
log(url.cyan)
log(`Note: please allow for 30 seconds before transaction appears on Etherscan`.magenta)
process.exit()
Here’s the whole file:
/**
* Require the credentials that you entered in the .env file
*/
require('dotenv').config()
const Web3 = require('web3')
const axios = require('axios')
const EthereumTx = require('ethereumjs-tx')
const log = require('ololog').configure({ time: true })
const ansi = require('ansicolor').nice
/**
* Network configuration
*/
const testnet = `https://rinkeby.infura.io/${process.env.INFURA_ACCESS_TOKEN}`
/**
* Change the provider that is passed to HttpProvider to `mainnet` for live transactions.
*/
const web3 = new Web3( new Web3.providers.HttpProvider(testnet) )
/**
* Set the web3 default account to use as your public wallet address
*/
web3.eth.defaultAccount = process.env.WALLET_ADDRESS
/**
* The amount of ETH you want to send in this transaction
* @type {Number}
*/
const amountToSend = 0.00100000
/**
* Fetch the current transaction gas prices from https://ethgasstation.info/
*
* @return {object} Gas prices at different priorities
*/
const getCurrentGasPrices = async () => {
let response = await axios.get('https://ethgasstation.info/json/ethgasAPI.json')
let prices = {
low: response.data.safeLow / 10,
medium: response.data.average / 10,
high: response.data.fast / 10
}
console.log("\r\n")
log (`Current ETH Gas Prices (in GWEI):`.cyan)
console.log("\r\n")
log(`Low: ${prices.low} (transaction completes in < 30 minutes)`.green)
log(`Standard: ${prices.medium} (transaction completes in < 5 minutes)`.yellow)
log(`Fast: ${prices.high} (transaction completes in < 2 minutes)`.red)
console.log("\r\n")
return prices
}
/**
* This is the process that will run when you execute the program.
*/
const main = async () => {
/**
* Fetch your personal wallet's balance
*/
let myBalanceWei = web3.eth.getBalance(web3.eth.defaultAccount).toNumber()
let myBalance = web3.fromWei(myBalanceWei, 'ether')
log(`Your wallet balance is currently ${myBalance} ETH`.green)
/**
* With every new transaction you send using a specific wallet address,
* you need to increase a nonce which is tied to the sender wallet.
*/
let nonce = web3.eth.getTransactionCount(web3.eth.defaultAccount)
log(`The outgoing transaction count for your wallet address is: ${nonce}`.magenta)
/**
* Fetch the current transaction gas prices from https://ethgasstation.info/
*/
let gasPrices = await getCurrentGasPrices()
/**
* Build a new transaction object and sign it locally.
*/
let details = {
"to": process.env.DESTINATION_WALLET_ADDRESS,
"value": web3.toHex( web3.toWei(amountToSend, 'ether') ),
"gas": 21000,
"gasPrice": gasPrices.low * 1000000000, // converts the gwei price to wei
"nonce": nonce,
"chainId": 4 // EIP 155 chainId - mainnet: 1, rinkeby: 4
}
const transaction = new EthereumTx(details)
/**
* This is where the transaction is authorized on your behalf.
* The private key is what unlocks your wallet.
*/
transaction.sign( Buffer.from(process.env.WALLET_PRIVATE_KEY, 'hex') )
/**
* Now, we'll compress the transaction info down into a transportable object.
*/
const serializedTransaction = transaction.serialize()
/**
* Note that the Web3 library is able to automatically determine the "from" address based on your private key.
*/
// const addr = transaction.from.toString('hex')
// log(`Based on your private key, your wallet address is ${addr}`)
/**
* We're ready! Submit the raw transaction details to the provider configured above.
*/
const transactionId = web3.eth.sendRawTransaction('0x' + serializedTransaction.toString('hex') )
/**
* We now know the transaction ID, so let's build the public Etherscan url where
* the transaction details can be viewed.
*/
const url = `https://rinkeby.etherscan.io/tx/${transactionId}`
log(url.cyan)
log(`Note: please allow for 30 seconds before transaction appears on Etherscan`.magenta)
process.exit()
}
main()
We’re ready to run the transaction! You can do that by entering the following command into your Terminal:
babel-node ./index.js
You should see some output in the Terminal that will provide some information about the program as it executes. Nice!
To go live, simply switch out your wallet information with a wallet that was generated on the Ethereum mainnet and use the mainnet url provided by Infura.
If this tutorial helps you make boatloads of magic internet money, remember your roots and don’t forget to give back: 0xDcd2E5A0641C1A50d4b2a79288C83896E9912b08