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);
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.