Skip to main content

How to Create Websocket Subscriptions to Solana Blockchain using Typescript

Updated on
Dec 11, 2023

7 min read

Overview

Creating event listeners is an effective way to alert your app or your users that something you're monitoring has changed. Solana has several built-in handy event listeners, also known as subscriptions, that make listening for changes on the Solana Blockchain a breeze. Not sure how you would use that? Here are a couple of examples where this might come in handy: a Discord bot that looks for interactions with an on-chain Program (e.g., sales bot), a dApp that checks for errors with a user's transaction, or a phone notification when the balance of a user's wallet changes.

What You Will Do

In this guide, you will learn how to use several Solana event listener methods and QuickNode's Websocket endpoints (WSS://) to listen for changes on chain. Specifically, you will create a simple typescript application to track for changes in an Account (or Wallet). Then you will learn how to use Solana's unsubscribe methods to remove a listener from your application. We'll also cover basics about some of Solana's other listener methods.

What You Will Need

  • Nodejs installed (version 16.15 or higher)
  • npm or yarn installed (We will be using yarn to initialize our project and install the necessary packages. Feel free to use npm instead if that’s your preferred package manager)
  • Typescript experience and ts-node installed
  • Solana Web3

Set Up Your Environment

Create a new project directory in your terminal with:

mkdir solana-subscriptions
cd solana-subscriptions

Create a file, app.ts:

echo > app.ts

Initialize your project with the "yes" flag to use default values for your new package: 

yarn init --yes
#or
npm init --yes

Install Solana Web3 dependencies:

yarn add @solana/web3.js
#or
npm install @solana/web3.js

Open app.ts in your preferred code editor and on line 1, import Connection, PublicKey, and LAMPORTS_PER_SOL from the Solana Web3 library: 

import { Connection, PublicKey, LAMPORTS_PER_SOL, } from "@solana/web3.js";

Alright! We're all ready to go. 

Set Up Your Quicknode Endpoint

To build on Solana, you'll need an API endpoint to connect with the network. You're welcome to use public nodes or deploy and manage your own infrastructure; however, if you'd like 8x faster response times, you can leave the heavy lifting to us. See why over 50% of projects on Solana choose QuickNode and sign up for a free account here.

We're going to use a Solana Devnet node. Copy the HTTP Provider and WSS Provider links:

New Solana Endpoint

Create two new variables on lines 3 and 4 of app.js to store these URLs: 

const WSS_ENDPOINT = 'wss://example.solana-devnet.quiknode.pro/000/'; // replace with your URL
const HTTP_ENDPOINT = 'https://example.solana-devnet.quiknode.pro/000/'; // replace with your URL

Establish a Connection to Solana

On line 5, create a new Connection to Solana: 

const solanaConnection = new Connection(HTTP_ENDPOINT,{wsEndpoint:WSS_ENDPOINT});

If you've created Connection instances to Solana in the past, you may notice something different about our parameters, particularly the inclusion of {wsEndpoint:WSS_ENDPOINT}. Let's dig in a little deeper. 

The Connection class constructor allows us to pass an optional commitmentOrConfig. There are a few interesting options we can include with the ConnectionConfig, but today we're going to hone in on the optional parameter, wsEndpoint. This is an option for you to provide an endpoint URL to the full node JSON RPC PubSub Websocket Endpoint. In our case, that's our WSS Endpoint we defined earlier, WSS_ENDPOINT.

What happens if you don't pass a wsEndpoint? Well, Solana accounts for that with a function, makeWebsocketUrl that replaces your endpoint URL's https with wss or http with ws (source). Because all QuickNode HTTP endpoints have a corresponding WSS endpoint with the same authentication token, it is fine for you to omit this parameter unless you'd like to use a separate endpoint for your Websocket queries.

Let's create some subscriptions!

Create an Account Subscription

To track a wallet on Solana, we'll need to call the onAccountChange method on our solanaConnection. We will pass ACCOUNT_TO_WATCH, the public key of the wallet we'd like to search, and a callback function. We've created a simple log that alerts us that an event has been detected and log the new account balance. Add this snippet to your code after your solanaConnection declaration on line 7: 

(async()=>{
const ACCOUNT_TO_WATCH = new PublicKey('vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg'); // Replace with your own Wallet Address
const subscriptionId = await solanaConnection.onAccountChange(
ACCOUNT_TO_WATCH,
(updatedAccountInfo) =>
console.log(`---Event Notification for ${ACCOUNT_TO_WATCH.toString()}--- \nNew Account Balance:`, updatedAccountInfo.lamports / LAMPORTS_PER_SOL, ' SOL'),
"confirmed"
);
console.log('Starting web socket, subscription ID: ', subscriptionId);
})()

Build a Simple Test

This code is ready to run as is, but we're going to add one more functionality to help us test that it's working properly. We'll do this by adding a sleep function (to add a time delay) and an airdrop request. Before your async code block on line 6, add: 

const sleep = (ms:number) => {
return new Promise(resolve => setTimeout(resolve, ms));
}

And then, inside your async code block after the "Starting web socket" log, add this airdrop call:

    await sleep(10000); //Wait 10 seconds for Socket Testing
await solanaConnection.requestAirdrop(ACCOUNT_TO_WATCH, LAMPORTS_PER_SOL);

Your code will effectively wait 10 seconds after the socket has initiated to request an airdrop to the wallet (note: this will only work on devnet and testnet). 

Our code now looks like this: 

import { Connection, PublicKey, LAMPORTS_PER_SOL, } from "@solana/web3.js";

const WSS_ENDPOINT = 'wss://example.solana-devnet.quiknode.pro/000/'; // replace with your URL
const HTTP_ENDPOINT = 'https://example.solana-devnet.quiknode.pro/000/'; // replace with your URL
const solanaConnection = new Connection(HTTP_ENDPOINT, { wsEndpoint: WSS_ENDPOINT });
const sleep = (ms:number) => {
return new Promise(resolve => setTimeout(resolve, ms));
}

(async () => {
const ACCOUNT_TO_WATCH = new PublicKey('vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg');
const subscriptionId = await solanaConnection.onAccountChange(
ACCOUNT_TO_WATCH,
(updatedAccountInfo) =>
console.log(`---Event Notification for ${ACCOUNT_TO_WATCH.toString()}--- \nNew Account Balance:`, updatedAccountInfo.lamports / LAMPORTS_PER_SOL, ' SOL'),
"confirmed"
);
console.log('Starting web socket, subscription ID: ', subscriptionId);
await sleep(10000); //Wait 10 seconds for Socket Testing
await solanaConnection.requestAirdrop(ACCOUNT_TO_WATCH, LAMPORTS_PER_SOL);
})()

Start Your Socket!

Let's go ahead and test it out. In your terminal you can enter ts-node app.ts to start your web socket! After about 10 seconds, you should see a terminal callback log like this:

solana-subscriptions % ts-node app.ts
Starting web socket, subscription ID: 0
---Event Notification for vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg---
New Account Balance: 88790.51694709 SOL

Excellent! You should notice that your application remains open even after the event notification. That's because our subscription is still listening for changes to our account. We need a way to unsubscribe to the listener. Hit Ctrl^C to stop the process.

Unsubscribe from Account Change Listener

Solana has created a built-in method that we can use to unsubscribe from our Account Change listener, removeAccountChangeListener. The method accepts a valid subscriptionId (number) as its only parameter. Inside your async block, add another sleep after your airdrop to allow time for the transaction to process and then call removeAccountChangeListener

    await sleep(10000); //Wait 10 for Socket Testing
await solanaConnection.removeAccountChangeListener(subscriptionId);
console.log(`Websocket ID: ${subscriptionId} closed.`);

Now run your code again, and you should see the same sequence followed by the closing of the Websocket: 

solana-subscriptions % ts-node app.ts
Starting web socket, subscription ID: 0
---Event Notification for vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg---
New Account Balance: 88791.51694709 SOL
Websocket ID: 0 closed.

Nice job! As you can see, this can be useful if you want to disable a listener after something has happened (e.g., time elapsed, certain threshold realized, number of notifications, etc.). 

We've published the final code for this script to our Github repo for your reference.

Other Solana Websocket Subscriptions

Solana has several other, similar Websocket subscribe/unsubscribe methods that are also useful. We'll describe them briefly in this section.

  • onProgramAccountChange: Register a callback to be invoked whenever accounts owned by the specified program change. Pass a Program's PublicKey and an optional array of account filters. Among other things, this can be handy for tracking changes to a user's Token Account. Unsubscribe with removeProgramAccountChangeListener.
  • onLogs: Registers a callback to be invoked whenever logs are emitted--can be used similar to onAccountChange by passing a valid PublicKey. The callback will return recent transaction ID. Unsubscribe with removeOnLogsListener
  • onSlotChange: Register a callback to be invoked upon slot changes. Only a callback function is passed into this method. Unsubscribe with removeSlotChangeListener
  • onSignature: Register a callback to be invoked upon signature updates by passing a valid Transaction Signature. The callback will return whether or not the transaction has experienced an error. Unsubscribe with removeSignatureListener. onRootChange: Register a callback to be invoked upon root changes. Only a callback function is passed into this method. Unsubscribe with removeRootChangeListener

*Note: all unsubscribe methods require a subscriptionId (number) parameter. You can find more information on these methods in our documentation at  quicknode.com/docs/solana. Feel free to experiment with each by making minor modifications to app.js to try some of these other subscriptions.  

Conclusion

Nice work! Now you should have a handle on how to use Websocket subscriptions on Solana. How are you using Solana Websockets? We'd love to see what you're creating! Share your app with us on Discord or Twitter. If you have any feedback or questions on this guide, we’d love to hear from you

To learn more, check out some of our other Solana tutorials here, and if you've had fun with Websocket subscriptions, consider subscribing to our newsletter.

Share this guide