DlmmProIDL). Use this page to build indexers, analytics and monitoring from transaction data.
Delivery
Most events use Anchor event CPI. The program calls itself with an inner instruction whose data is an 8-byte event tag (e445a52e51cb9a1d in hex), the 8-byte event discriminator and the Borsh-encoded fields. Instructions that emit this way include the event_authority account (the standard Anchor PDA, seed __event_authority) and the program account. Inner instructions are not truncated by the log size limit.
Two events, EvtClaimProtocolFee and EvtInitializeBitmapExtension, are written to the transaction log as Program data: lines instead. The runtime can truncate logs, so either line can be missing from the transaction that produced it.
Anchor’s EventParser reads only log lines, so it does not see event CPI events. See Reading Events.
The discriminator column is the 8-byte event discriminator from the IDL, which is the first 8 bytes of the SHA-256 hash of
event: followed by the event name. For an event CPI instruction, bytes 0 to 8 of the instruction data are the event tag, bytes 8 to 16 are the discriminator and the rest is the Borsh-encoded event. For a program log event, base64-decode the Program data: line: the first 8 bytes are the discriminator and the rest is the Borsh-encoded event. Borsh writes integers little-endian, Option as a 1-byte flag followed by the value when the flag is 1, and Vec as a u32 length followed by the items, so an indexer in any language can decode events without Anchor.
Tick arrays created inside open_position, expand_position or open_limit_order emit no EvtInitializeTickArray, so do not rely on that event to discover tick arrays.
Reading Events
The example decodes both kinds from one transaction. It uses the exportedDlmmProIDL and DlmmProTypes with @coral-xyz/anchor 0.32.
name and data. Event and field names are camel case in the TypeScript coder (evtSwap, currentSqrtPrice) and as in the IDL in the tables below.
Pool and Config Events
EvtInitializePool
Emitted byinitialize_pool_with_static_config, initialize_pool_with_dynamic_config and initialize_launch_pool once the pool, its two reserve vaults and its oracle exist. Indexers use it to discover pools and to read the settings fixed at creation. pool_type tells which instruction produced it: 0 for the static config instruction, 1 for the dynamic config instruction and 2 for the launch pool instruction. For the static config instruction pool_fees, tick_spacing, collect_fee_mode, activation_type and extensions are copied from the StaticConfig, activation_point is the current slot or timestamp, and pool_activation is always None. For the other two instructions the fields echo the instruction arguments, and pool_activation is Some only when extensions has the ActivationAccess bit.
EvtCreateStaticConfig
Emitted bycreate_static_config. Indexers use it to list the configs that pools can be created from. A pool_creator_authority equal to the default key (all zeros) marks a public config that anyone can use.
EvtCreateDynamicConfig
Emitted bycreate_dynamic_config. Indexers use it to list the configs whose pool creator authority picks fees and activation per pool. Pools created from the config record pool_creator_authority as their partner and take partner_fee_bps as their partner fee share.
EvtCloseConfig
Emitted byclose_config for a static or a dynamic config. Indexers use it to mark the config as closed. The event does not say which config type it was, so look the address up in the configs seen earlier.
EvtSetPoolStatus
Emitted byset_pool_status when an operator changes the pool status. Indexers use it to know when swaps or deposits are switched off. The instruction fails if the new status equals the current one, so every event is a change.
EvtUpdatePartnerFee
Emitted byupdate_partner_fee on a pool that has a partner, which means a pool created from a dynamic config. Indexers use it to track the partner share of the protocol fee that later swaps apply.
EvtClaimPartnerFee
Emitted byclaim_partner_fee when at least one of the two amounts is non-zero. Indexers use it to track partner fee payouts against the partner_fee totals of EvtSwap.
EvtClaimProtocolFee
Emitted byclaim_protocol_fee2 when the claimed amount is non-zero. It is a program log event, so it can be missing from the transaction that produced it. The instruction pays one token per call and is signed by the protocol fee authority, so integrators do not send it. Indexers use the event to track protocol fee payouts against the protocol_fee totals of EvtSwap.
Swap Events
EvtSwap
Emitted by every successfulswap, after the token transfers. Indexers use it for trades, volume, price, fees and the current tick. The fee fields are all in the fee token, the token named by fees_on_input and fees_on_token_x. Together mm_fee, limit_order_fee, protocol_fee, host_fee and partner_fee add up to the whole trading fee the swap charged.
amount_in is the whole input_amount, including the input mint’s transfer fee and the trading fee. amount_out is what the user receives, after the output mint’s transfer fee. amount_left is always 0, because a swap consumes its whole input or fails. The event does not name the trader; read the user account of the swap instruction.
Tick Array and Oracle Events
EvtInitializeTickArray
Emitted byinitialize_tick_array only when the call creates the tick array account. Calling it for an array that exists emits nothing. Tick arrays created inside open_position, expand_position or open_limit_order emit no event, so this event does not list every tick array.
EvtInitializeBitmapExtension
Emitted byinitialize_tick_bitmap_extension only when the call creates the extension, and by the three pool creation instructions when a bitmap extension account is passed. It is a program log event, so it can be missing from the transaction. Indexers use it to know that the pool can hold tick arrays outside indexes -512 to 511. The is_bitmap_extension_initialized field of the Pool account gives the same answer without relying on logs.
EvtIncreaseOracleLength
Emitted byincrease_oracle_length after the oracle account grows. Indexers use it to know how many observations the oracle ring buffer can hold.
Position Events
EvtOpenPosition
Emitted byopen_position after the position account and its NFT exist. Indexers use it to create the position and to learn its range slots, because the account itself is only decoded on demand. Slot i of the position is element i of tick_ranges. Tick arrays created by the call emit no event.
EvtExpandPosition
Emitted byexpand_position after new range slots are added. The signer is the owner or a delegate with the UpdatePositionRange permission. The new ranges are appended after the existing slots, so the first new slot has the index equal to the slot count before the call. The event does not carry that count, so keep it from earlier events or read the position account. The call also raises the position layout_version. Tick arrays created by the call emit no event.
EvtRemovePosition
Emitted byremove_position after empty range slots are removed. The signer is the owner or a delegate with the UpdatePositionRange permission. The remaining slots keep their order and move down to fill the gaps, so slot indexes after the removed ones change. The call also raises the position layout_version.
EvtClosePosition
Emitted byclose_position after the position account is closed and its NFT is burned. The position must be empty in every slot. Indexers use it to mark the position as closed.
EvtAddLiquidity
Emitted byadd_liquidity, add_liquidity_by_weight and add_liquidity_imbalance after the deposit. The event does not say which of the three ran, so read the discriminator of the parent instruction. In every case bitmap selects range slots and liquidities lists one amount per set bit in ascending slot order. For add_liquidity they are the arguments. For add_liquidity_by_weight each entry is the multiplier times the u8 weight of its slot. For add_liquidity_imbalance the bitmap has exactly one bit, the index argument, and the single entry is the liquidity the program derived from the two maximum amounts, the smaller of the two results. Indexers use it to track the liquidity of each slot.
amount_x and amount_y are what the signer pays, transfer fee included.
EvtRemoveLiquidity
Emitted byremove_liquidity and remove_liquidity_by_bps after the withdrawal. The event does not say which of the two ran, so read the discriminator of the parent instruction. For remove_liquidity bitmap and liquidities are the arguments. For remove_liquidity_by_bps the program computes them after releasing vested liquidity: each selected slot contributes its unlocked liquidity times bps divided by 10000, rounded down, and slots that round to zero are left out. The bps value is not in the event.
In
EvtRemoveLiquidity and EvtClaimFee, amount_x and amount_y are what leaves the reserves, before any transfer fee.
EvtClaimFee
Emitted byclaim_fee on every call, including a call with nothing to pay. The amounts are the pending swap fees summed over every range slot of the position, which the instruction pays out and resets. Indexers use it to track fees paid to liquidity providers.
EvtLockPermanent
Emitted bylock_permanent_position. The owner must sign. After releasing vested liquidity, the instruction moves bps of the unlocked liquidity of every range slot into permanent lock, where it stays in range and keeps earning fees but can never be removed. Indexers use it to separate locked from removable liquidity.
EvtLockVesting
Emitted bylock_vesting_position. The owner must sign, and a position can have one vesting schedule in its lifetime. The instruction moves parameters.bps of the unlocked liquidity of every range slot into the schedule, which then releases it after a cliff and in periods. Indexers use it to follow the release schedule.
EvtUpdatePositionDelegatePermission
Emitted byupdate_position_delegate_permission, signed by the NFT owner. It records the permission bitmask a delegate has on the position. It does not record the approval itself, which is a Token 2022 approve on the NFT token account. delegate shows who is approved at the time of the call.
Limit Order Events
EvtOpenLimitOrder
Emitted byopen_limit_order after the limit order account and its NFT exist and the deposit is made. Indexers use it to create the order and to learn its ticks. Tick arrays created by the call emit no event. There is no event when a swap fills the order. See Limit Order Fills.
EvtCancelLimitOrder
Emitted bycancel_limit_order, which cancels the ticks listed in tick_ids and pays the owner. This is the only event that shows how much of an order was filled, and only as totals over the canceled ticks, so cancel one tick per call to get a per-tick result. The signer is the owner or a delegate with the CancelLimitOrder permission.
owner_amount_x and owner_amount_y are the totals paid to the owner and already include owner_fee_x and owner_fee_y. referral_fees_on_token_x says which token referral_fee was paid in and only matters when referral_fee is above 0.
EvtCloseLimitOrder
Emitted byclose_limit_order after the limit order account is closed and its NFT is burned. Every tick of the order must be canceled first. Indexers use it to mark the order as closed.
EvtUpdateLimitOrderDelegatePermission
Emitted byupdate_limit_order_delegate_permission, signed by the NFT owner. It records the permission bitmask a delegate has on the limit order. The approval itself is a Token 2022 approve on the NFT token account and emits nothing.
Reward Events
EvtInitializeReward
Emitted byinitialize_reward when a reward slot of a pool with the LiquidityMining extension is set up. Indexers use it to learn the reward mints and who may fund them. It starts no emission.
EvtSetRewardEmission
Emitted byset_reward_emission after the funder pays the reward for the whole window into the reward vault. Indexers use it to know when a reward runs and at what rate.
EvtUpdateRewardAuthority
Emitted byupdate_reward_authority. Indexers use it to track who can fund each reward slot. The old authority is not in the event.
EvtClaimReward
Emitted byclaim_reward when the paid amount is non-zero. The amount is the pending reward of one reward slot summed over every range slot of the position. Indexers use it to track reward payouts.
EvtWithdrawIneligibleReward
Emitted bywithdraw_ineligible_rewards when the amount is non-zero. The reward authority recovers the reward that was emitted while the pool had no liquidity, once the emission has ended.
Nested Types
Types used inside the events above.PoolFeeParameters
Fee settings of a pool. Carried byEvtInitializePool and EvtCreateStaticConfig.
BaseFeeConfigParameters
Base fee schedule insidePoolFeeParameters. The layout of data for each mode is on the Accounts page.
DynamicFeeParameters
Dynamic fee settings insidePoolFeeParameters. The dynamic fee grows with recent price movement.
PoolActivationParameters
Activation access settings of a pool with the ActivationAccess extension. Carried byEvtInitializePool.
PoolActivationInfoParameters
One activation slot insidePoolActivationParameters. The layout of data for each mode is on the Accounts page.
LockVestingPositionParameters
Vesting schedule oflock_vesting_position. Carried by EvtLockVesting. Times and periods are in slots or seconds, following the activation_type of the pool.
TickRange
Price range of one position slot. Carried byEvtOpenPosition, EvtExpandPosition and EvtRemovePosition. The range holds liquidity while the pool’s current tick id is at or above the lower id and below the upper id.
TickLimitOrderAmount
Deposit of a limit order into one tick. Carried byEvtOpenLimitOrder.
Indexing Notes
The tables above give the fields. These notes cover what an indexer needs beyond them.Reconstructing Pool Price and Liquidity
- Start from
EvtInitializePool. The pool begins atinitial_sqrt_price(Q64.64, atoms of token Y per atom of token X). Its current tick id is the highest tick id whose price is at or below that price, which the SDK helpertickIdFromSqrtPricecomputes. - After that, take the price and tick from the latest
EvtSwap:post_sqrt_priceandpost_tick_id. Usepost_tick_idas the tick id instead of recomputing it from the price, because right after a swap crosses a tick the pool records the tick below it. The UI price is the square ofpost_sqrt_pricedivided by 2 to the power 128, times 10 to the power of the difference between the X and Y decimals. EvtAddLiquidity,EvtRemoveLiquidityandEvtClaimFeecarrycurrent_sqrt_price, the price when the instruction ran, so they give extra price samples between swaps. No other event moves the price.- Pool liquidity is the sum of the liquidity of every position slot whose range holds the current tick id, meaning
tick_lower_idis at or below it andtick_upper_idis above it. Limit orders do not count toward it. - Keep the liquidity of every slot per position. Slots come from
EvtOpenPosition(slotiis elementioftick_ranges), grow withEvtExpandPosition(new ranges are appended) and shrink withEvtRemovePosition(the bits ofbitmapname the removed slots, and the others shift down in order). Add eachliquiditiesentry ofEvtAddLiquidityto the slot named by the matching bit ofbitmap, and subtract each entry ofEvtRemoveLiquiditythe same way. EvtLockPermanentandEvtLockVestingmove liquidity between the unlocked, locked and vesting buckets of a slot. They do not change the slot’s total, so they do not change pool liquidity. Releases from a vesting schedule emit nothing and also do not change the total.- A swap changes pool liquidity only by crossing ticks, and the events do not list the ticks crossed. Recompute liquidity from the slot sums above at the new
post_tick_id, or read theliquidityfield of thePoolaccount.
State Changes That Emit No Event
- Tick arrays created inside
open_position,expand_positionoropen_limit_order.EvtInitializeTickArraycomes only frominitialize_tick_arraywhen it creates the account. To find the tick arrays of a pool, read the accounts or derive them from the slot ranges and limit order ticks: the array index of a tick id is the id divided by the tick spacing and then by 128, each rounded down. - Ticks and tick array space released by
close_position,remove_positionandclose_limit_order. They shrink tick arrays and return rent, and emit only their own events. - The oracle update inside every
swap. The first swap starts the oracle, and later swaps add the previous tick weighted by the seconds elapsed, opening a new observation once 15 seconds have passed since the latest one was created. Read theOracleaccount for observations.EvtIncreaseOracleLengthcovers only the length. - Fee and reward accrual. A swap adds the liquidity provider fee to the pool’s fee growth and updates reward growth. A position’s pending fees and rewards are brought up to date, with no event, by the add and remove liquidity instructions and by
refresh_fees_and_rewards.claim_feeandclaim_rewardpay what is already pending. Only the claim events show amounts that left the pool. - Activation state. A BondingCurve activation slot flips to reached after the swap that reaches its target price, and TimeBased slots open as time passes. Neither emits an event.
- Delegate approvals and NFT transfers. They are Token 2022 operations.
EvtUpdatePositionDelegatePermissionandEvtUpdateLimitOrderDelegatePermissionrecord permission bits only. Theownerin each event is the owner of the NFT token account when the event was emitted, so read the token account to follow ownership. - The bitmap bits that mark tick arrays with liquidity. They flip inside swaps, liquidity changes and limit order calls.
create_operator_account,close_operator_accountandrefresh_fees_and_rewardsemit no event. Read the operator and position accounts to track them.
Attributing a Swap to a Trader
EvtSwap has no user field. The event is an inner instruction of the swap that emitted it, and it is emitted last, after the token transfers. Find that swap instruction (at the top level, or an inner instruction when a router calls it) and read its user account, the signer and the 12th account in IDL order (index 11). user authorizes the transfer of the input tokens. The output goes to user_token_out, which can be any token account of the output mint, so the receiver can differ from user. A transaction with several swaps on one pool has one event per swap, in the order the swaps ran.
Fees in Swap Events
- All fee fields of
EvtSwapare in the fee token.fees_on_inputandfees_on_token_xname it. In OutputToken mode the fee is on the output token, sofees_on_inputisfalseandfees_on_token_xistruewhen the user buys token X. In OnlyY mode the fee token is always token Y, so it is the input when the user sells token Y and the output when the user sells token X. protocol_feeis what is left for the protocol after the host and partner cuts. The host cut is at most 20 percent of the protocol fee, paid to thereferraltoken account during the swap, sohost_feeis 0 without one. The partner cut applies on pools that have a partner, which are pools created from a dynamic config, at the pool’spartner_fee_bps. It is taken from what is left after the host cut.- The protocol and partner amounts accrue in the pool. They leave it later in
EvtClaimProtocolFeeandEvtClaimPartnerFee. Liquidity provider fees (mm_fee) leave inEvtClaimFee, and limit order fees (limit_order_fee) leave inEvtCancelLimitOrderasowner_fee_x,owner_fee_yandreferral_fee. - The event does not carry the fee rate, which can change between swaps because of scheduled and dynamic fees. Work it out from the fee total and the amount the fee was charged on.
Limit Order Fills
A swap fills limit orders at the tick price when it reaches a tick whose orders hold the token the swap buys. No event is emitted per order or per tick for these fills.- In
EvtSwap,limit_order_feeabove 0 shows that limit orders were filled. A value of 0 does not prove the opposite, because the fee shares round down. The filled amounts are not in the event. They are part ofamount_inandamount_out, mixed with the market making fills. - The filled and unfilled parts of an order are settled when the owner cancels it.
EvtCancelLimitOrdergives the totals over the canceled ticks: the unfilled amount comes back in the order’s own token, the filled part comes back as the other token at the tick price, and the owner’s fee share is insideowner_amount_xandowner_amount_y. Cancel one tick per call if you need a per-tick split. - The proceeds of a filled order stay in the pool until the owner cancels the order.
EvtOpenLimitOrdershows the deposit,EvtCancelLimitOrdershows the payout, andEvtCloseLimitOrderends the order.
Log Events Can Be Truncated
EvtClaimProtocolFee and EvtInitializeBitmapExtension are written to the transaction log. When a transaction’s logs exceed the runtime log size limit, the runtime truncates them and these events can be missing, while event CPI events stay in the inner instructions. Do not treat a missing log event as proof that the action did not happen. Reconcile instead: the pool’s protocol_fee_x and protocol_fee_y fall by the claimed amount, and is_bitmap_extension_initialized on the pool (or the existence of the bitmap extension account) says whether the extension exists.
