# v4 events and accounting

One trade on a Signal v4 pool emits logs from several contracts. They describe the same trade from different sides. Adding them up double-counts it.

## The logs of one swap

| Emitter | Event | What it says |
| --- | --- | --- |
| PoolManager | `Swap(id, sender, amount0, amount1, sqrtPriceX96, liquidity, tick, fee)` | **The pool's own amounts.** On a buy, the pair asset that reached the pool after the hook's fee. On a sale, the pair asset the pool paid out before the hook's fee |
| Hook | `FeeTaken(id, buy, fee, tax, sniperSurplus)` | What the hook charged this swap, in the pair asset. `tax` and `sniperSurplus` are parts of `fee`, not additions to it |
| Token and pair asset | `Transfer` | The actual movements, including router hops |

```solidity
event FeeTaken(bytes32 indexed id, bool buy, uint256 fee, uint256 tax, uint256 sniperSurplus);
// topic 0: 0xcb06e9b550297e242ebbed1ca909965e3afe67f426041dd9785b3db02f2320ad
```

`sniperSurplus` is everything above the base rate: the opening fee's part above the base, plus the size and deviation extras. `fee − sniperSurplus` is the base fee, of which `tax` is the buy or sell tax's part.

## What the trader paid and received

| | Pair asset, all in | Tokens |
| --- | --- | --- |
| Buy | pool's pair-asset amount from `Swap` **+** `FeeTaken.fee` | pool's token amount from `Swap` |
| Sale | pool's pair-asset amount from `Swap` **−** `FeeTaken.fee` | pool's token amount from `Swap` |

A price computed from `Swap` alone is the pool's price. It is the right price for a chart of the pool. It is the wrong figure for what a user paid during a 90% opening, when the pool's side of a buy is a tenth of what left the wallet. Pair each `Swap` with the `FeeTaken` of the same pool in the same transaction.

**A pair asset that taxes its own transfers.** The table holds when the pair asset moves the amount it is sent. On an asset that keeps a cut of its own transfers (PSTR), `Swap` and `FeeTaken` are amounts at the PoolManager: a buyer's wallet sent more and a seller's wallet received less, by that asset's cut, and a trade in native HYPE passed through that asset's own market first. Use the wallet's own `Transfer` logs and native value for what the trader paid and received.

**A swap whose fee is zero emits no `FeeTaken`:** every swap of a feeless pool, and on other pools a side with a zero rate or a dust trade. Match a `FeeTaken` to its `Swap` by its `buy` flag; a `Swap` without one paid no hook fee.

**Not every PoolManager swap is a trade.** The hook's own buyback swaps the pool too, inside a flush. It is paid from collected fees and pays no fee. Recognise it by `BoughtBack` in the same transaction and by `Swap.sender` being the hook, and count it as a burn, not as volume.

Use `(chainId, transactionHash, logIndex)` as an event identity. Keep block number and hash, handle removed logs, and replay overlapping ranges safely. Keep raw amounts when a dollar price is not available; unpriced is not zero.

## Payout events

All emitted from the hook's address. `Flushed`, `LegPaid`, `VestReleased`, `BoughtBack` and `BidAdded` are declared in the flush library the hook runs; their ABI is [SignalV4FlushEvents](v4-deployments.md#interfaces). `PotWon`, `PayoutOwed` and `OwedPaid` are in the hook ABI.

```solidity
event Flushed(bytes32 indexed id, uint256 protocol, uint256 creator, uint256 holders,
              uint256 burn, uint256 liquidity, uint256 floor);
event LegPaid(bytes32 indexed id, address indexed to, uint256 amount);
event PotWon(bytes32 indexed id, address indexed winner, uint256 amount, uint32 buyIndex);
event VestReleased(bytes32 indexed id, uint256 amount);
event BoughtBack(bytes32 indexed id, uint256 quoteSpent, uint256 tokensBurned);
event BidAdded(bytes32 indexed id, bytes32 indexed kind, int24 tpLo, int24 tpHi,
               uint256 quoteSpent, uint128 liquidity);
event PayoutOwed(address indexed recipient, address indexed quote, uint256 amount);
event OwedPaid(address indexed recipient, address indexed quote, uint256 amount);
```

| Event | Topic 0 |
| --- | --- |
| `Flushed` | `0x21d45e0e958d8381a22206495c35193dca4fa5516feee5301d328babeb018335` |
| `LegPaid` | `0x4ef82e18e70727bf3ad29af492a007429cee27018a072f1003552ba174791bdc` |
| `PotWon` | `0x7bc36abca2ee894f2b9688d1c90e60652bb224b5f1501d57233054fc8dc8b10d` |
| `VestReleased` | `0xf3d005fc3a61626ee18bc1c922e9be51f3716de22962b46c6478a165cf25c187` |
| `BoughtBack` | `0x1dc1aa2bd126e6ae3065d1fff6e005f0e3aed517938496a623174a12b73924a8` |
| `BidAdded` | `0xcac05c2ab979d98c688dc578bbbffbd309a1eb81e6fded88ce655a0de0b892b7` |
| `PayoutOwed` | `0x8891c9ea04d540f2768c4fafb2942f8cab15fefcafe4b16d9e08bd494557afb6` |
| `OwedPaid` | `0xb62a184d3f60e5d527e834fd6d66873cec28c9ac5c34a0b1c0f102520583187f` |

Reading them:

- `Flushed` reports what one flush sent or set aside, in the pair asset. `creator` is only what went to `creatorPayout` in this flush: the pot's slice, the co-recipients (`LegPaid`) and anything placed in a vest (`VestReleased`, later) are not in it, so its fields do not sum to the fees taken on a pool with a pot, co-recipients or a vest. `burn` and `liquidity` are amounts set aside for the buyback and the bid, spent in the same flush or a later one (`BoughtBack` a slice at a time, `BidAdded`).
- A divided amount is not always a delivered amount. A payout the asset refuses becomes `PayoutOwed` and stays in the hook until `OwedPaid`. For "what reached a wallet", follow the pair asset's `Transfer` logs from the PoolManager, or pair `Flushed` with the absence of `PayoutOwed` for that recipient.
- `PotWon` fires inside a buy, not inside a flush: the pot is paid to the buy's recipient in the same transaction.

## A holders token's events

A holders token receives its share at a flush and pays it out itself:

```solidity
event DividendFundsQueued(uint256 amount, uint256 burnedOwnTokens);
event DividendStreamStarted(uint256 amount, uint256 start, uint256 finish);   // five minutes
event DividendClaimed(address indexed holder, uint256 amount);
event DividendPushed(address indexed holder, uint256 amount);
```

Money queued or streaming is owed, not paid. Count `DividendClaimed` and `DividendPushed` as payments. `withdrawableDividendOf(holder)` is what a holder can claim now. The PoolManager, the hook, the dead address and the token itself earn nothing, and a stream starts only once at least 0.1% of the supply is in other hands.

## Changes of destination and identity

| Event | Meaning |
| --- | --- |
| `SplitProposed(id, split, activatesAt)`, `LegsProposed(id, legs, activatesAt)` | The launcher announced a new division of the fee. Nothing changes until it is activated |
| `SplitActivated(id, split)`, `LegsActivated(id, legs)` | It is now in force. Fees collected before were divided under the old one |
| `ChangeCancelled(id, split)` | The launcher withdrew a proposal |
| `ModuleProposed`, `ModuleAttested`, `ModuleActivated`, `ModuleCancelled` | A module pool's implementation life cycle |
| Token `IdentitySet(name, symbol)` | The launcher changed the token's name and ticker |

## Factory events worth watching

`PausedSet(bool)` closes or reopens a factory for new launches. `QuoteSet(...)` admits, updates or closes a pair asset for future launches. `ProtocolBpsSet(uint16)` changes Signal's share for future launches. None of them touches a launched pool.
