Account updates using memcmp
memcmp Account Filters - Subscribing to Updates with Known Byte Patterns
memcmp
filters allow you to match specific portions of binary data within accounts, helping you include only those account updates that meet specific criteria. This makes them especially useful for targeting specific states or values in program-owned accounts. For instance, you can use a memcmp
filter to track liquidity pool balances, monitor token ownership, or identify program-specific flags. A memcmp
filter typically specifies three key parameters: offset
, which defines the starting byte position in the account data to compare; bytes
, the value to match at the specified offset; and Encoding (optional), which determines how the bytes are encoded, such as base64 or base58.
import Client, {
CommitmentLevel,
SubscribeRequestAccountsDataSlice,
SubscribeRequestFilterAccounts,
SubscribeRequestFilterBlocks,
SubscribeRequestFilterBlocksMeta,
SubscribeRequestFilterEntry,
SubscribeRequestFilterSlots,
SubscribeRequestFilterTransactions,
} from "@triton-one/yellowstone-grpc";
import { SubscribeRequestPing } from "@triton-one/yellowstone-grpc/dist/grpc/geyser";
import { LIQUIDITY_STATE_LAYOUT_V4 } from "@raydium-io/raydium-sdk";
import bs58 from "bs58";
// Interface for the subscription request structure
interface SubscribeRequest {
accounts: { [key: string]: SubscribeRequestFilterAccounts };
slots: { [key: string]: SubscribeRequestFilterSlots };
transactions: { [key: string]: SubscribeRequestFilterTransactions };
transactionsStatus: { [key: string]: SubscribeRequestFilterTransactions };
blocks: { [key: string]: SubscribeRequestFilterBlocks };
blocksMeta: { [key: string]: SubscribeRequestFilterBlocksMeta };
entry: { [key: string]: SubscribeRequestFilterEntry };
commitment?: CommitmentLevel;
accountsDataSlice: SubscribeRequestAccountsDataSlice[];
ping?: SubscribeRequestPing;
}
/**
* Subscribes to the gRPC stream and handles incoming data.
*
* @param client - Yellowstone gRPC client
* @param args - The Subscription request which specifies what data to stream
*/
async function handleStream(client: Client, args: SubscribeRequest) {
const stream = await client.subscribe();
// Promise that resolves when the stream ends or errors out
const streamClosed = new Promise<void>((resolve, reject) => {
stream.on("error", (error) => {
console.error("Stream error:", error);
reject(error);
stream.end();
});
stream.on("end", resolve);
stream.on("close", resolve);
});
// Handle incoming transaction data
stream.on("data", (data) => {
if (data?.account) {
console.log("\nReceived Account Update for:");
console.log(bs58.encode(data?.account?.account?.pubkey));
console.log("\n");
console.log(data?.account);
}
});
// Send the subscription request
await new Promise<void>((resolve, reject) => {
stream.write(args, (err: any) => {
err ? reject(err) : resolve();
});
}).catch((err) => {
console.error("Failed to send subscription request:", err);
throw err;
});
// Wait for the stream to close
await streamClosed;
}
/**
* Entry point to start the subscription stream.
*
*/
async function subscribeCommand(client: Client, args: SubscribeRequest) {
while (true) {
try {
await handleStream(client, args);
} catch (error) {
console.error("Stream error, retrying in 1 second...", error);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
// Instantiate Yellowstone gRPC client with .env credentials
const client = new Client(
"YOUR-GRPC-ENDPOINT", //Your Region specific gRPC URL
"YOUR-ACCESS-TOKEN", // your Access Token
undefined,
);
/**
* Subscribe Request: The `account` field is for streaming account updates. The `filters`
* specify the conditions for the accounts to be streamed. The `owner` field specifies the
* that involve the specified address in `accountInclude`.
*/
const req: SubscribeRequest = {
slots: {},
accounts: {
sol_usdc: {
account: [],
filters: [
{
memcmp: {
offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('marketProgramId').toString(),
base58: "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"
}
}
],
owner: ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"]
},
},
transactions: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED, // Subscribe to processed blocks for the fastest updates
entry: {},
transactionsStatus: {},
};
// Start the subscription
subscribeCommand(client, req);
You can copy and paste this code on replit to see it in action, or simply remix this code on Replit.
For instance, this subscribe request streams updates for Raydium pool accounts with a marketProgramId
equal to serum. The filter targets the marketProgramId
field of the account, ensuring updates are streamed only for matching accounts.
Last updated
Was this helpful?