Overview
Transaction monitoring lets you watch Solana transactions as they happen. Track swaps, transfers, success/failures, program calls, and token changes - all in real-time.
You’ll need: Run npm install @triton-one/yellowstone-grpc
first
Installation
npm install @triton-one/yellowstone-grpc
Complete Working Example
Here’s a ready-to-use transaction monitor that you can customize:
const Client = require ( "@triton-one/yellowstone-grpc" ). default ;
const { CommitmentLevel } = require ( "@triton-one/yellowstone-grpc" );
// Example program IDs - replace with programs you want to monitor
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ;
const SYSTEM_PROGRAM = "11111111111111111111111111111111" ;
// Connect to Yellowstone gRPC
const getClient = async () => {
let client = false ;
try {
client = new Client (
process . env . GRPC_ENDPOINT || "https://grpc.solanatracker.io" ,
process . env . GRPC_API_KEY ,
{
"grpc.max_receive_message_length" : 100 * 1024 * 1024 ,
}
);
const version = await client . getVersion ();
if ( version ) {
console . log ( "Connected to Yellowstone gRPC! Version:" , version );
return client ;
}
} catch ( e ) {
console . error ( "Failed to connect:" , e );
}
if ( ! client ) {
throw new Error ( "Failed to connect!" );
}
};
// Transaction statistics
const txStats = {
totalTransactions: 0 ,
successfulTx: 0 ,
failedTx: 0 ,
totalFees: 0 ,
totalComputeUnits: 0 ,
programCounts: new Map (),
lastReportTime: Date . now ()
};
// Calculate SOL balance changes
function calculateSolChanges ( meta ) {
const changes = [];
if ( ! meta . preBalances || ! meta . postBalances ) return changes ;
meta . preBalances . forEach (( preBalance , index ) => {
const postBalance = meta . postBalances [ index ] || 0 ;
const change = ( postBalance - preBalance ) / 1e9 ; // Convert lamports to SOL
if ( Math . abs ( change ) > 0.000001 ) { // Ignore tiny changes
changes . push ({
accountIndex: index ,
change: change
});
}
});
return changes ;
}
// Calculate token balance changes
function calculateTokenChanges ( meta ) {
const changes = [];
if ( ! meta . preTokenBalances || ! meta . postTokenBalances ) return changes ;
meta . preTokenBalances . forEach (( preBalance , index ) => {
const postBalance = meta . postTokenBalances [ index ];
if ( preBalance && postBalance && preBalance . mint === postBalance . mint ) {
const preAmount = preBalance . uiTokenAmount ?. uiAmount || 0 ;
const postAmount = postBalance . uiTokenAmount ?. uiAmount || 0 ;
const change = postAmount - preAmount ;
if ( change !== 0 ) {
changes . push ({
mint: preBalance . mint ,
change ,
decimals: preBalance . uiTokenAmount ?. decimals || 0
});
}
}
});
return changes ;
}
( async () => {
const client = await getClient ();
const stream = await client . subscribe ();
// Handle stream lifecycle
const streamClosed = new Promise (( resolve , reject ) => {
stream . on ( "error" , ( error ) => {
console . error ( "Stream error:" , error );
});
stream . on ( "end" , () => {
console . log ( "Stream ended" );
resolve ();
});
stream . on ( "close" , () => {
console . log ( "Stream closed" );
resolve ();
});
});
// Handle incoming transactions
stream . on ( "data" , ( data ) => {
if ( data ?. transaction ?. transaction ) {
const tx = data . transaction . transaction ;
const slot = data . transaction . slot ;
txStats . totalTransactions ++ ;
const success = ! tx . meta ?. err ;
if ( success ) {
txStats . successfulTx ++ ;
} else {
txStats . failedTx ++ ;
}
const fee = tx . meta ?. fee || 0 ;
const computeUnits = tx . meta ?. computeUnitsConsumed || 0 ;
txStats . totalFees += fee ;
txStats . totalComputeUnits += computeUnits ;
// Track which programs were used
const accountKeys = tx . transaction ?. message ?. accountKeys || [];
const instructions = tx . transaction ?. message ?. instructions || [];
instructions . forEach (( ix ) => {
const programId = accountKeys [ ix . programIdIndex ];
if ( programId ) {
const count = txStats . programCounts . get ( programId ) || 0 ;
txStats . programCounts . set ( programId , count + 1 );
}
});
console . log ( ` \n [Transaction # ${ txStats . totalTransactions } ]` );
console . log ( ` Signature: ${ tx . signature } ` );
console . log ( ` Slot: ${ slot } ` );
console . log ( ` Status: ${ success ? '✅ SUCCESS' : '❌ FAILED' } ` );
console . log ( ` Fee: ${ ( fee / 1e9 ). toFixed ( 6 ) } SOL` );
console . log ( ` Compute Units: ${ computeUnits . toLocaleString () } ` );
console . log ( ` Instructions: ${ instructions . length } ` );
// Show SOL balance changes
const solChanges = calculateSolChanges ( tx . meta );
if ( solChanges . length > 0 ) {
console . log ( ` SOL Changes:` );
solChanges . forEach ( change => {
console . log ( `Account ${ change . accountIndex } : ${ change . change > 0 ? '+' : '' }${ change . change . toFixed ( 6 ) } SOL` );
});
}
// Show token balance changes
const tokenChanges = calculateTokenChanges ( tx . meta );
if ( tokenChanges . length > 0 ) {
console . log ( ` Token Changes:` );
tokenChanges . forEach ( change => {
console . log ( ` ${ change . mint . slice ( 0 , 8 ) } ... ${ change . change > 0 ? '+' : '' }${ change . change . toFixed ( 6 ) } ` );
});
}
// Show program IDs used
if ( accountKeys . length > 0 ) {
const uniquePrograms = new Set ();
instructions . forEach (( ix ) => {
const programId = accountKeys [ ix . programIdIndex ];
if ( programId ) uniquePrograms . add ( programId );
});
if ( uniquePrograms . size > 0 ) {
console . log ( ` Programs Called:` );
uniquePrograms . forEach ( program => {
const name = program === TOKEN_PROGRAM ? "Token Program"
: program === SYSTEM_PROGRAM ? "System Program"
: program . slice ( 0 , 12 ) + "..." ;
console . log ( ` - ${ name } ` );
});
}
}
// Show error details for failed transactions
if ( ! success && tx . meta ?. err ) {
console . log ( ` Error: ${ JSON . stringify ( tx . meta . err ) } ` );
if ( tx . meta ?. logMessages && tx . meta . logMessages . length > 0 ) {
console . log ( ` Last Log: ${ tx . meta . logMessages [ tx . meta . logMessages . length - 1 ] } ` );
}
}
// Report statistics every 5 minutes
const now = Date . now ();
if ( now - txStats . lastReportTime > 300000 ) {
const avgFee = txStats . totalTransactions > 0
? txStats . totalFees / txStats . totalTransactions / 1e9
: 0 ;
const successRate = txStats . totalTransactions > 0
? ( txStats . successfulTx / txStats . totalTransactions * 100 )
: 0 ;
console . log ( ` \n === Transaction Statistics Report ===` );
console . log ( ` Total Transactions: ${ txStats . totalTransactions } ` );
console . log ( ` Successful: ${ txStats . successfulTx } ` );
console . log ( ` Failed: ${ txStats . failedTx } ` );
console . log ( ` Success Rate: ${ successRate . toFixed ( 2 ) } %` );
console . log ( ` Total Fees: ${ ( txStats . totalFees / 1e9 ). toFixed ( 4 ) } SOL` );
console . log ( ` Average Fee: ${ avgFee . toFixed ( 6 ) } SOL` );
console . log ( ` Avg Compute Units: ${ ( txStats . totalComputeUnits / txStats . totalTransactions ). toFixed ( 0 ) } ` );
console . log ( ` \n Most Used Programs:` );
Array . from ( txStats . programCounts . entries ())
. sort (( a , b ) => b [ 1 ] - a [ 1 ])
. slice ( 0 , 5 )
. forEach (([ program , count ]) => {
const name = program === TOKEN_PROGRAM ? "Token Program"
: program === SYSTEM_PROGRAM ? "System Program"
: program . slice ( 0 , 12 ) + "..." ;
console . log ( ` ${ name } : ${ count } transactions` );
});
txStats . lastReportTime = now ;
}
}
});
// Subscribe to transactions
// Customize the accountInclude array with programs you want to monitor
const request = {
accounts: {},
slots: {},
transactions: {
allTransactions: {
vote: false ,
failed: false ,
signature: undefined ,
accountInclude: [ TOKEN_PROGRAM , SYSTEM_PROGRAM ], // Add your program IDs here
accountExclude: [],
accountRequired: [],
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined ,
commitment: CommitmentLevel . CONFIRMED ,
};
// Send subscribe request
await new Promise (( resolve , reject ) => {
stream . write ( request , ( err ) => {
if ( err === null || err === undefined ) {
console . log ( "✅ Monitoring transactions \n " );
resolve ();
} else {
reject ( err );
}
});
}). catch (( reason ) => {
console . error ( "Subscribe failed:" , reason );
throw reason ;
});
await streamClosed ;
})();
// Graceful shutdown
process . on ( 'SIGINT' , () => {
console . log ( ' \n\n Shutting down gracefully...' );
console . log ( `Final Stats - Total: ${ txStats . totalTransactions } , Success: ${ txStats . successfulTx } , Failed: ${ txStats . failedTx } ` );
process . exit ( 0 );
});
How to Filter Transactions
By Program
By Account
Advanced
Watch transactions using specific programs: const request = {
// ... other fields
transactions: {
myTransactions: {
vote: false ,
failed: false ,
accountInclude: [
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ,
"11111111111111111111111111111111"
],
accountExclude: [],
accountRequired: [],
}
},
commitment: CommitmentLevel . CONFIRMED ,
};
More Examples
Example 2: Track Failed Transactions
Find out why transactions fail:
const failureStats = {
total: 0 ,
errorTypes: new Map ()
};
( async () => {
const client = await getClient ();
const stream = await client . subscribe ();
const streamClosed = new Promise (( resolve , reject ) => {
stream . on ( "error" , ( error ) => console . error ( "Stream error:" , error ));
stream . on ( "end" , () => resolve ());
stream . on ( "close" , () => resolve ());
});
stream . on ( "data" , ( data ) => {
if ( data ?. transaction ?. transaction ?. meta ?. err ) {
const tx = data . transaction . transaction ;
const slot = data . transaction . slot ;
failureStats . total ++ ;
const errorType = JSON . stringify ( tx . meta . err );
const count = failureStats . errorTypes . get ( errorType ) || 0 ;
failureStats . errorTypes . set ( errorType , count + 1 );
console . log ( ` \n [Failed Transaction # ${ failureStats . total } ]` );
console . log ( ` Signature: ${ tx . signature } ` );
console . log ( ` Slot: ${ slot } ` );
console . log ( ` Error: ${ errorType } ` );
console . log ( ` Fee Paid: ${ ( tx . meta . fee / 1e9 ). toFixed ( 6 ) } SOL` );
// Log messages for debugging
if ( tx . meta ?. logMessages ) {
console . log ( ` Last 5 Log Messages:` );
tx . meta . logMessages . slice ( - 5 ). forEach (( msg ) => {
console . log ( ` ${ msg } ` );
});
}
// Report failure patterns every 20 failures
if ( failureStats . total % 20 === 0 ) {
console . log ( ` \n === Failure Analysis ===` );
console . log ( ` Total Failures: ${ failureStats . total } ` );
console . log ( ` \n Top Error Types:` );
Array . from ( failureStats . errorTypes . entries ())
. sort (( a , b ) => b [ 1 ] - a [ 1 ])
. slice ( 0 , 5 )
. forEach (([ error , count ]) => {
const percentage = ( count / failureStats . total * 100 ). toFixed ( 1 );
console . log ( ` ${ error } : ${ count } ( ${ percentage } %)` );
});
}
}
});
const request = {
accounts: {},
slots: {},
transactions: {
failedTransactions: {
vote: false ,
failed: true , // Only failed transactions
accountInclude: [ "YourProgramId" ],
accountExclude: [],
accountRequired: [],
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined ,
commitment: CommitmentLevel . CONFIRMED ,
};
await new Promise (( resolve , reject ) => {
stream . write ( request , ( err ) => {
if ( err === null || err === undefined ) {
console . log ( "✅ Monitoring failed transactions \n " );
resolve ();
} else {
reject ( err );
}
});
});
await streamClosed ;
})();
Example 3: Watch Big Transfers
Track large SOL transfers:
const HIGH_VALUE_THRESHOLD = 10 ; // 10 SOL
const highValueStats = {
totalMonitored: 0 ,
highValueCount: 0 ,
totalVolume: 0 ,
largestTransfer: 0 ,
largestSignature: ""
};
( async () => {
const client = await getClient ();
const stream = await client . subscribe ();
const streamClosed = new Promise (( resolve , reject ) => {
stream . on ( "error" , ( error ) => console . error ( "Stream error:" , error ));
stream . on ( "end" , () => resolve ());
stream . on ( "close" , () => resolve ());
});
stream . on ( "data" , ( data ) => {
if ( data ?. transaction ?. transaction ?. meta ) {
const tx = data . transaction . transaction ;
const slot = data . transaction . slot ;
highValueStats . totalMonitored ++ ;
const preBalances = tx . meta . preBalances || [];
const postBalances = tx . meta . postBalances || [];
// Calculate largest balance change
let maxChange = 0 ;
let changedAccountIndex = - 1 ;
preBalances . forEach (( preBalance , index ) => {
const postBalance = postBalances [ index ] || 0 ;
const change = Math . abs ( postBalance - preBalance );
if ( change > maxChange ) {
maxChange = change ;
changedAccountIndex = index ;
}
});
const changeInSOL = maxChange / 1e9 ;
if ( changeInSOL > HIGH_VALUE_THRESHOLD ) {
highValueStats . highValueCount ++ ;
highValueStats . totalVolume += changeInSOL ;
if ( changeInSOL > highValueStats . largestTransfer ) {
highValueStats . largestTransfer = changeInSOL ;
highValueStats . largestSignature = tx . signature ;
}
const accountKeys = tx . transaction ?. message ?. accountKeys || [];
const changedAccount = accountKeys [ changedAccountIndex ];
console . log ( ` \n [High-Value Transaction # ${ highValueStats . highValueCount } ]` );
console . log ( ` Signature: ${ tx . signature } ` );
console . log ( ` Slot: ${ slot } ` );
console . log ( ` 💰 Transfer Amount: ${ changeInSOL . toFixed ( 2 ) } SOL` );
console . log ( ` Fee: ${ ( tx . meta . fee / 1e9 ). toFixed ( 6 ) } SOL` );
console . log ( ` Changed Account: ${ changedAccount || 'Unknown' } ` );
// Report summary every 10 high-value transactions
if ( highValueStats . highValueCount % 10 === 0 ) {
console . log ( ` \n === High-Value Summary ===` );
console . log ( ` Monitored: ${ highValueStats . totalMonitored } transactions` );
console . log ( ` High-Value: ${ highValueStats . highValueCount } transactions` );
console . log ( ` Total Volume: ${ highValueStats . totalVolume . toFixed ( 2 ) } SOL` );
console . log ( ` Largest Transfer: ${ highValueStats . largestTransfer . toFixed ( 2 ) } SOL` );
}
}
// Log progress every 100 monitored transactions
if ( highValueStats . totalMonitored % 100 === 0 ) {
console . log ( ` \n [Progress] Monitored ${ highValueStats . totalMonitored } transactions, found ${ highValueStats . highValueCount } high-value` );
}
}
});
const request = {
accounts: {},
slots: {},
transactions: {
systemTransactions: {
vote: false ,
failed: false ,
accountInclude: [ "11111111111111111111111111111111" ], // System Program
accountExclude: [],
accountRequired: [],
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined ,
commitment: CommitmentLevel . CONFIRMED ,
};
await new Promise (( resolve , reject ) => {
stream . write ( request , ( err ) => {
if ( err === null || err === undefined ) {
console . log ( `✅ Monitoring transactions with transfers > ${ HIGH_VALUE_THRESHOLD } SOL \n ` );
resolve ();
} else {
reject ( err );
}
});
});
await streamClosed ;
})();
Understanding Filters
Include (OR) accountInclude: Transaction must touch ANY of these accounts
Required (AND) accountRequired: Transaction must touch ALL of these accounts
Exclude (NOT) accountExclude: Transaction must NOT touch any of these
How They Work Together Transaction passes if: (include OR empty) AND (all required) AND NOT (any excluded)
What You Can Build
Monitor DEX transactions to find trading opportunities, track competitors, or analyze market activity in real-time.
Watch specific wallets or programs to see every transaction they make. Perfect for portfolio tracking or whale watching.
Track failed transactions to debug your program, understand user issues, or monitor network problems.
Measure transaction volume, fees, and activity across programs to understand usage patterns and trends.
Get instant notifications for high-value transfers, specific program calls, or suspicious activity.
Pick Right Commitment CONFIRMED is usually best - fast but reliable
Filter Smart Use specific program IDs to reduce data
Process Async Don’t block - handle transactions asynchronously
Watch Resources Monitor your memory and CPU usage
Common Questions
How much data will I get?
It depends on your filters. Watching all transactions = huge data. Watching one program = manageable. Watching a specific wallet = very little. Always use filters!
Can I watch multiple programs at once?
Yes! Just add them all to accountInclude
. The transaction will match if it touches ANY of them.
What's the difference between Include and Required?
Include (OR): Transaction touches ANY of these accounts
Required (AND): Transaction touches ALL of these accounts
Use Include for “I want to see transactions involving A or B or C”
Use Required for “I only want transactions that involve both A and B”
Should I include failed transactions?
Usually no - they’re just noise. But set failed: true
if you’re debugging or analyzing failure patterns.
How do I track a specific wallet?
Add the wallet address to accountInclude
. You’ll see every transaction that touches that wallet.
Why do I see vote transactions?
Set vote: false
in your filter. Vote transactions are validator consensus votes and usually aren’t what you want.
Compute units measure how much computation a transaction used. More complex transactions use more compute units and usually cost more.
Next Steps