How to monitor Starknet wallets

Starknet

August 18, 2023

Introduction

Wallet monitoring is no doubt an important aspect of blockchain interaction. Whether it’s for keeping tabs on your transactions, getting notified about strange activities on specific addresses or even tracking whale trades, a robust wallet monitoring service is invaluable.

In this tutorial, I’ll guide you through the process of building a wallet monitoring service using Juno, our open-source Starknet node client. This step-by-step guide will cover everything from setting up your node to building the monitoring service itself.

Setting up your Starknet node with Juno

Before diving into the nitty-gritty of building the monitoring service, you need to first set up your Starknet node using the Juno client. The node enables you to access and interact with Starknet data in real-time.

To begin the setup process, check out our comprehensive documentation. This will walk you through every step and provide additional insights into the functionalities that Juno offers.

Building your wallet monitoring service

In this section, we’ll build a wallet monitoring service that sends the user an email whenever a transaction is executed by their wallet. We’ll be using TypeScript, NodeJS, and the Mailjet API.

Step 1: Setting up your project

Start by ensuring you have Node.js and npm installed on your machine. If not, you can download and install them from here.

Next, create a new directory for your project and navigate into it:

mkdir starknet-wallet-monitor
cd starknet-wallet-monitor

Then, initialize a new Node.js project with npm:

npm init -y

This will create a package.json file in your directory.

Step 2: Installing dependencies

We’ll use the starknet package to interact with Starknet and the node-mailjet package to send emails. Install them with npm:

npm install starknet node-mailjet dotenv js-sha3

Step 3: Setting up environment variables

We’ll be using environment variables to store sensitive information such as the node URL and Mailjet API keys. Create a new file named .env in your project directory and add the following line:

STARKNET_NODE_URL=your_juno_node_url
MJ_APIKEY_PUBLIC=your_mailjet_public_key
MJ_APIKEY_PRIVATE=your_mailjet_secret_key

Replace your_juno_node_url, your_mailjet_public_key, and your_mailjet_secret_key with your actual Juno node URL and Mailjet API keys.

Step 4: Setting up the Email Notification

We’ll use Mailjet to send email notifications whenever a transaction is detected. For that, create a new TypeScript file named email.ts in your project directory. This file will handle sending emails through Mailjet. Feel free to use any email provider of your choice.

In email.ts, paste the following code.

import Mailjet from 'node-mailjet';

const mailjet = Mailjet.connect(
    process.env.MJ_APIKEY_PUBLIC!,
    process.env.MJ_APIKEY_PRIVATE!,
);
export const mailjetRequest = async (transactionHash: string) => {
    const response = await mailjet
        .post('send', { version: 'v3.1' })
        .request({
            Messages: [
                {
                    From: {
                        Email: "wallet-monitor@nethermind.io",
                        Name: "Wallet Monitor"
                    },
                    To: [
                        {
                            Email: "user@starknet.com",
                            Name: "user"
                        }
                    ],
                    Subject: "Alert: New Transaction Detected on Your Wallet",
                    TextPart: `Dear User, \\\\n\\\\nWe have detected a new transaction on your StarkNet wallet. You can view the details of this transaction on Voyager by following this link: \\\\n\\\\nhttps://voyager.online/tx/${transactionHash} \\\\n\\\\nPlease review this transaction carefully. If you did not initiate this transaction, it may indicate that your private keys have been compromised. Ensure that your keys are stored securely and consider moving your funds to a new wallet if you suspect any foul play. \\\\n\\\\nStay Safe, \\\\nWallet Monitor`,
                    HTMLPart: `<p>Dear User,</p><p>We have detected a new transaction on your StarkNet wallet. You can view the details of this transaction on <a href="<https://voyager.online/tx/${transactionHash}>">Voyager</a>.</p><p>Please review this transaction carefully. If you did not initiate this transaction, it may indicate that your private keys have been compromised. Ensure that your keys are stored securely and consider moving your funds to a new wallet if you suspect any foul play.</p><p>Stay Safe,<br/>Wallet Monitor</p>`
                }
            ]
        })
    return response;
}

Don’t forget to replace "wallet-monitor@nethermind.io" with your actual email address and "user@starknet.com" with the email address you want to send alerts to. Also, make sure to replace process.env.MJ_APIKEY_PUBLIC! and process.env.MJ_APIKEY_PRIVATE! with your actual Mailjet API public and private keys.

Now, whenever mailjetRequest(transactionHash) is called, an email will be sent to the user with the details of the transaction on Voyager.

In the next step, we’ll put it all together and create the logic for the wallet monitoring service in index.ts.

Step 5: Building the Wallet Monitoring Service

Let’s go back to our index.ts file and import the mailjetRequest function from email.ts:

import { mailjetRequest } from './email';
import { config } from 'dotenv';
config();

Next, create a function stringToHexFelt that converts strings to sn_keccak.

const stringToHexFelt = (name: string): string => {
    // Compute the Keccak-256 hash of the name encoded in ASCII
    const nameHash = keccak256.array(name);
    // Get the last 250 bits of the hash
    const last250Bits = nameHash.slice(-31).reverse();
    // Convert the bytes to a bigint
    let selectorInt = 0n;
    for (let i = 0; i < last250Bits.length; i++) {
        selectorInt |= BigInt(last250Bits[i]) << BigInt(i * 8);
    }
    return "0x" + selectorInt.toString(16);
}

Create listenToEvents(lastBlockNumber: number) that listens for new events from your Starknet node. If the function finds a new event, it sends an email to the user:

const CONTRACT_ADDRESS = ADDRESS_YOU_WANT_TO_MONITOR;

export const provider = new RpcProvider({
  nodeUrl: process.env.STARKNET_NODE_URL!
})
const listenToEvents = async (lastBlockNumber: number) => {
    let continuationToken: string | undefined;
    let lastTransactionHash: string | undefined;
    while (true) {
        const event = await provider.getEvents({
            continuation_token: continuationToken,
            from_block: {
                block_number: lastBlockNumber,
            },
            to_block: "latest" as any,
            address: CONTRACT_ADDRESS,
            keys: [[stringToHexFelt("transaction_executed")]],
            chunk_size: 1000,
        })
        continuationToken = event.continuation_token;
        for await (const item of event.events) {
            const transactionHash = item.transaction_hash;
            if (transactionHash != lastTransactionHash) {
                // send transaction to email
                await mailjetRequest(transactionHash);
            }
            lastTransactionHash = transactionHash;
        }
        if (!continuationToken) {
            break;
        }
    }
}

Lastly, create a loop that keeps checking for new blocks and listens for new events:

const getLatestBlockNumber = async () => {
    const block = await provider.getBlock("latest");
    return block.block_number;
}

const main = async () => {
    let lastBlockNumber = 0
    while (true) {
        try {
            const latestBlockNumber = await getLatestBlockNumber()
            console.log('Latest block number:', latestBlockNumber)
            if (latestBlockNumber > lastBlockNumber) {
                await listenToEvents(latestBlockNumber)
                // Update lastBlockNumber only after listenToEvents has executed successfully
                lastBlockNumber = latestBlockNumber
            }
        } catch (error) {
            console.error('Failed to fetch latest block number or listen to events:', error)
        }
        await new Promise(resolve => setTimeout(resolve, 30000));
    }
}
main().catch(console.error)

Step 6: Running the Wallet Monitoring Service

To run your wallet monitoring service, compile your TypeScript files to JavaScript using the TypeScript compiler. If you haven’t already installed TypeScript, you can do so with the following command:

npm install -g typescript

Now, compile your TypeScript files to JavaScript:

tsc index.ts email.ts

Finally, run your service with Node.js:

node index.js

Alternatively, you can run:

npx ts-node index.ts

Your wallet monitoring service is now running! It will send an email to the specified user whenever a new transaction is detected on the monitored wallet, and print the details of the transaction to the console.


Please note that the code provided in this tutorial is intended for educational purposes and is not suitable for production use.

Want to play a part in Juno’s evolution? Start contributing here.

Latest articles