Skip to main content

Stream Blockchain Data to Discord

Updated on
Sep 30, 2024

Overview

Discord is amongst the most popular communication platforms, especially within the blockchain communities. It can be very handy to have crucial data fed directly into your Discord channel.

In this Function example, we will set up a blockchain data stream to a Discord channel, using QuickNode Streams and Functions.

Sample Function

We will establish the data stream using QuickNode Streams. Streams offer various popular data solutions such as Webhook, Postgres, AWS S3, Snowflake, etc. However, there are instances where you may require a specific or custom destination. With Functions, we can create blockchain serverless functions that send data to custom destinations. Furthermore, Streams ensures guaranteed data delivery with retries on delivery failures. We have implemented error handling using try-catch blocks in our Function's code. This means that if something goes wrong with Discord, our Stream will attempt redelivery via Function, and any errors will also be logged on the Streams dashboard.

Now, let's create the Function to Stream blockchain data to Discord. To do so, go to the QuickNode Functions editor and paste the following code:

// Import the axios library for making HTTP requests
const axios = require('axios');

// Discord webhook URL for sending messages
const DISCORD_WEBHOOK_URL = 'YOUR_DISCORD_CHANNEL_WEBHOOK_URL';

// Maximum number of embeds allowed per Discord message
const MAX_EMBEDS = 10;

/**
* Creates a Discord embed object for a single stream data item
* @param {Object} item - The stream data item
* @returns {Object} - Discord embed object
*/
function createEmbed(item) {
return {
title: `Stream on ${item.network || 'Ethereum Network'}`,
fields: [
{ name: 'Amount', value: `${(BigInt(item.amount) / BigInt(1e18)).toString()} ETH`, inline: true },
{ name: 'Block Number', value: item.blockNumber || 'N/A', inline: true },
{ name: 'Spender', value: item.spender || 'N/A', inline: true },
{ name: 'Token', value: item.token || 'N/A', inline: true },
{ name: 'Transaction Hash', value: item.transactionHash || 'N/A', inline: false },
{ name: 'Wallet', value: item.wallet || 'N/A', inline: true },
],
color: 3447003, // A nice blue color
};
}

/**
* Sends stream data to Discord as one or more messages
* @param {Array} data - Array of stream data items
* @returns {Promise<string>} - A message indicating the number of Discord messages sent
*/
async function sendToDiscord(data) {
try {
const messages = [];
// Split data into chunks of MAX_EMBEDS size
for (let i = 0; i < data.length; i += MAX_EMBEDS) {
const chunk = data.slice(i, i + MAX_EMBEDS);
messages.push({
content: `New Stream Data (Batch ${Math.floor(i / MAX_EMBEDS) + 1})`,
embeds: chunk.map(createEmbed),
});
}

// Send all messages concurrently
const results = await Promise.all(messages.map(message =>
axios.post(DISCORD_WEBHOOK_URL, message)
));

console.log(`Sent ${results.length} messages to Discord`);
return `Sent ${results.length} messages to Discord`;
} catch (error) {
console.error('Error sending to Discord:', error.response ? error.response.data : error.message);
throw error;
}
}

/**
* Main function to process incoming data and send it to Discord
* @param {Object} params - Parameters passed to the function
* @param {Array} params.data - Array of stream data items
* @returns {Object} - Result of the operation
*/
async function main(params) {
console.log('Starting main function with params:', JSON.stringify(params, null, 2));

try {
const { data } = params;

// Validate input
if (!Array.isArray(data)) {
throw new Error('Invalid input: data is not an array');
}

if (data.length === 0) {
return {
message: "No data to send to Discord",
result: "No messages sent.",
};
}

// Send data to Discord
const result = await sendToDiscord(data);
console.log('Discord send result:', result);
return {
message: "Streams data sent to Discord",
result: result,
processedTransactions: data.length
};
} catch (error) {
console.error('Error in main function:', error.message);
return {
error: "Failed to send data to Discord",
details: error.message,
};
}
}

// Export the main function for use in QuickNode
module.exports = { main };

Replace YOUR_DISCORD_CHANNEL_WEBHOOK_URL with your Discord channel's webhook URL.

Learn how to get a Discord Channel's webhook URL
  1. Open Discord and go to the server where you want to receive the messages.

  2. Right-click on the channel where you want the messages to appear and select Edit Channel.

  3. In the channel settings, click on Integrations in the left sidebar.

  4. Click on the Create Webhook button.

  5. Give your webhook a name and optionally set an avatar image.

  6. Click on the Copy Webhook URL button. This is the URL you'll use in your code.

  7. Click Save Changes to create the webhook.

The above script takes data from QuickNode Stream, extracts it, and sends the Stream data into a Discord channel.

Set up a QuickNode Stream

For our example, we'll stream token approval data from the Ethereum mainnet. But, with Streams, you can do all kinds of things, from getting raw data to highly granular and enriched data; the possibilities are endless. You can check some examples in our backfill library.

The following is the Streams Filter being used:

Try it yourself by deploying the filtered Stream to your account.

function extractAddress(paddedAddress) {
if (typeof paddedAddress !== 'string' || paddedAddress.length < 40) {
console.error('Invalid address format:', paddedAddress);
return null;
}
return '0x' + paddedAddress.slice(-40);
}

function hexToDecimal(hexValue) {
if (typeof hexValue !== 'string') {
console.error('Invalid hex value:', hexValue);
return '0';
}
// Remove '0x' prefix if present
hexValue = hexValue.replace(/^0x/, '');
// If the resulting string is empty, return '0'
if (hexValue === '') {
return '0';
}
// Parse the hex string to a BigInt (to handle large numbers)
try {
return BigInt('0x' + hexValue).toString();
} catch (error) {
console.error('Error converting hex to decimal:', error);
return '0';
}
}

function findAndFilterLogs(data) {
const targetTopic = "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925";
let results = [];
let signatureFound = false;

function search(item) {
if (Array.isArray(item)) {
item.forEach(search);
} else if (typeof item === 'object' && item !== null) {
if (item.topics && item.topics[0] === targetTopic) {
signatureFound = true;
try {
const wallet = extractAddress(item.topics[1]);
const spender = extractAddress(item.topics[2]);
if (wallet && spender) {
results.push({
token: item.address,
wallet: wallet,
spender: spender,
amount: hexToDecimal(item.data),
blockNumber: hexToDecimal(item.blockNumber),
transactionHash: item.transactionHash
});
}
} catch (error) {
console.error('Error processing log entry:', error);
}
} else {
Object.values(item).forEach(search);
}
}
}

search(data);
return signatureFound ? results : null;
}

function main(data) {
const filteredData = findAndFilterLogs(data);
console.log(JSON.stringify(filteredData, null, 2));
return filteredData;
}

Now, while selecting a destination for your QuickNode Stream select the previously created Function as the destination for your Stream and create the Stream. Once the Stream starts streaming the data to Function, the Function will take the data and push to a Discord channel. It'll look something like this:

Data in Discord channel

Learn more about QuickNode Functions.

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Share this doc