Blockchain 101 — Understanding Gas Fees

How gas fees work on blockchain networks and why they are important (with examples)

Colin Kraczkowsky
7 min readOct 8, 2021

On blockchain networks each node is incentivized to provide the processing power to add a new block to the chain by the promise of earning a reward. One such reward is a gas fee. A gas fee is paid per transaction in the network’s native currency by the blockchain address wishing to execute the transaction. Before we begin, I want to point out that as blockchain networks enter the mainstream it is easy to start viewing gas fees as a nuisance. We must keep in mind that they serve a very important purpose. First, gas fees help to pay for network security by rewarding the people keeping track of what is going on across the network. Without them, these people would be supporting the network at their own financial cost which would be unsustainable. Second, they incentivize people using the network to send transactions back and forth with purpose. If there was no personal investment in a transaction, it would be easy for anyone to spam the network with meaningless transactions threatening the stability of the network.

While the significance of gas fees should now be understood, the concept and how they work can seem convoluted especially when a transaction involves tokens that are not the native currency, e.g. ERC-20 tokens. To understand gas fees better, let’s look at some concrete examples.

We start by looking at gas fees for a transaction that directly interacts with the blockchain network. In this example, the blockchain will be Ethereum and the transaction is to deploy a smart contract containing a slimmed down ERC-20 token. To get a copy of this smart contract and to follow along, you can find the source code on my GitHub.

Our smart contract is written in the Solidity programming language, so we need to install a Solidity compiler to compile the file down into the necessary formats for deployment and execution.

$ npm install -g solc

To get the smart contract into a format readable by an Ethereum Virtual Machine (EVM), we first compile it down to bytecode which produces a .bin file. Next, so that other applications can interface with it, we compile the contract down to a binary interface which produces a .abi file.

$ solcjs — bin smart_contract_boilerplate.sol
$ solcjs — abi smart_contract_boilerplate.sol

We are ready to deploy our smart contract to the Ethereum blockchain. For the purposes of this example, we won’t deploy to the public blockchain. Instead, we spin up a local instance of Ethereum by opening up a terminal window then installing and launching a handy tool called Ganache. This will return a list of Ethereum addresses that we will use to send transactions.

$ npm install -g ganache-cli$ ganache-cli
Ganache CLI v6.12.2 (ganache-core: 2.13.2)
Available Accounts
==================
(0) 0x7Bb9a29cD5E4a88c8492AC18cd6d5Cf43aE6F63C (100 ETH)
(1) 0x9382422Ebc565CBdC9ec1f5A8e57147c114B3b5f (100 ETH)
(2) 0x52427A871d651741469c1B54E7897cea42a0a83F (100 ETH)
(3) 0x799F429f9C97573b5ad9Fb0D38a8e58dF0447569 (100 ETH)
(4) 0xB64085dF6cBF0236dC1768c7374a84470c495229 (100 ETH)
(5) 0xaE391A299Bd7CCa8d257f00978ea2422BE8b8fD0 (100 ETH)
(6) 0x1575Cbe396060f4F9085969E4993A1487c6c0364 (100 ETH)
(7) 0xa2A11Ec65234B57B7f14Ad28F06b486A48408893 (100 ETH)
(8) 0x506dE4A73e9BAd4fDBa3ce1fE43571F938247F50 (100 ETH)
(9) 0xEB3c794F04877fF2E5E21353d19cAE345d255ce1 (100 ETH)
Private Keys
==================
(0) 0xd5cc3503bdda975383f1bba1eb842e7da98a15c849c736c11d0cc0279c342067
(1) 0x9179bd1331132904063196a0b2e028627027ebe1f7769046cf4bbb4dd7bb1fca
(2) 0xbda163776ffe870178a10cc6f7ea00f7d98a101d9c636ff8cdc18a4d6d5b38d8
(3) 0xdc714e72b3aa8fc08074a069510cb228b615af3205fae6cd65e1227e4c9fa95f
(4) 0x3da26541d7517a06ccdbb2ad826ecac0fb767be571e869d065d9fe96d2ddbfd6
(5) 0xce6aa6e6f1e5b3e8951af81269f84521a66f7786410064cce2e65478589ddbde
(6) 0x9c8d09c9fe83d572c7d09180b22d4ee0a993308a1cd447d7ef67f918692149ee
(7) 0x52d56ba800c23a2455d6121cc9be313890961bab5a531fc4ff7161292ed00e34
(8) 0x7c8633bd2a48084757005f3cacb65f5bdf5739d9ad88ed8c64bafa78a3fbce6c
(9) 0xb45eccb005553904a4fac5530eb55e764d4f4b11c4b4e2e0cf3987e38f6dedbf
HD Wallet
==================
Mnemonic: crisp holiday club trap bridge cattle nominee stadium neck roof victory company
Base HD Path: m/44'/60'/0'/0/{account_index}Gas Price
==================
20000000000
Gas Limit
==================
6721975
Call Gas Limit
==================
9007199254740991
Listening on 127.0.0.1:8545

Next, we need a way to interact with our local Ethereum blockchain. As I am most comfortable in a Node.js environment I chose to install and use the web3 library. Let’s open a new terminal window and launch a Node.js environment.

$ npm install web3$ node
var Web3 = require(‘web3’);
// point to the location of the host running our local Ethereum instance
const web3 = new Web3(‘http://127.0.0.1:8545');
// verify that everything is working by grabbing the balance of one of the Ethereum addresses provided by Ganache
web3.eth.getBalance(‘0x7Bb9a29cD5E4a88c8492AC18cd6d5Cf43aE6F63C’).then(console.log); > 100000000000000000000

Now we are ready to deploy our smart contract. This deployment involves sending a transaction to the blockchain from an Ethereum address. Let’s use the address for which we got the balance in the previous step above.

$ node
bytecode = fs.readFileSync(‘smart_contract_boilerplate_sol_BasicSmartContract.bin’).toString();
abi = JSON.parse(fs.readFileSync(‘smart_contract_boilerplate_sol_BasicSmartContract.abi’).toString());var contract = new web3.eth.Contract(abi);contract.deploy({data: bytecode}).send({from: ‘0x7Bb9a29cD5E4a88c8492AC18cd6d5Cf43aE6F63C’, gas: 1500000, gasPrice: web3.utils.toWei(‘0.00003’, ‘ether’)}).then((contractInstance) => { contract.options.address = contractInstance.options.address});

The result of the deployment is a transaction being added to the mempool to eventually be included in a new block that will be added to the blockchain. Since we are using a local Ethereum blockchain, this new block will be added immediately. Back in the terminal window where Ganache is running, we can see this new block and the address of our freshly deployed smart contract.

eth_sendTransactionTransaction: 0x159d4dff8dee06a1836ac8ac767a53a200ed132dd9dd85363b6deb38c7620ce2
Contract created: 0x2a68bff8a231ed4dcad176bdfd2fdc5e648d2e0c
Gas usage: 168511
Block Number: 1
Block Time: Wed Sep 29 2021 10:34:24 GMT-0400 (Eastern Daylight Time)

Back in the terminal running our Node.js environment, we see that the gas fee has been deducted from the address that sent the transaction.

web3.eth.getBalance(‘0x7Bb9a29cD5E4a88c8492AC18cd6d5Cf43aE6F63C’).then(console.log);
> 94944670000000000000

In the deployment of our smart contract, we proposed a gasPrice of 30000000000000 Wei (0.00003 ETH) for the transaction. So we paid 0.00003 ETH per unit of gas and, as we can see in Ganache, the transaction used 168511 units of gas so the total price for the transaction was 5055330000000000000 Wei (5.05533 ETH) which was deducted for our sending address which is why we now see a balance of 94944670000000000000 Wei (94.94467 ETH) at that address.

Alright so we have seen how gas fees work for a transaction that directly interacts with the blockchain network. Next, we look at a transaction that interacts indirectly, e.g. when transacting with ERC-20 tokens like UNI, USDT, USDC, SUSHI, AAVE, OMG, ZRX, MKR, REP, GNT, LINK, LRC, etc. At their core, all ERC-20 tokens are smart contracts running on the Ethereum network; the 0x network has a good ERC-20 template to understand how these tokens function.

So let’s say that I am Basecoin: an exchange for the purchase and sale of tokens. I have a user named Alice holding 100 USDC at Basecoin. Alice now wants to transfer 10 USDC to her account at Trinance, another exchange, perhaps because they can offer her a higher annual percentage yield (APY) for holding small amounts of USDC. When Alice issues that transfer, she will be charged ETH. Why is this so?

Under the covers, Alice has two addresses on the Ethereum network: 0x9382422Ebc565CBdC9ec1f5A8e57147c114B3b5f is her address at Basecoin and 0x52427A871d651741469c1B54E7897cea42a0a83F is her address at Trinance. Because USDC is an ERC-20 token managed by a smart contract, when we call the smart contract to retrieve the balance it recognizes that at address 0x9382422Ebc565CBdC9ec1f5A8e57147c114B3b5f there is a balance of 100 USDC. When Alice makes the transfer, she is sending a transaction to the Ethereum network calling that smart contract to deduct 10 USDC from her balance at her Basecoin address and to add 10 USDC to her Trinance address. Because this transaction will need to be included in a new block, it requires payment in ETH to perform which, as we saw above, is deducted from the ETH balance of the sending address. The price of that transaction is determined by how high a gas fee the caller contract is willing to pay (a higher gas fee results in quicker processing of the transaction, e.g. at time of writing ETH Gas Station recommends 0.000000079 ETH to process a transaction in less than 2 minutes and 0.000000064 ETH to process it in under 30 minutes).

We can see this in action by returning to our deployed contract for our slimmed down ERC-20 token. As our smart contract is representative of the smart contract governing the USDC token, we’ll refer to this token as “pseudo-USDC”.

// get ETH balance of Alice’s Basecoin address
web3.eth.getBalance(‘0x9382422Ebc565CBdC9ec1f5A8e57147c114B3b5f’).then(console.log);
> 100000000000000000000
// get pseudo-USDC balance of Alice’s address at Basecoin
contract.methods.balanceOf(‘0x9382422Ebc565CBdC9ec1f5A8e57147c114B3b5f’).call().then(console.log);
> 100
// get pseudo-USDC balance of Alice’s address at Trinance
contract.methods.balanceOf(‘0x52427A871d651741469c1B54E7897cea42a0a83F’).call().then(console.log);
> 0
// transfer 10 tokens from Alice’s Basecoin address to her Trinance address
contract.methods.transfer(‘0x52427A871d651741469c1B54E7897cea42a0a83F’, 10).send({from:’0x9382422Ebc565CBdC9ec1f5A8e57147c114B3b5f’, gas:1500000, gasPrice:web3.utils.toWei(‘0.00003’, ‘ether’)});

As discussed above, this transaction to change the balance at these two addresses adds a new block to the blockchain.

Transaction: 0xc4ab359ce8334880a3764c01135d46c98055c539c06518b9787098c1a3e23f40
Gas usage: 37532
Block Number: 4
Block Time: Wed Sep 29 2021 13:10:54 GMT-0400 (Eastern Daylight Time)

Let’s observe how this affected our ETH balance at Alice’s Basecoin address and her pseudo-USDC balances at her Basecoin and Trinance addresses.

web3.eth.getBalance(‘0x9382422Ebc565CBdC9ec1f5A8e57147c114B3b5f’).then(console.log);
> 98874040000000000000
contract.methods.balanceOf(‘0x9382422Ebc565CBdC9ec1f5A8e57147c114B3b5f’).call().then(console.log);
> 90
contract.methods.balanceOf(‘0x52427A871d651741469c1B54E7897cea42a0a83F’).call().then(console.log);
> 10

As we can see, 1125960000000000000 Wei (1.12596 ETH) was deducted from Alice’s address at Basecoin such that her ETH balance at that address is now 98874040000000000000 Wei (98.87404 ETH). That amount was determined as a calculation of the 37532 units of gas that was used and the 30000000000000 Wei (0.00003 ETH) gasPrice that we were willing to pay which resulted in a total gas fee of 1125960000000000000 Wei (1.12596 ETH). In addition, her pseudo-USDC balance at that address has been deducted 10 pseudo-USDC and her balance at her address at Trinance has increased by 10 pseudo-USDC.

Having worked through this exercise, we should now understand more about the importance of gas fees for the security, sustainability, and validity of a blockchain network and how they are manifested by the network. We saw that gas fees are used to pay nodes to include our transactions in new blocks on the chain. These transactions may be direct interactions with the blockchain, e.g. deploying a smart contract, or indirect interactions, e.g. calling a smart contract to transfer tokens between addresses. In either case, the transaction is usually paid in the native currency of the blockchain, e.g. ETH on Ethereum, and the fee is deducted from the address sending the transaction.

--

--

Colin Kraczkowsky

Problem solver wielding JavaScript and Solidity as my tools. Scholar of the newly possible.