Last active
January 12, 2025 20:50
-
-
Save Leeyah-123/98031b6fbf39b1fb2570c92e1a7b2df1 to your computer and use it in GitHub Desktop.
QuickNode stream filter to filter out transactions we don't need and function to transform QuickNode Solana Programs+Logs Stream data to human-readable format
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function main(stream) { | |
const data = Array.isArray(stream.data) ? stream.data[0] : stream.data; | |
// Obtain addresses from KV Store | |
const addresses = qnGetList("watchlist_addresses"); | |
// Convert addresses array to Set for faster lookups | |
const addressSet = new Set(addresses?.map(addr => addr.toLowerCase()) || []); | |
const filteredData = data.filter(tx => { | |
// Check all accounts in all program invocations | |
return tx.programInvocations?.some(inv => | |
inv.instruction.accounts.some(acc => | |
addressSet.has(acc.pubkey.toLowerCase()) | |
) | |
); | |
}); | |
console.log(`Reduced data from ${data.length} tx(s) to ${filteredData.length} tx(s).`) | |
return filteredData; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import dependencies, learn more at: https://www.quicknode.com/docs/functions/runtimes/node-js-20-runtime | |
const axios = require('axios'); | |
const { format } = require('date-fns'); | |
const WEBHOOK_URL = "https://katlog.onrender.com/api/webhook"; | |
const SYSTEM_PROGRAM_ID = '11111111111111111111111111111111'; | |
const VOTE_PROGRAM_ID = 'Vote111111111111111111111111111111111111111'; | |
const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; | |
const TOKEN_2022_PROGRAM_ID = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; | |
/** | |
* Parses Solana Programs+Logs into a human-readable format | |
* @param {Array} data - The Solana Programs+Logs data | |
*/ | |
const processSolanaTransactions = (data) => { | |
return data | |
// Filter out Vote transactions | |
.filter(tx => !tx.programInvocations?.some(inv => | |
inv.programId === VOTE_PROGRAM_ID)) | |
// Map to desired format | |
.map(tx => { | |
const programInvocations = tx.programInvocations[0]; | |
// Get all account changes from program invocations | |
const accounts = programInvocations.instruction.accounts.filter(acc => | |
acc.preBalance !== acc.postBalance); | |
// Format timestamp | |
const date = new Date(tx.blockTime * 1000); | |
const formattedDate = format(date, 'MMM d, yyyy HH:mm:ss'); | |
// Find the main from/to accounts based on balance changes | |
let from, to; | |
// Determine transaction type based on program ID | |
let type = 'unknown'; | |
const programId = programInvocations?.programId; | |
const instruction = programInvocations?.instruction | |
switch (programId) { | |
case SYSTEM_PROGRAM_ID: | |
type = 'SOL Transfer'; | |
break; | |
case TOKEN_PROGRAM_ID || TOKEN_2022_PROGRAM_ID: | |
type = 'Token Transfer'; | |
break; | |
default: | |
type = 'Other'; | |
} | |
let amount; | |
if (type === 'SOL Transfer') { | |
from = instruction.data.info.source; | |
to = instruction.data.info.destination; | |
// For native SOL transfers | |
amount = Math.abs(instruction.data.info.lamports) / 1e9; // Convert lamports to SOL | |
} else if (type === 'Token Transfer') { | |
from = accounts.find(acc => acc.preBalance > acc.postBalance)?.pubkey || ''; | |
to = accounts.find(acc => acc.postBalance > acc.preBalance)?.pubkey || ''; | |
// For token transfers, check token balances | |
const tokenTransfer = programInvocations?.find(inv => | |
inv.instruction.tokenBalances?.some(tb => | |
tb.uiTokenAmount?.uiAmount > 0 | |
) | |
); | |
if (tokenTransfer?.instruction.tokenBalances) { | |
const tokenAmount = tokenTransfer.instruction.tokenBalances.find(tb => | |
tb.uiTokenAmount?.uiAmount > 0 | |
); | |
amount = tokenAmount?.uiTokenAmount?.uiAmount || 0; | |
} | |
} | |
return { | |
signature: tx.signature, | |
programId, | |
type, | |
from, | |
to, | |
amount, | |
success: tx.success, | |
timestamp: formattedDate, | |
}; | |
}); | |
}; | |
/** | |
* Sends data to the specified webhook | |
* @param {Object} data - The data to send to the webhook | |
*/ | |
async function sendToWebhook(data) { | |
try { | |
const response = await axios.post(WEBHOOK_URL, data, { | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
}); | |
console.log('Data sent to webhook successfully'); | |
return response.data; | |
} catch (error) { | |
if (axios.isAxiosError(error)) { | |
console.error('Error sending data to webhook:', error.response?.data || error.message); | |
} else { | |
console.error('Error sending data to webhook:', error); | |
} | |
throw error; | |
} | |
} | |
/** | |
* main(params) is invoked when your Function is called from Streams or API. | |
* | |
* @param {Object} params - May contain a dataset (params.data), params.metadata, and params.user_data | |
* | |
* @returns {Object} - A message that will be logged and returned when a Function is called via API. | |
* Tip: You can chain Functions together by calling them inside each other. | |
* Learn more at: https://www.quicknode.com/docs/functions/getting-started#overview | |
*/ | |
async function main(data) { | |
const parsedData = processSolanaTransactions(data); | |
if (parsedData.length > 0) { | |
// Prepare payload for webhook | |
const payload = JSON.stringify({ | |
data: parsedData, | |
}); | |
sendToWebhook(payload) | |
.then(() => console.log(`Sent batch of ${parsedData.length} txs to webhook`)) | |
.catch(err => console.log("Unable to send txs to webhook: ", err)); | |
} | |
// Return anything that you will consume on API side or helping you check your execution later | |
return { | |
message: `Processed batch of ${parsedData.length} txs`, | |
processedTransactions: parsedData.length, | |
successfulTransactions: parsedData.reduce((currentValue, { success }) => { | |
if (success) currentValue++; | |
return currentValue; | |
}, 0) | |
}; | |
} | |
// Find more examples at https://github.com/quiknode-labs/awesome-functions/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment