Build with Signal
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 |
event FeeTaken(bytes32 indexed id, bool buy, uint256 fee, uint256 tax, uint256 sniperSurplus);
// topic 0: 0xcb06e9b550297e242ebbed1ca909965e3afe67f426041dd9785b3db02f2320adsniperSurplus 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. PotWon, PayoutOwed and OwedPaid are in the hook ABI.
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:
Flushedreports what one flush sent or set aside, in the pair asset.creatoris only what went tocreatorPayoutin 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.burnandliquidityare amounts set aside for the buyback and the bid, spent in the same flush or a later one (BoughtBacka slice at a time,BidAdded).- A divided amount is not always a delivered amount. A payout the asset refuses becomes
PayoutOwedand stays in the hook untilOwedPaid. For "what reached a wallet", follow the pair asset'sTransferlogs from the PoolManager, or pairFlushedwith the absence ofPayoutOwedfor that recipient. PotWonfires 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:
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.