How to monitor Starknet wallets
Learn how to build a Starknet wallet monitoring service with Juno. Set up real-time alerts on wallet activities like token transfers, contract interactions, whale trades, and more.
By Jelilat Anofiu.
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 😊
About us
Nethermind is a team of world-class builders and researchers. We empower enterprises and developers worldwide to access and build upon the decentralized web. Our work touches every part of the web3 ecosystem, from our Nethermind node to fundamental cryptography research and infrastructure for the Starknet ecosystem.
If you’re interested in solving some of blockchain’s most difficult problems, visit our job board!
Disclaimer:
This blogpost has been prepared for the general information and understanding of the readers. The views and strategies described may not be suitable for all readers. No third party should rely on this article in any way, including without limitation as financial, investment, tax, regulatory, legal, or other advice, or interpret this article as any form of recommendation.
This blogpost does not indicate Nethermind’s endorsement of any particular project or team, nor guarantee its security. Readers should use their best judgment and exercise caution where appropriate.
To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this blogpost, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement.