dApps on Ethereum have grown in complexity, depth, and breadth in the past few years. One missing piece is the efficient querying of historical block information. With standard Ethereum JSON-RPC methods, developers either need to maintain centralized databases containing large swaths of the blockchain history, or repeatedly query blocks across long time periods in order to scrap the entire transaction history of a particular address. These two options complicate the process in which users like wallet providers supply information such as the history of a particular user's interactions on the blockchain. Without easy access to this information, developers must rely on expensive, slow methods that limit the feature set of their apps.
While building historical queries into dApps has traditionally been complicated, time-consuming, and error-prone, the Alchemy Transfers API allows for developers to query historical wallet activity, token transfers, and other account-driven transactions in a simple transaction. Â
In this tutorial, weâll look at an example of how, with just a few lines of code, your dApp can integrate the power of the Alchemy Transfers API.
Our Example
For our pre-packaged example, weâll create a dApp dashboard that tracks transaction activity for a particular address, processes it in the backend, and then pushes it in real-time to the frontend.
Problem Statement: đ
Instead of sending burnt tokens to 0xdead, which is recommended for most token burning, creators of Akita Inu (AKITA) and Shiba Inu (SHIBA) chose to gift Vitalik Buterin with large swaths of their dog-themed tokens. However, rather than leaving the meme tokens untouched, Vitalk instead chose to sell tokens in batches to Uniswap, swapping them for ETH, and donated both the ETH proceeds and the rest of the tokens that could not be sold to a whole host of charities. Check out this article for more info.
One of the lucky recipients of this windfall was the Gitcoin community multisig account (multisig stands for multi-signature, which is a specific type of digital signature that makes it possible for two or more users to sign in to access / control the funds in a single wallet address). With the market valuation of the AKITA token transfer at ~$450 million, no single market would be able to absorb a single sale of the tokens, and if the multisig attempted to do so, the price of AKITA would plummet. Â As such, the Gitcoin community decided to implement a token ârescueâ process that would burn 13 AKITA tokens for every 1 AKITA sold off to the open market. Â
Our example dashboard follows along with the story of AKITA and its rescue process by tracking the total number of AKITA tokens held in the Gitcoin multisig and the total number of tokens that have been burnt as part of the rescue contract. Â
The dashboard that we create performs two functions. Â Upon refresh of page or user click, the webapp fires off a request to Alchemy, querying the Gitcoin multisig address and the ârescueâ smart contract. Â After receiving the response, the dashboard parses the JSON object and processes it. Â Ultimately, the website frontend displays the processed items.
-- CODE language-js line-numbers --
git clone https://github.com/alchemyplatform/Alchemy-Transfers-Tutorial
cd Alchemy-Transfers-Tutorial
b) Install Heroku-CLI and verify/install dependencies
In this tutorial, we utilize Heroku for hosting a server and website; if you choose to use Heroku, be sure to follow all of the following steps. If you want to use another provider, see Option 2: Build Project From Scratch
Download Heroku-CLI based on your OS. Â Make sure you download the correct version based on what kind of computer environment you are using!
After installation, navigate into the file that you just git cloned and run the following command in your command line to login to your Heroku account.
-- CODE language-js line-numbers --
heroku login
Follow the commands to login into your Heroku account. If you don't have a Heroku account, you can sign up for one for free!
Let's confirm that you have downloaded the correct version of Node. In your command line run:
After running the command, you will either see a version number appear or you will instead get an error message telling you that you do not have Node installed. Â
Note that Heroku requires users to have any version of Node greater than 10 installed. If you donât have it or have an older version, install a more recent version of Node.
Lastly, let's confirm that we also have npm installed properly.
Again in your command line, run the following command:
fromBlock:0xC30965 is the block that the contract (0x3301Ee63Fb29F863f2333Bd4466acb46CD8323E6) was deployed in, we don't need to look in any earlier blocks there would not be any activity.
toBlock:latest the most recent block, we want all transaction activity
fromAddress:0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6 is the Gitcoin multisig address
toAddress:0xDead000000000000000000000000000000000d06 is the address used for burning tokens
contractAddresses:0x3301Ee63Fb29F863f2333Bd4466acb46CD8323E6 contract where the Akita rescue logic is stored
Navigate to the main.py to find where your API key is being used. Â Note that to get an Alchemy API key you will need to create an App in the Alchemy dashboard.
NOTE: For this tutorial, we require using an app that is pointed towards the Ethereum mainnet since our contract is deployed on mainnet.
Our Heroku app is configured to accept the Alcehmy API key as an environment variable to encourage safe, best practices. Â Once you get your API key after creating an account, run the following command in the same file directory that you previously ran heroku create to set your environment variables within Heroku itself.
You've set up your API key! To confirm that it is properly configured, you can view environment variables on Heroku with: heroku config
Your Heroku environment variables should look similar to this:
5. Deploy Heroku App!
Now, we're in the final steps! Â Confirm that you are navigated to the file that your Heroku project lives within. Once there, run the following commands to save your changes on Git and deploy the app.
-- CODE language-js line-numbers --
git add . // to add changes
git commit -m "added Alchemy keys" // to add a comment
git push heroku master // to push and deploy your heroku app
With that, we have pushed all changes to Heroku and are now able to view our dashboard live. Open the Heroku app at the URL that your project has been pushed to. Â
Note that upon refreshing the page, we see that the dashboard's data is updated. Â Likewise, if we click on the "Refresh Data" button, we find that the balances displayed also change if someone has interacted with the rescue contract.
And now, with everything in place, you can test out your dApp! Â
đ Congratulations on your dApp deployment! Feel free to edit your app, point the target address at other interesting contracts / public figures, or make the frontend more spiffy!
Option 2: Build the dApp From Scratch
In this tutorial, we provide a generalized setup for a Python webapp that allows you to query the Alchemy Transfers API, process the JSON response, and then push the data to the frontend.
1-2. Complete Steps 2 & 3 from the Heroku Project.
3. Configure your App with your Alchemy API Key
To follow safe, best practices, we encourage you to store your API key in an .env file. Taking the key that we generated from the previous step, create and .env file with the following contents:
HINT: You'll need to install the dotenv package if you haven't already. Follow these steps to learn more.
-- CODE language-js line-numbers --
KEY=""
Congrats! You've set up your API key! Â
4. Create Backend Processing Script
For this tutorial, we use Python / React for sending our API requests, decoding the JSON response, and for processing it.
a) Install necessary dependencies
Our tutorial is primarily built with Python and we use Flask to power our app. Make sure that you have the follow dependencies in your environment to follow along.
One easy way to install the necessary dependencies is to create a file named requirements.txt with the following items inside of it.
To call balanceOf to get the current balance of an ERC20 token, we need an additional piece of code on top of what we would normally call in Web3 to get the Ethereum balance of an address. To do that we'll add the following function in main.py:
-- CODE language-js line-numbers --
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.alchemyapi.io/v2/'+ALCHEMY_KEY))
# includes the standard ERC20 ABI info
ERC20_ABI = json.loads('[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}]') # noqa: 501
akita = w3.eth.contract(address=AKITA_ADDRESS, abi=ERC20_ABI)
def get_gtc_akita_bal():
return str(akita.functions.balanceOf(GITCOIN_ADDRESS).call())
Note that we include the ERC20 ABI in this code snippet in order to properly read the AKITA balance of the Gitcoin multisig wallet.The Contract Application Binary Interface (ABI) is the standard way to interact with contracts in the Ethereum ecosystem and we use the ERC20 ABI to interact with ERC20 contracts such as the AKITA contract.
d) Define Alchemy Transfer function
Here, we use the Alchemy specific method alchemy_getAssetTransfers to get the total number of AKITA tokens burned by the Gitcoin contract.
To understand the parameter breakdown, read more above.
NOTE: We parse our JSON response by using the imported Python package json to help us easily sort through the response and filter for the desired fields. To get a better idea of what this response looks like raw, try the Alchemy Composer App or try executing an Alchemy API call from Postman.
e) Configure Flask routing
To ensure that "Refresh Data" command from the frontend button triggers the execution of the 3 functions defined above, we need to configure our flask to handle this logic! Add the following to your main.py file:
Whenever the "Refresh Data" button is triggered, we fire off a request to the Alchemy API which in turn gives us our updated data. Note that lines 7-10 and 14-18 are duplicated to ensure that our website refreshes its data whenever we first load in as we visit it, whenever someone refreshes the site, or when a user clicks the "Refresh Data" button.
Once we are able to get the most updated values from each of the 3 functions, we can then pass this data into our render_template function which pushes the new information to the frontend.
Our script is ready! Here is the entire sample main.py we have created together:
-- CODE language-js line-numbers --
from flask import Flask, jsonify, render_template, request
from forms import DataTriggerForm
import os
import json
from web3 import Web3
import requests
ALCHEMY_KEY = os.environ.get('KEY')
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.alchemyapi.io/v2/'+ALCHEMY_KEY))
# includes the standard ERC20 ABI info
ERC20_ABI = json.loads('[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}]') # noqa: 501
# configures web3 to point towards the AKITA token address
AKITA_ADDRESS = '0x3301Ee63Fb29F863f2333Bd4466acb46CD8323E6'
GITCOIN_ADDRESS = '0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6'
akita = w3.eth.contract(address=AKITA_ADDRESS, abi=ERC20_ABI)
app = Flask(__name__)
SECRET_KEY = os.urandom(32)
app.config['SECRET_KEY'] = SECRET_KEY
def get_block_num():
return str(w3.eth.get_block('latest')['number'])
def get_gtc_akita_bal():
return str(akita.functions.balanceOf(GITCOIN_ADDRESS).call())
def get_total_burn():
total_burn = requests.post('https://eth-mainnet.alchemyapi.io/v2/'+ALCHEMY_KEY, json={"jsonrpc": "2.0","id": 0,"method": "alchemy_getAssetTransfers","params": [{"fromBlock": "0xC30965","toBlock": "latest","fromAddress": "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6","toAddress": "0xDead000000000000000000000000000000000d06","contractAddresses": ["0x3301Ee63Fb29F863f2333Bd4466acb46CD8323E6"],"category": ["external","token"]}]})
json_response = total_burn.json()
transfer_nums = len(json_response['result']['transfers'])
burned = 0
for i in range(transfer_nums):
burned = burned + json_response['result']['transfers'][i]['value']
return burned
@app.route('/', methods=['GET', 'POST'])
def refresh():
#num1 = None
block_num = get_block_num()
balance = get_gtc_akita_bal()
total_burn = get_total_burn()
form = DataTriggerForm()
if request.method == 'POST':
#num1 = form.num1.data
block_num = get_block_num()
balance = get_gtc_akita_bal()
total_burn = get_total_burn()
return render_template('index.html', form=form, bal=balance, block_num=block_num, total_burn=total_burn)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Note that one of the import statements in our previous Python file refers to _DataTriggerForm_ in a file namedforms.py This piece of code effects the text that appears on our frontend button and contains a default form from Flask.
Include below is the code for _DataTriggerForm _which lives in our forms.py file.
-- CODE language-js line-numbers --
from flask_wtf import FlaskForm
from wtforms import SubmitField, IntegerField
class DataTriggerForm(FlaskForm):
submit = SubmitField('Refresh Data!')
5. Create dApp Dashboard Frontend
With our Python script ready, weâll now build our dashboard. Our example client is extremely simple. Of course, youâll want to integrate this code into your dApp as appropriate with a different target address and different graphics!
Our dashboard is a simple HTML page that displays any information that was processed by the Python scripts in the background.
a) Create your index.html file
This is the file where we will store all of our frontend code.
b) Create "Refresh Data" Button
-- CODE language-js line-numbers --
This piece of code "POSTS" a trigger to the Python script, we created previously. This lets us know when the user has click the "Refresh Data" button and executes the Python code.
c) Create UI Elements that our Python Script Can Push Data To
-- CODE language-js line-numbers --
Block Number: {{ block_num }}
Gitcoin Akita Balance: {{ bal }}
Akita Rescued: {{ total_burn }}
This is only a small number of HTML elements that we can add to frontend of our dashboard. Feel free to change up the UI that we included below; here's the sample index.html file that we just built together (with some liberty taken for graphics / UI)
-- CODE language-js line-numbers --
Akita Rescue
Akita đ Rescue đ
A dashboard that tracks the "rescue" of the AKITA tokens in the Gitcoin multi-sig. For every purchase of 1 AKITA, 13 are burnt from Gitcoin.
ETH from the sale lands back in the Gitcoin multi-sig and will be used for the ETH side of an ETH/AKITA LBP.
For more info about Vitalik's donation of AKITA tokens to Gitcoin, check out this blog post.
After deploying this webapp in your desired environment, it should look like the following!
This is a simple example, but there are many ways you can expand on this to build dashboards and dApp for your users.
Fork đ´, build đď¸, and design đoff this repo!
Conclusion
Blockchain has evolved quickly, but not all developer features are easy or intuitive to use. In particular, the querying of historical transaction information is something that has plagued development. However, with the Alchemy Transfers API, your users can stay informed and confident about understanding their transaction activity both in the present and the past!