Meteora DLMM organizes liquidity in discrete price bins. A swap can cross bins and incur dynamic fees, so instruction arguments alone do not describe its final execution. Use the program layout to decode the request and execution data to determine the fill.
Select the DLMM program and instruction version
Meteora DLMM, DAMM, and Dynamic Bonding Curve are separate programs. Verify the program address and instruction layout using the official DLMM SDK. Filter your gRPC subscription, then validate each instruction's resolved program ID.
A subscription filter only selects candidate transactions. Another program in the same transaction can have similar-looking bytes. Resolve loaded addresses and visit inner instructions before decoding routed swaps.
Decode the classic exact-input swap body
The function below handles the classic Anchor swap body: an eight-byte discriminator followed by two u64 arguments. It intentionally rejects other layouts. Run it as decode.mts with node --import tsx decode.mts after installing tsx.
import { createHash } from 'node:crypto';
import assert from 'node:assert/strict';
const discriminator = createHash('sha256').update('global:swap').digest().subarray(0, 8);
function decodeDlmmSwap(data: Uint8Array) {
const bytes = Buffer.from(data);
if (bytes.length !== 24 || !bytes.subarray(0, 8).equals(discriminator)) return null;
return {
amountInRaw: bytes.readBigUInt64LE(8).toString(),
minimumAmountOutRaw: bytes.readBigUInt64LE(16).toString(),
};
}
const fixture = Buffer.alloc(24);
discriminator.copy(fixture);
fixture.writeBigUInt64LE(123n, 8);
fixture.writeBigUInt64LE(100n, 16);
assert.deepEqual(decodeDlmmSwap(fixture), {
amountInRaw: '123', minimumAmountOutRaw: '100',
});
assert.equal(decodeDlmmSwap(Buffer.alloc(24)), null);
This synthetic test establishes the function's byte-level behavior. It does not validate every live instruction version. Unknown discriminators should become observable unsupported records, not silently misdecoded swaps.
Attribute the fill and direction separately
minimumAmountOutRaw is a constraint, not actual output. Inspect the relevant transfers or decoded events and account for fees and other instructions. Preserve the raw integers until you know both mint decimals.
Classify direction relative to the token being displayed. A pool's X/Y ordering does not universally mean buy/sell, and quote tokens are not limited to SOL. The September platform updates expanded quote-token coverage across several launchpad markets; hardcoded SOL assumptions can miss supported pools.
Bin price needs token-decimal adjustment
For the standard signed bin ID convention, the raw bin ratio is (1 + binStep / 10000) ** binId. A human-unit Y-per-X quote also needs the X/Y decimal adjustment. Do not subtract an arbitrary large offset from an already signed bin ID.
Use the protocol SDK's price helpers for production calculations. Large positive or negative bin IDs and floating-point arithmetic can create precision or range problems. A bin price is also not the execution price of a swap that crosses several bins.
Liquidity actions are a separate data model
Adding, removing, and initializing positions do not share the swap layout. If your goal is an LP activity feed, use liquidity history and streams, which preserve token amounts and separate principal from transfer semantics.
FAQ
Does this handle swap2 or exact-output variants?
No. Dispatch those to the matching current IDL layout instead of reusing this decoder.
Why is my bin-derived price wrong by powers of ten?
Check mint decimals and price orientation before changing the bin formula.
Can a minimum-output field be used as trade volume?
No. It is a user constraint, not a realized fill.
References
Companion project
The companion example contains a fuller project layout. Check the companion dependencies and bundled program layouts against the versions described here before use. Keep credentials in a local environment file, never in a shared browser workspace.