Tutorial: How to Stream Moonshot Live Prices
In this tutorial, you will learn how to create a simple React application to stream live prices from the Moonshot protocol using WebSocket and Bitquery's Moonshot APIs.
You can find the complete repo here
The final output would look something like this
Prerequisitesβ
- Basic knowledge of React and JavaScript.
- Node.js installed on your machine.
- Bitquery OAuth token.
Step 1: Setting Up the Projectβ
First, create a new React project if you donβt have one already:
npx create-react-app moonshot-stream
cd moonshot-stream
Step 2: Install Required Packagesβ
Install the necessary dependencies for WebSocket support:
npm install reconnecting-websocket
Step 3: Create the useWebSocket
Hookβ
The useWebSocket
hook will manage the WebSocket connection, handle reconnection attempts, and process the incoming data.
1. useWebSocket.js
- Custom WebSocket Hookβ
This file contains a custom React hook named useWebSocket
, which manages the WebSocket connection.
Step 1: Initialize State Variables
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [isConnected, setIsConnected] = useState(false);
const [retryCount, setRetryCount] = useState(0);data
: Stores the incoming data from the WebSocket.error
: Stores any error messages that may occur during connection.isConnected
: Tracks whether the WebSocket is currently connected.retryCount
: Counts the number of reconnection attempts made.
Step 2: Define the
useEffect
HookuseEffect(() => {
const connectWebSocket = () => {
const ws = new ReconnectingWebSocket(url, ["graphql-ws"], options);useEffect
is used to initiate the WebSocket connection when the component mounts or when theretryCount
changes.connectWebSocket
function is defined inside theuseEffect
to handle the connection logic.
Step 3: Handle WebSocket Events
onopen Event
ws.onopen = () => {
setIsConnected(true);
setRetryCount(0);
ws.send(JSON.stringify({ type: "connection_init" }));
setTimeout(() => {
ws.send(
JSON.stringify({
type: "start",
id: "1",
payload: { query },
})
);
}, 1000);
};onopen
: Triggered when the WebSocket connection is established.- Sends an initial
connection_init
message. - Sends the GraphQL query after a short delay.
onmessage Event
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
if (response.type === "data") {
setData(response.payload.data);
}
};onmessage
: Triggered when a message is received from the server.- Parses the JSON data and updates the
data
state with the payload.
onclose Event
ws.onclose = () => {
setIsConnected(false);
if (retryCount < maxRetries) {
setRetryCount(retryCount + 1);
setTimeout(connectWebSocket, 2000);
} else {
setError("Max retry attempts reached. Could not connect to Bitquery.");
}
};onclose
: Triggered when the WebSocket connection is closed.- Attempts to reconnect if the maximum retry count has not been reached.
onerror Event
ws.onerror = (event) => {
console.error("WebSocket Error:", event);
setError("WebSocket error occurred. See console for details.");
};onerror
: Triggered when an error occurs with the WebSocket.- Logs the error to the console and updates the
error
state.
Step 4: Close Function
return () => {
ws.close();
};- Ensures that the WebSocket connection is properly closed when the component unmounts.
Step 5: Return Values
return { data, error, isConnected };
- The hook returns the current state values (
data
,error
,isConnected
) to be used in the component.
- The hook returns the current state values (
Step 4: App.js
- Main Componentβ
This file contains the main React component that uses the useWebSocket
hook to display the data.
Step 1: Define the GraphQL Query
const query = `
subscription MyQuery {
Solana {
DEXTrades(
where: {
Trade: { Dex: { ProtocolFamily: { is: "Moonshot" } } }
Transaction: { Result: { Success: true } }
}
) {
Instruction {
Program {
Method
}
}
Trade {
Dex {
ProtocolFamily
ProtocolName
}
Buy {
Amount
Account {
Address
}
Currency {
Name
Symbol
MintAddress
Decimals
Fungible
Uri
}
}
Sell {
Amount
Account {
Address
}
Currency {
Name
Symbol
MintAddress
Decimals
Fungible
Uri
}
}
}
Transaction {
Signature
}
}
}
}
`;- This query subscribes to real-time data from the Solana blockchain, filtering for DEX trades related to the "Moonshot" protocol.
Step 2: Define the WebSocket URL and Options
const url = "wss://streaming.bitquery.io/eap?token=YOUR_TOKEN";
const options = {
maxReconnectionDelay: 10000,
minReconnectionDelay: 1000,
reconnectionDelayGrowFactor: 1.3,
connectionTimeout: 5000,
maxRetries: Infinity,
debug: true,
};url
: The WebSocket endpoint, including your Bitquery Token.options
: Configuration for the WebSocket connection, such as reconnection delays and retry limits.
Step 3: Use the
useWebSocket
Hookconst { data, error, isConnected } = useWebSocket(url, options, query);
- The hook is called with the
url
,options
, andquery
, and returns the current connection status, any errors, and the received data.
- The hook is called with the
Step 4: Render the Component
return (
<div>
<h1>Bitquery WebSocket</h1>
<p>Status: {isConnected ? "Connected" : "Disconnected"}</p>
{error && <p>Error: {error}</p>}
{data ? (
<table>
<tbody>
<tr>
<th>Protocol Family</th>
<td>{data.Solana.DEXTrades[0].Trade.Dex.ProtocolFamily}</td>
</tr>
<tr>
<th>Protocol Name</th>
<td>{data.Solana.DEXTrades[0].Trade.Dex.ProtocolName}</td>
</tr>
<tr>
<th>Buy Amount</th>
<td>{data.Solana.DEXTrades[0].Trade.Buy.Amount}</td>
</tr>
<tr>
<th>Buy Currency</th>
<td>{data.Solana.DEXTrades[0].Trade.Buy.Currency.Symbol}</td>
</tr>
<tr>
<th>Sell Amount</th>
<td>{data.Solana.DEXTrades[0].Trade.Sell.Amount}</td>
</tr>
<tr>
<th>Sell Currency</th>
<td>{data.Solana.DEXTrades[0].Trade.Sell.Currency.Symbol}</td>
</tr>
<tr>
<th>Transaction Signature</th>
<td>{data.Solana.DEXTrades[0].Transaction.Signature}</td>
</tr>
</tbody>
</table>
) : (
<p>Loading data...</p>
)}
</div>
);- The component renders the connection status, any errors, and the data in a table format if available.
- If the data is not yet available, it displays a "Loading data..." message.