Skip to main content

Tracking Blockchain Event Data with QuickNode's QuickAlerts Expressions

Created on
Updated on
Nov 26, 2024

12 min read

Overview

QuickAlerts is a new developer experience on QuickNode that enables users to receive real-time notifications of activities happening on the blockchain. QuickAlerts expressions are evaluated against streaming blockchain data and deploy real-time notifications when targeted events occur.

QuickAlerts has the following capabilities:

  1. Users submit an expression that will be evaluated against streaming blockchain data
  2. Users configure a webhook destination that will receive a payload containing actionable data when their expression triggers an alert
  3. Users see their QuickAlerts usage within the QuickNode Dashboard

In this guide, we'll look at QuickAlert Expressions specifically. Custom expressions can be used to receive notification payloads delivered to one or more webhook destinations in real-time. You can customize our example expressions or build your own flexible expressions evaluated in real-time against streaming blockchain data.

info

QuickAlerts is available to all customers as of Feb 7, 2023. If you have any feedback regarding QuickAlerts, we'd love to hear from you. Kindly fill out this form with your comments.

What Are QuickAlerts Expressions?

An expression is a programming construct that evaluates and returns a value. Expressions use constants, variables, functions, and operators to define and execute isolated logic. 1 + 2 is a very basic example of an expression that evaluates to 3. QuickAlerts expressions are expressions that are evaluated on blockchain event data to trigger notifications.

Expressions can be simple or complex, depending on the event you would like to track. You can target blockchain fields relating to block, transactions, and logs. You can also target a combination of fields like (block_baseFeePerGas * block_gasUsed) / 1000000000000000000) to create a composable value to be compared. Once you have written your expression, it can be tested on a block or a set of historical blocks.

Note: When building expressions, it is important to keep in mind that the expressions target raw blockchain data. For example, event topics are 32 bytes plus a prefix of 0x. This means that a 32-byte hex string is 64 characters long, or 66 characters long when you include the 0x prefix.

 
EVM addresses are only 42 characters long when you include the 0x, so to look for an EVM address within tx_logs_topic#, you will need to include all of the 0s found within the raw data of a transaction.

For instance, if you want to target the address, 0xce90a7949bb78892f159f428d0dc23a8e3584d75, in tx_logs_topic2, you will need to format your expression like this: (tx_logs_topic2 == '0x000000000000000000000000ce90a7949bb78892f159f428d0dc23a8e3584d75')

When writing expressions for QuickAlerts, we are targeting data within blocks, transactions, and logs. Here is an example of data sampled from the Ethereum Blockchain. Skim through it and get comfortable with its structure. We'll use it to create expressions in the next section.

{
"code": 200,
"msg": "ok",
"data": {
"block_number": 15105769,
"topic": "raw-eth-mainnet",
"chain": "eth-mainnet",
"block": {
"rpc": "2.0",
"id": 1,
"result": {
"baseFeePerGas": "0x3a7056c6b",
"difficulty": "0x2a323c4352f652",
"extraData": "0x486976656f6e20686b",
"gasLimit": "0x1c9c380",
"gasUsed": "0xcbd18d",
"hash": "0xca8d74cab6b0f698f5a64351ebe1f69babfd193bc0662725dc1fadc7be202f45",
"logsBloom": "0x242f92d4f10e080f500040cda4f0cb65c810d130488c765223d1a51b19d0810b9f8d76ab11a455815960d00a084893661661a0045c0bb9208fa41ed5fb36f4405219270e16170c0e625a781eaf1c67f828d522915cec128511e33421df5d8441b240b293e60f596c2c80b182783e98990011e45800184445c06810d0818dd08805d06b291f4258c7ab6a2be7ca83c0808402b06b3460e379301621437b1010889afc5d29a7ca4510aae9789787312218c985da0508830a0663216b0ee6496b133b0053b274201121115e7821bf1b5000a43c3cc1b048405304412d4712de632ad2fea96d8b2409168650d97523541a47b437285b8f683d6f26c34080007ae782",
"miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836",
"mixHash": "0xacb93d219610ffe9f8be21f134465ec8c0e04723a73c663562cbe6515a0af820",
"nonce": "0x46b9de04ca36c610",
"number": "0xe67ee9",
"parentHash": "0x1dcb6225dd586c00359d5f99bc9073bd1eadf27c463f5e702eea8ebb77a29ed9",
"receiptsRoot": "0x044b25ca3bcb7bc089c527df3dd05f6c84989154572a90725463e4acb0add449",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size": "0x156b9",
"stateRoot": "0x4d29cb99407a1505a5f78cbd8a5ec79a21e1fd1d7d88fd7d06df16eac8181dba",
"timestamp": "0x62c8eafc",
"totalDifficulty": "0xb586290f7766766fbe5",
"transactions": [
{
"blockHash": "0xca8d74cab6b0f698f5a64351ebe1f69babfd193bc0662725dc1fadc7be202f45",
"blockNumber": "0xe67ee9",
"from": "0x91aae0aafd9d2d730111b395c6871f248d7bd728",
"gas": "0x493ee",
"gasPrice": "0x840cf186c",
"hash": "0xd511bf95c3eb2b0f5237d21329e317f2b3892abc17ae554401b1101f00eb9711",
"input": "0xce2e62ff00000000000000000000000000000000000000000000000019db7a47919e2600000000000000000000000000000000000000000000000245fdbf32633b80000000000000000000000000000099dfde431b40321a35deb6aeb55cf338ddd6eccd000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062c8eb52",
"nonce": "0x17238",
"to": "0x98c3d3183c4b8a650614ad179a1a98be0a8d6b8e",
"transactionIndex": "0x0",
"value": "0x0",
"type": "0x2",
"v": "0x1",
"r": "0xb091ad06bd64b9f27db10597b09551a23ab3675573906f410d9bc9cef0453fb6",
"s": "0x437ffea08f00ef3ce4f0fcd0abceb0294095cee1a5bd51db092e4c39ed870fc8",
"maxFeePerGas": "0x93d43c27e",
"maxPriorityFeePerGas": "0x499c9ac01",
"accessList": [],
"chainId": "0x1"
},
{ ... }
],
"transactionsRoot": "0x03c1523eba33d1a48ab5a503f7e75e22f36a2ca86a47de100de4f24c0a59f5bf",
"uncles": []
}
},
},
"receipts": {
"rpc": "",
"id": 0,
"result": [
{
"blockHash": "0xca8d74cab6b0f698f5a64351ebe1f69babfd193bc0662725dc1fadc7be202f45",
"blockNumber": "0xe67ee9",
"contractAddress": null,
"cumulativeGasUsed": "0x17ae1",
"effectiveGasPrice": "0x840cf186c",
"from": "0x91aae0aafd9d2d730111b395c6871f248d7bd728",
"gasUsed": "0x17ae1",
"logs": [
{
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x00000000000000000000000098c3d3183c4b8a650614ad179a1a98be0a8d6b8e",
"0x00000000000000000000000099dfde431b40321a35deb6aeb55cf338ddd6eccd"
],
"data": "0x00000000000000000000000000000000000000000000000019db7a47919e2600",
"blockNumber": "0xe67ee9",
"transactionHash": "0xd511bf95c3eb2b0f5237d21329e317f2b3892abc17ae554401b1101f00eb9711",
"transactionIndex": "0x0",
"blockHash": "0xca8d74cab6b0f698f5a64351ebe1f69babfd193bc0662725dc1fadc7be202f45",
"logIndex": "0x0",
"removed": false
},
{
"address": "0x0202be363b8a4820f3f4de7faf5224ff05943ab1",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x00000000000000000000000099dfde431b40321a35deb6aeb55cf338ddd6eccd",
"0x00000000000000000000000098c3d3183c4b8a650614ad179a1a98be0a8d6b8e"
],
"data": "0x000000000000000000000000000000000000000000000247235d0af8abded25c",
"blockNumber": "0xe67ee9",
"transactionHash": "0xd511bf95c3eb2b0f5237d21329e317f2b3892abc17ae554401b1101f00eb9711",
"transactionIndex": "0x0",
"blockHash": "0xca8d74cab6b0f698f5a64351ebe1f69babfd193bc0662725dc1fadc7be202f45",
"logIndex": "0x1",
"removed": false
},
{
"address": "0x99dfde431b40321a35deb6aeb55cf338ddd6eccd",
"topics": [
"0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"
],
"data": "0x000000000000000000000000000000000000000000017f532c1cfe945e956af20000000000000000000000000000000000000000000000110c1b2b134a64b664",
"blockNumber": "0xe67ee9",
"transactionHash": "0xd511bf95c3eb2b0f5237d21329e317f2b3892abc17ae554401b1101f00eb9711",
"transactionIndex": "0x0",
"blockHash": "0xca8d74cab6b0f698f5a64351ebe1f69babfd193bc0662725dc1fadc7be202f45",
"logIndex": "0x2",
"removed": false
}
]
}
]
}
}

Creating Various Expressions

Expressions are flexible and composable which enables you to use your imagination to build a wide variety of programable alerts. Using the data above, we can construct this first example below which will trigger an alert whenever the burned fees within a block are greater than 0.499 ETH, and it contains 1 or more transactions interacting with the Uniswap V3 Router.

Expression
Deploy
1
(
2
(block_baseFeePerGas * block_gasUsed)
3
/
4
1000000000000000000
5
) > 0.499
6
&&
7
tx_to == '0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45'

Maybe we want to look for low transaction volume, we can create the following expression to receive a notification whenever a block contains less than 50 transactions.

Expression
Deploy
1
block_totalTransactions < 50

Send an Alert for All Transfer Activity on an Address

The next example sends an alert for transfer activity on the to address.

Expression
Deploy
1
tx_to == '0x0..myAddress'

In addition to searching for transactions sent to an address, it is also possible to send an alert for activity from an address.

Expression
Deploy
1
tx_to == '0x0..myAddress'
2
||
3
tx_from == '0x0..myAddress'

Send an Alert when Wallet A sends a Transaction to Wallet B

In this example, we will create an expression that will evaluate and trigger a Webhook notification in real time whenever Wallet A sends a transaction to Wallet B. The targets tx_from and tx_to will be evaluated against all of the transactions in each new block.

Expression
Deploy
1
tx_from == '0x0..walletA'
2
&&
3
tx_to == '0x0..walletB'

This triggers a real time webhook notification when it finds a match. You can use common operators in your expressions enabling you to create very specific alerts.

Send an Alert When Any New NFT is Minted

Let's look at a more specific and complex expression with additional operators and logic. This example sends a webhook notification whenever a new NFT is minted, either ERC-721 or ERC-1155. Before we start looking at this example, it is important to understand Transactions Logs Topics. These are a value-added tools that allow users to evaluate the raw data.

For tx_logs_topic0 through tx_logs_topic3, users can append _int, like tx_logs_topic3_int to evaluate the value of tx_logs_topic3 as an integer. This is shown below in the example where we want to receive an alert when a transfer() event occurs AND the source is the zero wallet AND there is an integer returned in topic3, indicating a tokenID.

The expression targets logs and evaluates for each transfer, transferSingle, or transferBatch event in which the source is the 0 wallet and a token ID is provided in transfer events. We'll target the topic hashes which relate to transfer events on EVM chains, (Transfer() || (TransferSingle() || TransferBatch()). Target the following topic hashes:

  • Transfer: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
  • TransferSingle: 0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62
  • TransferBatch: 0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb
Expression
Deploy
1
(
2
tx_logs_topic0 == '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
3
&&
4
tx_logs_topic1 == '0x0000000000000000000000000000000000000000000000000000000000000000'
5
&&
6
tx_logs_topic3_int > 0
7
)
8
||
9
(
10
tx_logs_topic0 == '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62'
11
&&
12
tx_logs_topic2 == '0x0000000000000000000000000000000000000000000000000000000000000000'
13
)
14
||
15
(
16
tx_logs_topic0 == '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb'
17
&&
18
tx_logs_topic2 == '0x0000000000000000000000000000000000000000000000000000000000000000'
19
)

You may be wondering, "what about non-standard topic hashes, like PunkTransfer?" Due to the inherent flexibility of our expressions, you can target literally ANY topic hash you want. Here is an example expression that will trigger an alert whenever the Crypto Punk is transferred into a new wallet:

Expression
Deploy
1
tx_logs_address == '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb'
2
&&
3
tx_logs_topic0 == '0x05af636b70da6819000c49f85b21fa82081c632069bb626f30932034099107d8'

Send an Alert Whenever Cozomo de' Medici Mints an ERC-721 NFT

For this example, we want to receive an alert whenever an NFT whale initiates a transaction in which a new ERC-721 NFT is minted. We are targeting transactions in which tx_from is Cozomo de’ Medici’s wallet address, a log is present with the topic hash for transfer in tx_logs_topic0 within the log, the source (tx_logs_topic1) is the 0 wallet, the destination (tx_logs_topic2) is Cozomo de’ Medici’s wallet, and there is an integer sent as token ID (tx_logs_topic3).

Expression
Deploy
1
(
2
tx_logs_topic0 == '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
3
&&
4
tx_logs_topic1 == '0x0000000000000000000000000000000000000000000000000000000000000000'
5
&&
6
tx_logs_topic2 == '0x000000000000000000000000ce90a7949bb78892f159f428d0dc23a8e3584d75'
7
&&
8
tx_logs_topic3_int > 0
9
)
10
&&
11
tx_from == '0xce90a7949bb78892f159f428d0dc23a8e3584d75'

This example includes NFTs transferred into the target wallet that are also contained within transactions initiated from the target wallet.

QuickAlerts can also target Chainlink data feeds which provide the ability to connect smart contracts to real-world data such as asset prices or reserve balances. Here is the process on Ethereum Mainnet.

  1. Visit Chainlink's documentation for Price Feed Contract Addresses and find the proxy contract for the cryptocurrency you would like to alert on. This example uses BTC/USD: 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c.
  2. Look up the contract on Etherscan or do an eth_call to aggregator() to retrieve the address of the aggregator for this contract. Take note of the value for decimals() as it will relate to the way you construct your expression: 0xae74faa92cb67a95ebcab07358bc222e33a34da7.
  3. To find the current price of BTC/USD as it updates, we will target transactions that are sent to the aggregator address, and which contain logs where the tx_logs_topic0 ==  0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f which represents AnswerUpdated().
  4. The data contained within tx_logs_topic1_int (the integer representation of tx_logs_topic1) represents the price of BTC/USD when you apply the decimals from step 2 above. For example, 2000000000000 == $20,000.

The following expression will send an alert when the price of BTC is greater than $20,000. Set the target block to 15423028 for a matching test expression.

Expression
Deploy
1
tx_to == '0xae74faa92cb67a95ebcab07358bc222e33a34da7'
2
&&
3
tx_logs_topic0 == '0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f'
4
&&
5
tx_logs_topic1_int > 2000000000000

API Reference

To understand the available API for QuickAlerts, we can look at two categories: Data Prefixes and Operators.

Data Prefixes

To use expressions to target the different layers of data within the blockchain data, we will use the following prefixes to target data at the desired level: block_, tx_, and tx_logs_.

  • Block_ targets data from the block
    • block_baseFeePerGas
    • block_difficulty
    • block_extraData
    • block_gasLimit
    • block_gasUsed
    • block_hash
    • block_logsBloom
    • block_miner
    • block_mixHash
    • block_number
    • block_parentHash
    • block_sha3Uncles
    • block_size
    • block_stateRoot
    • block_timestamp
    • block_totalTransactions
    • block_transactionsRoot
    • block_totalUncles
  • tx_ targets data from the transaction
    • tx_blockHash
    • tx_blockNumber
    • tx_contractAddress
    • tx_cumulativeGasUsed
    • tx_effectiveGasPrice
    • tx_gas
    • tx_gasPrice
    • tx_input
    • tx_nonce
    • tx_from
    • tx_to
    • tx_transactionHash
    • tx_logsBloom
    • tx_status
    • tx_transactionIndex
    • tx_value
    • tx_type
    • tx_maxFeePerGas
    • tx_maxPriorityFeePerGas
    • tx_v
    • tx_r
    • tx_s
  • tx_logs_ targets data from transaction logs
    • tx_logs_address
    • tx_logs_topic0
    • tx_logs_topic0_int
    • tx_logs_topic1
    • tx_logs_topic1_int
    • tx_logs_topic2
    • tx_logs_topic2_int
    • tx_logs_topic3
    • tx_logs_topic3_int
    • tx_logs_data
    • tx_logs_data_int
    • tx_logs_blockHash
    • tx_logs_blockNumber
    • tx_logs_logIndex
    • tx_logs_removed
    • tx_logs_transactionHash
    • tx_logs_transactionIndex

Operators

There are many operators which can be used within your expressions.

  • Modifiers: +, -, /, **, &, |, ^, **, %, >>, <<*.
  • Comparators: >, >=, <, <=, \==, !=, \=~, !~.
  • Logical operators: ||, &&.
  • Boolean constants: true, false.
  • Parenthesis to control order of evaluation: (, ).
  • Arrays: Anything separated by , within parenthesis, (1, 2, 'foo').
  • Prefixes: !, -, ~.
  • Ternary Conditional: ?, :.

Conclusion

If you have any questions or just want to get in touch with the community, visit our Discord or Twitter. We'd love to know what you're building! 

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Share this guide