APIs used:
  • Get all transactions in a block (v3)  

If you have followed accounts like @whale_alert on Twitter before, then you probably know the value of on-chain notifications. Notification services like these watch the blockchain for transactions that match a certain filter logic (having a value of more than 10M BTC, for instance) and notify users through their preferred medium. Examples of such products include Nansen’s Smart Alerts, as well as Santiment’s Sanbase Alerts.

Flagging or monitoring interesting on-chain transactions is useful for a variety of use cases. If you are working in AML (anti-money laundering), notifications can alert you whenever a suspicious wallet makes a transaction. If you are a crypto law enforcement agency like Chainalysis, real-time transaction flows determine whether you’ll succeed in cracking a case. Even if you are an everyday trader, notifications of large inflows and outflows from DEX liquidity pools or CEX addresses may aid your trading strategy.

How do you build an on-chain notification engine of your own?

Covalent'sendpoint lets you do this easily. With this endpoint you can access all the transactions that ever occur on-chain, grouped by block, at a near real-time latency of 2 block times. All you need to do is call the endpoint at the rate of the block production time (~14 seconds for Ethereum) to get all the transactions of every block. Our free API rate of 4 requests per second more than satisfies this need. Once you have all the latest transactions data, you can pass it into a function that contains your filter logic. If the filter condition is satisfied, an action can then be performed - for instance, sending a notification via email, SMS, or in-app.

Tutorial: How to Build a Notification App to Track Crypto Asset Movements

Let’s say you are an on-chain sleuth and you want to filter for the activities of a suspicious wallet, 0x31ed. This is how it can be done, in vanilla JS.

1

Setup

First, define the following function to fetch data from the Covalent API:

export const getDataFromCovalentAPI = (URL) => {
  let headers = new Headers()
  const COVALENT_API_KEY = //Insert your API Key
  return fetch(URL, { method: 'GET', headers: headers.set('Authorization', 'Basic ' + btoa(COVALENT_API_KEY))}).then((resp) => {
    if (resp.status === 200) return resp.json()
    else throw new Error('Invalid response')
  })
}
2

Filter transaction data

Finally, define a function that filters for transactions associated with the suspicious wallet:

const filterSuspiciousWallet = (data, walletAddress) => {
	const filteredTransactions = data.filter(transaction => transaction.from_address === walletAddress || transaction.to_address === walletAddress)
    return filteredTransactions
}

Run the function to get all the wallet transactions. Here is the full script:

JavaScript
export const getDataFromCovalentAPI = (URL) => {
  let headers = new Headers()
  const COVALENT_API_KEY = //Insert your API Key
  return fetch(URL, { method: 'GET', headers: headers.set('Authorization', 'Basic ' + btoa(COVALENT_API_KEY))}).then((resp) => {
    if (resp.status === 200) return resp.json()
    else throw new Error('Invalid response')
  })
}

const filterSuspiciousWallet = (data, walletAddress) => {
    const filteredTransactions = data.filter(transaction => transaction.from_address === walletAddress || transaction.to_address === walletAddress)
    return filteredTransactions
}

const main = () => {
    const url = 'https://api.covalenthq.com/v1/eth-mainnet/block/16176246/transactions_v3/'
    getDataFromCovalentAPI(url)
        .then(res => console.log(filterSuspiciousWallet(res.data.items, '0x4fa0002e68e600f30c04119d1ba86eb1c243be39')))
}

main()

A more robust filter

Now, while this filter is pretty good for sifting out all the transactions that either directly originated from (from_address), or interacted with (to_address) an address, it does not capture all of the associated activities with the address. This is because it is common to find a transaction address within the event logs, rather than directly in the to_address or from_address fields.

Consider a case where wallet A sends USDC to wallet B. On Etherscan, the address that shows up in the Interacted With (To) field - which maps to to_address our response - is not the address of wallet B, but the USDC address.

This design reflects the fact that when doing an ERC20 token transfer, the Interacted With (To) field shows the address we’re interacting with, which is the USDC contract address. The Covalent API response follows this convention as well.

To improve on the filter from above, consider that the address of wallet B could potentially be somewhere in the event logs. To get all the associated transactions of walletAddress, we should dive in and filter the event logs as well:

JavaScript
const filterSuspiciousWallet = (data, walletAddress) => {
    const filteredTransactionsOuter = data.filter(transaction => transaction.from_address === walletAddress || transaction.to_address === walletAddress)
    const filteredTransactionsInner = data.filter(transaction => {
        let found = false
        if (transaction.log_events) {
            transaction.log_events.filter(logEvent => {
                if (logEvent.decoded && logEvent.decoded.params) {
                    logEvent.decoded.params.filter(param => {
                        if (param.value === walletAddress) {
                            found = true
                        }
                    })
                }
            })
        }
        return found
    })
    const allFilteredTransactions = [...filteredTransactionsInner, ...filteredTransactionsOuter]
    const uniqueFilteredTransactions = [...new Set(allFilteredTransactions)]
    return uniqueFilteredTransactions
}

The filter looks a bit unwieldy, but all it does is ensure that we get returned transactions whenever the walletAddress is found either in the from_address, to_address, or within the value field of the decoded event logs.

Never miss a single on-chain transaction again!

Tutorial: How to Build a Notification App to Track Contract Events

Let’s say you want to get a notification whenever a Swap event occurs on-chain.

This is how you can build that app in vanilla JS again:

1

Setup

Same methods as in the previous example.

2

Filter transaction data

Define a filter function that takes the response data and a method name as arguments, and returns an array of found transactions:

const filterForMethod = (data, method) => {
    const filteredTransactions = data.filter(transaction => {
        let found = false
        if (transaction.log_events) {
            transaction.log_events.filter(logEvent => {
                if (logEvent.decoded && logEvent.decoded.name) {
                    if (logEvent.decoded.name === method) {
                        found = true
                    }
                }
            })
        }
        return found
    })

    return filteredTransactions
}

Here is the full script for filtering for a Swap event:

export const getDataFromCovalentAPI = (URL) => {
  let headers = new Headers()
  const COVALENT_API_KEY = //Insert your API Key
  return fetch(URL, { method: 'GET', headers: headers.set('Authorization', 'Basic ' + btoa(COVALENT_API_KEY))}).then((resp) => {
    if (resp.status === 200) return resp.json()
    else throw new Error('Invalid response')
  })
}

const filterForMethod = (data, method) => {
    const filteredTransactions = data.filter(transaction => {
        let found = false
        if (transaction.log_events) {
            transaction.log_events.filter(logEvent => {
                if (logEvent.decoded && logEvent.decoded.name) {
                    if (logEvent.decoded.name === method) {
                        found = true
                    }
                }
            })
        }
        return found
    })

    return filteredTransactions
}

const main = () => {
    const url = 'https://api.covalenthq.com/v1/eth-mainnet/block/16596019/transactions_v3/'
    getDataFromCovalentAPI(url)
        .then(res => console.log(filterForMethod(res.data.items, "Swap")))
}

main()

Go ahead, try it yourself!

Conclusion

With the large amounts of data produced by blockchains, it is crucial for us to have timely access to intelligible on-chain data. Theendpoint allows us to do just that. You can track any event throughout the entire chain in almost real time. Best of all, the same ~50-line script you saw above can be used across 200+ chains due to Covalent’s Unified API approach. This can then feed into the input of your notification service - enabling use cases such as multi-chain anti-AML engines, smart alerts, or even trading bots.