Skip to content

Commit

Permalink
updated websockets page (#362)
Browse files Browse the repository at this point in the history
  • Loading branch information
lufa23 authored Jul 18, 2023
1 parent acea830 commit dbbe4c9
Showing 1 changed file with 184 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ In this section we will look into how to use the websocket functionality provide

Make sure to have <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" target="_blank">npm and node</a> installed, then create a new node project by launching:

::highlight-cart
::highlight-card
```bash
npm init -y
```
::

Install **@archwayhq/arch3.js** by launching:

Expand All @@ -39,74 +40,106 @@ npm install --save @archwayhq/arch3.js

::

As **@cosmjs/tendermint-rpc** is installed as a dependency of **@archwayhq/arch3.js**, the WebSocket libraries are already available.
And install the **uuid** package with:
::highlight-card

```bash
npm install uuid
```

::



## Create an index.js

Within your project directory, create an **index.js** and paste the following code:




::highlight-card
```javascript
// Import the WebsocketClient class from the @cosmjs/tendermint-rpc package
const { WebsocketClient } = require('@cosmjs/tendermint-rpc');
const address = 'archway1szn4zwmmu73ktn04qswh0xgvjnnsrwmkqm8daa';
// Import the WebSocket library for real-time bidirectional communication.
const WebSocket = require('ws');

// Declare a variable to store the subscription object
let subscription;
// Import the UUID library to generate a unique ID for each subscription request.
const { v4: uuidv4 } = require('uuid');

// Declare an async function to query for balance updates
const queryForBalanceUpdate = async () => {
// Define the address that is checked in the transactions.
const address = 'archway1cc0kxd3y4670mzhgz5j34s5euzppwhgp4cljjc';

// Initialize websocket and wsQuery variables.
let websocket;
let wsQuery;


// This function initiates a WebSocket connection and sends a subscription request to track transactions that fulfill certain conditions.
const queryForBalanceUpdate = () => {
try {
// Create a new WebSocket client connected to the specified URL and with an error handler
const websocket = new WebsocketClient('wss://rpc.constantine.archway.tech/', (error) =>
console.error(error)
);
// Open a new WebSocket connection to the specified URL.
websocket = new WebSocket('wss://rpc.constantine.archway.tech:443/websocket');

// Set up the JSON-RPC request to subscribe to the transaction events using the websocket client
const stream = websocket.listen({
// Define the subscription request. It asks for transactions where the recipient address, and checks for transactions to be published.
wsQuery = {
jsonrpc: '2.0',
method: 'subscribe',
id: '0',
id: uuidv4().toString(),
params: {
query: `tm.event = 'Tx' AND transfer.recipient CONTAINS '${address}'`,
},
};

// When the WebSocket connection is established, send the subscription request.
websocket.on('open', () => {
websocket.send(JSON.stringify(wsQuery));
});

// Subscribe to the stream of transaction events
subscription = stream.subscribe({
// The next function is called for each transaction event
next: (_res) => {
console.log('Matching transaction found' + JSON.stringify(_res));
},
error: (err) => {
throw err;
},
// The complete function is called when the subscription is completed
complete: () => {
websocket.disconnect();
},
// When a message (i.e., a matching transaction) is received, log the transaction and close the WebSocket connection.
websocket.on('message', (event) => {
const eventData = JSON.parse(event);
if (eventData && eventData.result && eventData.result.data) {
console.log('Matching transaction found' + JSON.stringify(eventData.result.data));
disconnectFromWebsocket();
}
});

// If an error occurs with the WebSocket, log the error and close the WebSocket connection.
websocket.on('error', (error) => {
console.error(error);
disconnectFromWebsocket();
});
} catch (err) {
// If an error occurs when trying to connect or subscribe, log the error and close the WebSocket connection.
console.error(err);
// Declare a function to disconnect from the WebSocket and unsubscribe from the event stream
disconnectFromWebsocket();
}
};

// This function closes the WebSocket connection and resets the websocket and wsQuery variables.
const disconnectFromWebsocket = () => {
if (subscription) {
subscription.unsubscribe();
}
// If the WebSocket isn't open, exit the function.
if (!websocket || websocket.readyState !== WebSocket.OPEN) return;

// Send an 'unsubscribe' message to the server.
websocket.send(JSON.stringify({ ...wsQuery, method: 'unsubscribe' }));

// Close the WebSocket connection.
websocket.close();

// Reset the websocket and wsQuery variables.
websocket = null;
wsQuery = null;
};
// Add an event listener to clean up the WebSocket connection and unsubscribe when the process exits

// When the process is exiting, close the WebSocket connection if it's still open.
process.on('exit', () => {
disconnectFromWebsocket();
});

// Call the queryForBalanceUpdate function to start listening for transaction events
// Start the process by calling the queryForBalanceUpdate function.
queryForBalanceUpdate();

```
::

Expand All @@ -115,10 +148,21 @@ The above code sets up a WebSocket client to connect to a Tendermint RPC server

The essential part of the script is the **queryForBalanceUpdate** function, which establishes a WebSocket connection using the WebsocketClient class from the **@cosmjs/tendermint-rpc** package. It then creates a subscription using the listen method and the specified query parameters.

The query parameters can be set to subscribe to transactions where the recipient's address, the IBC destination channel, and the fungible token denom match the specified values. In this case we are listening to all the transactions for a specific archway address **${address}**. The WebSocket connection is closed by calling the disconnect method.
The query parameters can be set to subscribe to transactions where the recipient's address, the IBC destination channel, and the fungible token denom match the specified values. In this case we are listening to all the transactions for a specific archway address `${address}`**`. The WebSocket connection is closed by calling the disconnect method.

Note that we are using the **wss://rpc.constantine.archway.tech:443/websocket** endpoint for testnet, and the **wss:///rpc.mainnet.archway.io:443** for mainnet. Make sure to check the [networks page](/resources/networks) for details on the available RPC endpoints.



The provided code sets up a WebSocket client to connect to a Tendermint RPC server and subscribes to transaction events by using the native WebSocket object from the **ws** Node.js library to interact with the Archway network via WebSocket. This code logs the details of transactions that match the subscription query as they occur.

The core of the script is the **queryForBalanceUpdate** function, which establishes a WebSocket connection to the **wss://rpc.constantine.archway.tech:443/websocket** endpoint. It then sends a JSON-RPC 2.0 request with a method of **subscribe** and a specific query to listen for matching transactions.

Note that we are using the **wss://rpc.constantine.archway.tech/websocket** endpoint.
The query parameters are set to subscribe to transactions where the recipient's address, the IBC destination channel, and the fungible token denom all match the specified values. Specifically, it listens for transactions where the recipient's address is `${address}`, the IBC destination channel is `${channel}`, and the fungible token denom is `${externalDenom}`.

When the WebSocket connection is open, it sends the subscription request to the server. If a transaction matches the subscription query, the server sends a message to our client. The client logs the matching transaction data and disconnects from the WebSocket server by sending an 'unsubscribe' message, closing the WebSocket connection, and setting the websocket and wsQuery variables to null.

If an error occurs during the WebSocket communication or if an error is caught during the execution of the **queryForBalanceUpdate** function, the error is logged, and the WebSocket connection is closed in the same manner. Additionally, if the process is exiting, the WebSocket connection is properly closed if it's still open.



Expand All @@ -140,18 +184,21 @@ Now that the websocket connection is established, you can send a transaction to
## Next steps
Now, you can check how to subscribe to the <a href="https://docs.tendermint.com/v0.34/tendermint-core/subscription.html" target="_blank" >WebSockets events </a>, and look at the available queries <a href="https://docs.tendermint.com/v0.34/rpc/#/Websocket" target="_blank" >Tendermint documentation</a>.

For example, you can modify the index.js to declare a **channel** variable, and then listen to IBC packets sent to a specific address.
You can do that by uncommenting the line:
For example, you can modify the **index.js** to declare a **channel** and **externalDenom** variables, and then listen to IBC packets sent to a specific address:

::highlight-card

```javascript
const channel = '0'; // Cosmos Hub source channel
const externalDenom = 'uaxl';
const channel = 'channel-8';

```

::


And modifiying the line:

::highlight-card

```javascript
Expand All @@ -161,10 +208,106 @@ query: `tm.event = 'Tx' AND transfer.recipient CONTAINS '${address}'`,
::

into:

::highlight-card

```javascript
query: `tm.event = 'Tx' AND transfer.recipient CONTAINS '${address}' AND transfer.recipient CONTAINS '${address}' AND write_acknowledgement.packet_dst_channel = '${channel}' AND fungible_token_packet.denom = '${externalDenom}'`,
query: `tm.event = 'Tx' AND transfer.recipient CONTAINS '${address}' AND write_acknowledgement.packet_dst_channel = '${channel}' AND fungible_token_packet.denom = '${externalDenom}'`,
```

::

So that your **index.js** would become:

::highlight-card

```javascript
// Import the WebSocket library for real-time bidirectional communication.
const WebSocket = require('ws');

// Import the UUID library to generate a unique ID for each subscription request.
const { v4: uuidv4 } = require('uuid');

// Define the address that is checked in the transactions.
const address = 'archway1cc0kxd3y4670mzhgz5j34s5euzppwhgp4cljjc';

// Define the denomination of the token to track.
const externalDenom = 'uaxl';

// Initialize websocket and wsQuery variables.
let websocket;
let wsQuery;

// Define the channel that is used for the transactions on the Axelar testnet.
const channel = 'channel-2';

// This function initiates a WebSocket connection and sends a subscription request to track transactions that fulfill certain conditions.
const queryForBalanceUpdate = () => {
try {
// Open a new WebSocket connection to the specified URL.
websocket = new WebSocket('wss:////rpc.mainnet.archway.io:443/websocket');

// Define the subscription request. It asks for transactions where the recipient address, and checks for transactions to be published.
wsQuery = {
jsonrpc: '2.0',
method: 'subscribe',
id: uuidv4().toString(),
params: {
query: `tm.event = 'Tx' AND transfer.recipient CONTAINS '${address}'`,
},
};

// When the WebSocket connection is established, send the subscription request.
websocket.on('open', () => {
websocket.send(JSON.stringify(wsQuery));
});

// When a message (i.e., a matching transaction) is received, log the transaction and close the WebSocket connection.
websocket.on('message', (event) => {
const eventData = JSON.parse(event);
if (eventData && eventData.result && eventData.result.data) {
console.log('Matching transaction found' + JSON.stringify(eventData.result.data));
disconnectFromWebsocket();
}
});

// If an error occurs with the WebSocket, log the error and close the WebSocket connection.
websocket.on('error', (error) => {
console.error(error);
disconnectFromWebsocket();
});
} catch (err) {
// If an error occurs when trying to connect or subscribe, log the error and close the WebSocket connection.
console.error(err);
disconnectFromWebsocket();
}
};

// This function closes the WebSocket connection and resets the websocket and wsQuery variables.
const disconnectFromWebsocket = () => {
// If the WebSocket isn't open, exit the function.
if (!websocket || websocket.readyState !== WebSocket.OPEN) return;

// Send an 'unsubscribe' message to the server.
websocket.send(JSON.stringify({ ...wsQuery, method: 'unsubscribe' }));

// Close the WebSocket connection.
websocket.close();

// Reset the websocket and wsQuery variables.
websocket = null;
wsQuery = null;
};

// When the process is exiting, close the WebSocket connection if it's still open.
process.on('exit', () => {
disconnectFromWebsocket();
});

// Start the process by calling the queryForBalanceUpdate function.
queryForBalanceUpdate();
```
::

Make sure to check the [IBC channels page](/resources/ibc-channels) for details on the updated IBC channels.

0 comments on commit dbbe4c9

Please sign in to comment.