On-chain events
Decode authenticated pool initialization, swaps, liquidity changes and fee collection from grapl transaction logs.
The core emits Anchor Program data: logs after the operation's settlement and relevant after-hook have succeeded. The pinned IDL defines each event discriminator and Borsh payload. An event can still appear in a transaction that later fails: require a successful finalized receipt before applying it.
Event catalog
| Event | Meaning | Main fields |
|---|---|---|
PoolInitialized | A direct or curve pool was initialized | Context, creator, pool ID, mints, vaults, hook program, tick spacing, configured fee, mode and initial square-root price |
SwapExecuted | One underlying swap settled, including swaps inside zaps | Context, actor, actor token accounts, direction, ExactOut flag, effective fee, protocol fee, prices, current tick, active liquidity and outcome |
LiquidityChanged | Position liquidity changed | Context, actor, owner, position, bounds, signed delta, amounts, resulting position liquidity and graduation flag |
PositionFeesCollected | Accrued LP fees were paid | Pool, position, owner and actual amounts |
ProtocolFeesCollected | Protocol debt was paid to its recipient | Pool, recipient, mints and amounts |
ProtocolTreasuryInitialized | The protocol recipient was bound | Recipient |
PoolInitialized, SwapExecuted and LiquidityChanged carry context.version = 1, the pool address and its operation sequence. Fee collection does not advance that sequence. Deduplicate all events using (genesis hash, program ID, transaction signature, log index), not sequence alone.
A curve emits PoolInitialized with mode = 1. Graduation emits LiquidityChanged with graduation = true and preserves the pool address. Its amounts are existing curve reserves allocated to the creator's CLMM position, not a new wallet deposit. Ordinary add/remove events have graduation = false; the sign of liquidityDelta distinguishes them.
This catalog does not emit a separate event for every instruction. Position open/close, tick-array allocation, donations, beneficiary claims, and curve remainder claims still require instruction/account inspection. Do not infer that these operations did not happen from the absence of a typed event.
Swap amounts and prices
| Field | Unit and interpretation |
|---|---|
outcome.amountIn | Actual gross input debited, in input-mint atoms; includes trading fee |
outcome.amountOut | Actual output credited to the actor, in output-mint atoms; excludes the separate hook output fee |
outcome.feeAmount | Trading fee in input-mint atoms |
protocolFeeAmount | Protocol share of that trading fee, including fractional carry across swaps/steps; never an added trader charge |
outcome.outputFeeAmount | Separate hook output fee in output-mint atoms |
outcome.amountUnspent | Input budget left with the actor, including ExactOut maximum-input remainder |
feePpm | Effective trading fee for this swap, including a hook override |
sqrtPriceBeforeX64, outcome.sqrtPriceAfterX64 | Raw B/A square-root prices in Q64 fixed point |
aToB = true means input mint A and output mint B. Otherwise the direction reverses. Obtain the mints from the validated pool catalog. The human B-per-A spot price is (sqrtPriceX64 / 2^64)^2 × 10^(decimalsA-decimalsB); use rational or decimal arithmetic rather than converting raw amounts to JavaScript Number. Execution price can differ because of fees and traversal. Events do not contain a USD price or trusted metadata.
actor is the authority authenticated by the core. Under a router it can be a PDA or another intermediary. It is not an assertion of the transaction fee payer, original buyer wallet, or beneficial owner.
Decode a finalized receipt
import { decodeTransactionEvents } from "@grapl/sdk";
import type { Address } from "@solana/kit";
export function readTrades(
programAddress: Address,
meta: { err: unknown; logMessages: readonly string[] | null },
) {
const batch = decodeTransactionEvents(programAddress, meta);
if (batch.status !== "complete") return batch;
return batch.events.filter(({ event }) => event.name === "SwapExecuted");
}The decoder tracks the runtime invocation stack. It accepts core-owned data logs when the program is called directly or through CPI, ignores identical logs emitted by a hook, and drops events from failed nested invocations even when an outer router catches the error. Event decoding does not fetch or establish finality; the caller supplies the receipt from the expected chain at the required commitment.
| Decode status | Indexer action |
|---|---|
complete | Persist the decoded events, possibly an empty list for an instruction without an event |
failed | Preserve transaction failure; publish no successful protocol events |
unavailable | Missing metadata or logs; mark the coverage gap |
incomplete | Truncated logs or an unclosed invocation; apply no partial batch |
unsupported | Unknown/malformed event or invocation structure; preserve the receipt for investigation/replay |
These are log events, not emit_cpi! events. Providers can truncate logs. Complete receipt logs, retained transaction history and an explicit coverage record are required for reliable analytics. Account rescans recover current discovery, not lost swap history. Older deployed binaries do not gain these events retroactively.