Skip to content

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

EventMeaningMain fields
PoolInitializedA direct or curve pool was initializedContext, creator, pool ID, mints, vaults, hook program, tick spacing, configured fee, mode and initial square-root price
SwapExecutedOne underlying swap settled, including swaps inside zapsContext, actor, actor token accounts, direction, ExactOut flag, effective fee, protocol fee, prices, current tick, active liquidity and outcome
LiquidityChangedPosition liquidity changedContext, actor, owner, position, bounds, signed delta, amounts, resulting position liquidity and graduation flag
PositionFeesCollectedAccrued LP fees were paidPool, position, owner and actual amounts
ProtocolFeesCollectedProtocol debt was paid to its recipientPool, recipient, mints and amounts
ProtocolTreasuryInitializedThe protocol recipient was boundRecipient

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

FieldUnit and interpretation
outcome.amountInActual gross input debited, in input-mint atoms; includes trading fee
outcome.amountOutActual output credited to the actor, in output-mint atoms; excludes the separate hook output fee
outcome.feeAmountTrading fee in input-mint atoms
protocolFeeAmountProtocol share of that trading fee, including fractional carry across swaps/steps; never an added trader charge
outcome.outputFeeAmountSeparate hook output fee in output-mint atoms
outcome.amountUnspentInput budget left with the actor, including ExactOut maximum-input remainder
feePpmEffective trading fee for this swap, including a hook override
sqrtPriceBeforeX64, outcome.sqrtPriceAfterX64Raw 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 statusIndexer action
completePersist the decoded events, possibly an empty list for an instruction without an event
failedPreserve transaction failure; publish no successful protocol events
unavailableMissing metadata or logs; mark the coverage gap
incompleteTruncated logs or an unclosed invocation; apply no partial batch
unsupportedUnknown/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.