How to Track Mined and Pending Ethereum Transactions
Learn how you can track your transactions thorough the pending and mined states. We integrate Twilio to send SMS notifications when a transaction is pending and when it finally gets mined.
Ethereum blockchain does not natively support Notifications. For a user, that means every time your transaction gets submitted or completed, there's no way for you to stay updated of its progress other than constantly refreshing your app or etherscan. For a dApp, that means a bad User Experience (UX).
dapp notifications example
Using Alchemy Notify and Alchemy's pending transaction WebSocket, dApps can monitor activity and send real-time push notifications to their users. This leads to a much better UX and in today's market, can even be your competitive advantage. In this tutorial, we will show you how to send SMS notifications for any activity throughout the lifecycle of a transaction.

Overview

Our Example

For this example, we’ll be creating a notification system that
  • automatically detects a user's address activity
  • sends an SMS on phone whenever a pending transaction has been made from their address
  • sends an SMS on phone once that transaction has been mined
The code mainly consists of the below scripts
NOTE: For Dev and Testing purposes, you can use an Ethereum Testnet, like Goerli, which saves you from spending any real ETH β›½! For access to free Goerli ETH, use the Alchemy Goerli faucet.

Pending transactions notification workflow

This code for this is in the file sniffer.py
  1. 1.
    User initiates a transaction on MetaMask
  2. 2.
    Pending transaction is picked up by Alchemy
  3. 3.
    WebSocket script receives pending transaction
  4. 4.
    Script sends SMS notification

Mined transactions notification workflow

The code for this is in the file app.py
  1. 1.
    A pending transaction initiated by the user has been made and is confirmed by a miner
  2. 2.
    Alchemy Notify API picks up the mined transaction
  3. 3.
    WebHook (Alchemy notify) endpoint is notified of the transaction
  4. 4.
    Script sends SMS notification
We’ll go through two versions of the tutorial: the first by cloning the Github Repo using Heroku and the second by doing it all from scratch.

Option 1: Build Heroku-Serviced Project

1. Set Up Github Repo & Heroku

a) Clone the existing Github Repository​

Navigate to your command line and type:
1
git clone https://github.com/alchemyplatform/Transaction-Lifecycle-via-SMS.git
2
cd Transaction-Lifecycle-via-SMS
Copied!

b) Install Heroku-CLI and verify/install dependencies

In this tutorial, we use Heroku for hosting a server and website. If you want to use another provider, see Option 2: Build Project From Scratch​
  1. 1.
    Download the right Heroku-CLI based on your OS and configuration
https://devcenter.heroku.com/articles/heroku-cli#download-and-install
2. In the folder that you just git cloned, run the below command
1
heroku login
Copied!
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!
3. Let's confirm that you have downloaded the correct version of Node. In your command line run:
1
node --version
Copied!
Note that Heroku requires a Node version of greater than 10. If you don’t have it or have an older version, install a more recent version of Node.
4. Lastly, let's confirm that we also have npm installed properly. Again in your command line, run the following command:
1
npm --version
Copied!
npm is installed with Node, so check that it’s there. If you don’t have it, install a more recent version of Node.

c) Initiate Heroku

Now, create our Heroku app by running the following command:
1
heroku create
Copied!
Make sure you take note of the URL that pops up http://xxxxxxxxx.herokuapp.com/. We'll be using it later in the tutorial!
NOTE: For more detailed instructions on setting up your environment for Heroku, check out the official Heroku docs.

d) Create a Twilio account

Twilio is an online SMS provider that allows users to send text messages via the Internet.
If you are new to Twilio, sign up for a trial account. With your trial account, you'll have enough credits to power your SMS notifications! Once you've signed up, head over to your Console and grab the below details from the account info section
  • Account SID
  • Auth Token
  • Phone Number
You'll need to plug these values in the code when using the Twilio API.
Note that sending messages through Twilio requires a Twilio phone number with SMS capabilities. If you don’t currently own a Twilio phone number with SMS capabilities (i.e. if the Phone Number doesn't show up in the above section), you’ll need to buy one with your provided credits. Please proceed to buy one from the Buy a Number page on Twilio.

2. Alchemy Notify API & Register Webhook Notifications

First, let’s look at how notifications with Alchemy work. There are two ways to create and modify notifications: through the Alchemy Notify dashboard, and through the Alchemy Notify API. For our example, we’ll only be using the dashboard.
NOTE: If you don’t already have one, you’ll first need to create an account on Alchemy. The free version will work fine for getting started.
Once you have an account, go to the dashboard and select β€œNotify” from the header section. Here you’ll see the different kinds of notifications you can set up:
  • Address Activity
  • Dropped Transactions
  • Mined Transaction
  • Gas Price
For our example, we’ll use the Address Activity notification, but you should be able to easily swap out any of the others for your own use case.
NOTE: We use "Address Activity" and not "Mined Transaction Notifications" in this example since the "Mined Transaction" webhook only picks up on mined transactions made through the Alchemy API. "Address Activity" allows us to read all transactions via a user-defined address as long as it is posted onto the Ethereum blockchain.
In the dashboard
  • Click on 'Create Webhook' under Address activity
  • Add Webhook URL to receive notification on
    • If using Heroku, pick up the http://xxxxxxxxx.herokuapp.com/ URL from Step 1
    • If not using Heroku, use the custom URL from your provider
  • Add the addresses you want to monitor. For this tutorial, please use your own address.
  • Select "Ethereum" under Chain dropdown and "Goerli" under Network.
  • Click on Create Webhook and we're done!

3. Using alchemy_pendingTransactions to track pending transactions

Assuming you've created the account, we will now use alchemy_pendingTransactions method which allows you to receive notifications on pending asset transfers for an address.
For this tutorial, we make use of Alchemy's WebSockets to avoid making requests continuously when you want specific information. WebSockets maintain a network connection for you and listen for changes.
To get an Alchemy API key for WebSockets, please create an App in the Alchemy dashboard.
NOTE: When you copy your key from the dashboard you should get a full url like this:
Your key is just the last portion in the URL:
Make the following changes to the file sniffer.py
  • On line 10, replace <TWILIO SID> with the Twilio SID you grabbed in step 1(d)
  • On line 11, replace <TWILIO AUTH TOKEN> with the Auth token you grabbed in step 1(d)
  • On line 13, replace <ALCHEMY KEY> with the Alchemy key from your dashboard
  • On line 25, replace the address in the parameters with the Ethereum Address you want to monitor
  • On line 50, replace the from number to the Twilio Number you copied in step 1(d)
  • On line 51, replace with your own phone number (with country code) that you want to receive the SMS on
1
from websocket import create_connection
2
​
3
ALCHEMY_KEY = "<Alchemy Key>"
4
​
5
for i in range(3):
6
try:
7
ws = create_connection("wss://eth-rinkeby.alchemyapi.io/v2/"+ALCHEMY_KEY)
8
print("Connection made")
9
except Exception as error:
10
print('Connection Error: ' + repr(error))
11
time.sleep(3)
12
else:
13
break
14
​
15
ws.send(json.dumps({"jsonrpc":"2.0","method":"eth_subscribe","params":["alchemy_filteredNewFullPendingTransactions", {"toAddress": "0xcF3A24407aae7c87bd800c47928C5F20Cd4764D2"}],"id":1}))
16
print("JSON eth_subscribe sent")
Copied!
In this code snippet, we embed our wss connection in a for loop that runs three times to help ensure that our WebSocket is properly connected upon script initiation. This ensures a more stable WebSocket connection. Configure below retry logic to suit your needs!
1
for i in range(3):
2
try:
3
ws = create_connection("wss://eth-rinkeby.alchemyapi.io/v2/"+ALCHEMY_KEY)
4
print("Connection made")
5
except Exception as error:
6
print('Connection Error: ' + repr(error))
7
time.sleep(3)
8
else:
9
break
Copied!

4. Configure SMS notifications

Make the following changes to the app.py file
  • On line 16, replace <TWILIO SID> with the Twilio SID you grabbed in step 1(d)
  • On line 17, replace <TWILIO AUTH TOKEN> with the Auth token you grabbed in step 1(d)
  • On line 52, replace the from number to the Twilio Number you copied in step 1(d)
  • On line 52, replace with your actual phone number (with country code) that you want to receive the SMS on
Make the SID and Auth Token changes in the below section of your code
1
# Find your Account SID and Auth Token at twilio.com/console
2
# and set the environment variables. See http://twil.io/secure
3
​
4
account_sid = '<TWILIO SID>'
5
auth_token = '<TWILIO AUTH TOKEN>'
6
client = Client(account_sid, auth_token)
Copied!
NOTE: If you are hosting a webapp on cloud computing services and plan to use environment variables, different computing environments have different ways of storing these variables.
Make the phone number changes in the below section of your code
1
message = client.messages \
2
.create(
3
body="\n \n PENDING TX! \n\n From: " + from_address + " \n\n To: " + to_address + "\n\n @tx:" + hash,
4
from_='+14435267244',
5
to='+14158230041'
6
)
Copied!

5. Deploy Heroku App!

Now, we're in the final steps! In the root folder of your Heroku project, run the following commands to save your changes on Git and deploy the app.
1
git add . // to add changes
2
git commit -m "added Alchemy / Twilio keys" // to add a comment
3
git push heroku master // to push and deploy your heroku app
Copied!
With that, we have pushed all changes to Heroku and our app is live!
NOTE: This app has no frontend and is configured for use on a server.
You can view the logs of your Heroku app by logging in to Heroku and navigating to the logs for your deployed app.
The log should look like the following! Both a worker and web file should be running.
And now, with everything in place, you can test out your dApp!
An alternate way to check your heroku logs is to go to your terminal, type the command heroku logs -t and hit enter
​
You can also use this command or the above dashboard to debug any errors with your heroku deployment
πŸŽ‰ Congratulations on your dApp deployment! Feel free to edit your webapp, point the target address at other interesting contracts / public figures, or make improvements to this transaction life cycle tracker!

Option 2: Build project from scratch

1-2. Complete Steps 1-2 from the Heroku Project.

3. Create WebSocket Connection

For this tutorial, we also make use of Alchemy's WebSockets, that maintains a continuous network connection for you and listens for changes, alerting you in real-time.
For more details on WebSockets vs HTTP requests, use this for reference.

a) Install dependencies

To create a WebSocket connection, we use a Python client to help simplify our build. Also, make sure that you have the following dependencies in your environment to follow along.
  • Create a file named requirements.txt and copy-paste the below in it.
1
aiohttp==3.7.4.post0
2
async-timeout==3.0.1
3
attrs==21.2.0
4
backports.entry-points-selectable==1.1.0
5
base58==2.1.0
6
bitarray==1.2.2
7
certifi==2021.5.30
8
chardet==4.0.0
9
charset-normalizer==2.0.3
10
cytoolz==0.11.0
11
distlib==0.3.2
12
eth-abi==2.1.1
13
eth-account==0.5.5
14
eth-hash==0.3.1
15
eth-keyfile==0.5.1
16
eth-keys==0.3.3
17
eth-rlp==0.2.1
18
eth-typing==2.2.2
19
eth-utils==1.10.0
20
filelock==3.0.12
21
hexbytes==0.2.1
22
idna==3.2
23
ipfshttpclient==0.7.0
24
jsonschema==3.2.0
25
lru-dict==1.1.7
26
mpmath==1.2.1
27
multiaddr==0.0.9
28
multidict==5.1.0
29
netaddr==0.8.0
30
parsimonious==0.8.1
31
platformdirs==2.1.0
32
protobuf==3.17.3
33
pycryptodome==3.10.1
34
PyJWT==1.7.1
35
pyrsistent==0.18.0
36
pytz==2021.1
37
pywin32==301
38
rd==1.0.0.3
39
requests==2.26.0
40
rlp==2.0.1
41
six==1.16.0
42
sympy==1.8
43
toolz==0.11.1
44
twilio==6.62.1
45
typing-extensions==3.10.0.0
46
urllib3==1.26.6
47
varint==1.0.2
48
virtualenv==20.6.0
49
web3==5.21.0
50
websocket-client==1.1.0
51
websockets==9.1
52
yarl==1.6.3
Copied!
Then, run the following command to install the packages:
1
pip install requirements.txt
Copied!
b) Create a file called sniffer.py
This is where our WebSocket script will live and allow us to monitor pending transaction information. We'll also add a few installations and define a few key variables at the top.
Be sure to change the Twilio and Alchemy Keys to reflect your particular Twilio Account SID / Auth Token and the Alchemy Key on your dashboard.
1
import json, time
2
import requests
3
from websocket import create_connection
4
import os
5
from twilio.rest import Client
6
import pickle
7
​
8
# Find your Account SID and Auth Token at twilio.com/console
9
# and set the environment variables. See http://twil.io/secure
10
account_sid = '<TWILIO SID>'
11
auth_token = '<TWILIO AUTH TOKEN>'
12
client = Client(account_sid, auth_token)
13
ALCHEMY_KEY = '<YOUR ALCHEMY KEY>'
Copied!
c) Initiate WebSocket connection
In our tutorial, we use WebSockets to receive pending transaction activity from an address that we pass into our ws send request.
To initiate our WebSocket connection using the Python client, we can add the following lines to sniffer.py:
1
for i in range(3):
2
try:
3
ws = create_connection("wss://eth-rinkeby.alchemyapi.io/v2/"+ALCHEMY_KEY)
4
print("Connection made")
5
except Exception as error:
6
print('Connection Error: ' + repr(error))
7
time.sleep(3)
8
else:
9
break
Copied!
NOTE: We embed our wss connection in a for loop that runs three times to help ensure that our WebSocket is properly connected upon script initiation.
After initiating the connection, to define the type of information we want to receive from the WebSocket, add the below line:
1
ws.send(json.dumps({"jsonrpc":"2.0","method":"eth_subscribe","params":["alchemy_filteredNewFullPendingTransactions", {"toAddress": "0xcF3A24407aae7c87bd800c47928C5F20Cd4764D2"}],"id":1}))
Copied!
Breaking down our JSON message, we send the following:
1
{
2
"jsonrpc":"2.0",
3
"method":"eth_subscribe",
4
"params":[
5
"alchemy_pendingTransactions", {
6
"toAddress": "0x6B3595068778DD592e39A122f4f5a5cF09C90fE2"
7
}
8
],
9
"id":1
10
}
Copied!
Notice how we use the alchemy_pendingTransactions method which allows us to receive notifications on pending asset transfers for a user-defined address.
Remember to change the address field to reflect the address/wallet that you want to monitor.
d) Parse WebSocket response & Send SMS text message
Now that we are able to create a WebSocket and send our request, we must listen for a response, parse it, and then act on the notification.
We use a while true loop to force our script to continuously listen for a response and wrap our parsing code within the loop so that we can interpret the notification.
Add the below code snippet to your sniffer.py file
1
while True:
2
try:
3
result = ws.recv()
4
result = json.loads(result)
5
from_address = (result["params"]["result"]["from"])
6
to_address = (result["params"]["result"]["to"])
7
hash = (result["params"]["result"]["hash"])
8
blockHash = (result["params"]["result"]["blockNumber"])
9
​
10
print("from:", from_address)
11
print("to:", to_address)
12
print("hash: ", hash)
13
print("blockHash: ", blockHash)
14
​
15
except KeyError as error:
16
print("Check JSON params for parsing")
17
​
18
except Exception as error:
19
print('JSON Error: ' + repr(error))
20
time.sleep(1)
Copied!
To send an SMS via the script - Include the following lines in thesniffer.pyfile within the while loop. Replace the from field with your Twilio phone number and the to field with your own phone number. The from and to parameters must strictly use E.164 formatting (+ and a country code, e.g., +16175551212). The parameter body is the text body of the SMS to be sent.
1
message = client.messages \
2
.create(
3
body="\n \n PENDING TX! \n\n From: " + from_address + " \n\n To: " + to_address + "\n\n @tx:" + hash,
4
from_='+14435267244',
5
to='+14158230041'
6
)
7
​
8
print(message.sid)
Copied!
Our script is ready! Here is the entire sample sniffer.py:
1
import json, time
2
import requests
3
from websocket import create_connection
4
import os
5
from twilio.rest import Client
6
import pickle
7
​
8
# Find your Account SID and Auth Token at twilio.com/console
9
# and set the environment variables. See http://twil.io/secure
10
account_sid = '<TWILIO SID>'
11
auth_token = '<TWILIO AUTH TOKEN>'
12
client = Client(account_sid, auth_token)
13
ALCHEMY_KEY = '<ALCHEMY KEY>'
14
​
15
for i in range(3):
16
try:
17
ws = create_connection("wss://eth-rinkeby.alchemyapi.io/v2/"+ALCHEMY_KEY)
18
print("Connection made")
19
except Exception as error:
20
print('Connection Error: ' + repr(error))
21
time.sleep(3)
22
else:
23
break
24
25
ws.send(json.dumps({"jsonrpc":"2.0","method":"eth_subscribe","params":["alchemy_filteredNewFullPendingTransactions", {"toAddress": "0xcF3A24407aae7c87bd800c47928C5F20Cd4764D2"}],"id":1}))
26
print("JSON eth_subscribe sent")
27
​
28
while True:
29
try:
30
result = ws.recv()
31
result = json.loads(result)
32
from_address = (result["params"]["result"]["from"])
33
to_address = (result["params"]["result"]["to"])
34
hash = (result["params"]["result"]["hash"])
35
blockHash = (result["params"]["result"]["blockNumber"])
36
​
37
print("from:", from_address)
38
print("to:", to_address)
39
print("hash: ", hash)
40
print("blockHash: ", blockHash)
41
​
42
print("Send Twilio SMS for pending transaction!")
43
message = client.messages \
44
.create(
45
body="\n \n PENDING TX! \n\n From: " + from_address + " \n\n To: " + to_address + "\n\n @tx:" + hash,
46
from_='+14435267244',
47
to='+14158130071'
48
)
49
​
50
print(message.sid)
51
​
52
except KeyError as error:
53
print("Check JSON params for parsing")
54
​
55
except Exception as error:
56
print('JSON Error: ' + repr(error))
57
time.sleep(1)
58
​
59
ws.close()
Copied!

4. Create WebHook Connection

With our WebSocket script above, we are able to receive information about pending transactions. Now, we want to receive information on mined transactions! For this, we use Alchemy Notify.
a) Create a file named app.py
This part of the tutorial is primarily built with Python and Flask. Here, we add a few installations and define a few key variables at the top.
Be sure to change the Twilio SID, Twilio Auth Token and Alchemy Keys to reflect your particular Twilio Account SID / Auth Token!
1
import os
2
from flask import Flask
3
from flask import request
4
from webhook import webhook
5
from twilio.rest import Client
6
import json, time
7
import requests
8
from websocket import create_connection
9
import os
10
import pickle
11
​
12
from twilio.rest import Client
13
​
14
# Find your Account SID and Auth Token at twilio.com/console
15
# and set the environment variables. See http://twil.io/secure
16
account_sid = '<TWILIO SID>'
17
auth_token = '<TWILIO AUTH TOKEN>'
18
client = Client(account_sid, auth_token)
Copied!
b) Configure Flask routing
To ensure that JSON data that hits our WebHook endpoint triggers an SMS text, we need to route incoming POST requests to trigger the main body of our Python logic.
Add the following to your app.py file:
1
app = Flask(__name__)
2
app.debug = True
3
queue = []
4
​
5
@app.route('/', methods=['POST', 'GET'])
6
​
7
def request_handler():
8
​
9
if request.method == 'POST':
10
data = (request.json)
Copied!

c) Parse incoming JSON response

After detecting a POST request, we want to be able to parse the JSON message. By decoding the JSON, we can pull out key pieces of information like
  • from_address
  • to_address
  • blockNumber
  • hash
Add the following code snippet to the request handler function:
1
if request.method == 'POST':
2
data = (request.json)
3
if len(data['event']['activity'])==1:
4
timestamp = data['createdAt']
5
from_address = data['event']['activity'][0]['fromAddress']
6
to_address = data['event']['activity'][0]['toAddress']
7
blockNum = data['event']['activity'][0]['blockNum']
8
hash = data['event']['activity'][0]['hash']
9
​
10
​
11
else:
12
for i in range(len(data['event']['activity'])):
13
timestamp = data['createdAt']
14
from_address = data['event']['activity'][i]['fromAddress']
15
to_address = data['event']['activity'][i]['toAddress']
16
blockNum = data['event']['activity'][i]['blockNum']
17
hash = data['event']['activity'][i]['hash']
18
​
19
​
20
print("DATA: ", data)
21
print("HASH: ", hash)
Copied!

d) Send SMS with Twilio

Make sure to replace the phone numbers to reflect the Twilio phone number that you acquired previously in the from field and your own phone number in the to field! Add the following two lines after parsing the POST request.
1
message = client.messages.create(body=" \n\n TX MINED! \n\n From: " + from_address + " \n\n To: " + to_address + " \n\n @#:" + blockNum + " \n Check tx: https://rinkeby.etherscan.io/tx/" +hash ,from_='+14435267241', to='+14158330071')
2
print(message.sid)
Copied!
With the main sections of our app.py complete, here's the complete sample script:
1
# -*- coding: utf-8 -*-
2
import os
3
from flask import Flask
4
from flask import request
5
from twilio.rest import Client
6
import json, time
7
import requests
8
from websocket import create_connection
9
import os
10
import pickle
11
​
12
from twilio.rest import Client
13
​
14
# Find your Account SID and Auth Token at twilio.com/console
15
# and set the environment variables. See http://twil.io/secure
16
account_sid = '<TWILIO SID>'
17
auth_token = '<TWILIO AUTH TOKEN>'
18
client = Client(account_sid, auth_token)
19
​
20
​
21
app = Flask(__name__)
22
app.debug = True
23
queue = []
24
​
25
@app.route('/', methods=['POST', 'GET'])
26
​
27
def request_handler():
28
print("Sending Twilio SMS for Mined transaction if webhook received!")
29
if request.method == 'POST':
30
data = (request.json)
31
if len(data['event']['activity'])==1:
32
timestamp = data['createdAt']
33
from_address = data['event']['activity'][0]['fromAddress']
34
to_address = data['event']['activity'][0]['toAddress']
35
blockNum = data['event']['activity'][0]['blockNum']
36
hash = data['event']['activity'][0]['hash']
37
​
38
​
39
else:
40
for i in range(len(data['event']['activity'])):
41
timestamp = data['createdAt']
42
from_address = data['event']['activity'][i]['fromAddress']
43
to_address = data['event']['activity'][i]['toAddress']
44
blockNum = data['event']['activity'][i]['blockNum']
45
hash = data['event']['activity'][i]['hash']
46
​
47
​
48
print("DATA: ", data)
49
print("HASH: ", hash)
50
​
51
​
52
message = client.messages.create(body=" \n\n TX MINED! \n\n From: " + from_address + " \n\n To: " + to_address + " \n\n @#:" + blockNum + " \n Check tx: https://rinkeby.etherscan.io/tx/" +hash ,from_='+14415267244', to='+14154230071')
53
print(message.sid)
54
​
55
​
56
return ("Ok")
57
#return webhook(session), 200
58
​
59
def run():
60
app.run(host='0.0.0.0', port=5000)
61
​
62
if __name__ == "__main__":
63
port = int(os.environ.get("PORT", 5000))
64
app.run(host='0.0.0.0', port=port)
Copied!

5. Deploy App

Now, we're in the final steps! With custom hosting solutions, you have the freedom to either run sniffer.py and app.py in the same environment or in two separate hosting platforms. Deploy both files in your desired environment!
NOTE: This app has no frontend and is configured for use on a server.
If you are able to have command line access to your files, take a look at the CLI readouts and make sure that they resemble the following start-up process for the scripts.
And now, with everything in place, you can test out your dApp!
πŸŽ‰ Congratulations on your dApp deployment! Feel free to edit your webapp, point the target address at other interesting contracts / public figures, or make improvements to this transaction life cycle tracker!

Test Your Integration βœ…

Load up your MetaMask wallet and make a transfer of testnet ETH from the wallet address that you added into sniffer.py .
Upon making the transaction, you should receive a text message stating its pending status.
This text message is sent by the script sniffer.py and can also be sent through your local systems by running the command
python sniffer.py
Similarly, upon miner confirmation of that transaction, the Heroku webapp also sends an SMS alert with the change in transaction status!

Conclusion

And that's it! You now know how to use Alchemy Notify to add notifications to your dApp! You also know where to go if you want to send SMS notifications for an addresses' activity. If you enjoyed this tutorial for setting Alchemy Notify on your dApp, give us a tweet @AlchemyPlatform! (Or if you have any questions/feedback give the authors @crypt0zeke and @ankg404 a shoutout!)
Don't forget to join our Discord server to meet other blockchain devs, builders, and entrepreneurs! Ready to start using Alchemy Notify? Create a free Alchemy account and do share your project with us
πŸŽ‰
!