signaldocs Open Signal

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

EmitterEventWhat it says
PoolManagerSwap(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
HookFeeTaken(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 assetTransferThe 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 inTokens
Buypool's pair-asset amount from Swap + FeeTaken.feepool's token amount from Swap
Salepool's pair-asset amount from Swap − FeeTaken.feepool'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.

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);
EventTopic 0
Flushed0x21d45e0e958d8381a22206495c35193dca4fa5516feee5301d328babeb018335
LegPaid0x4ef82e18e70727bf3ad29af492a007429cee27018a072f1003552ba174791bdc
PotWon0x7bc36abca2ee894f2b9688d1c90e60652bb224b5f1501d57233054fc8dc8b10d
VestReleased0xf3d005fc3a61626ee18bc1c922e9be51f3716de22962b46c6478a165cf25c187
BoughtBack0x1dc1aa2bd126e6ae3065d1fff6e005f0e3aed517938496a623174a12b73924a8
BidAdded0xcac05c2ab979d98c688dc578bbbffbd309a1eb81e6fded88ce655a0de0b892b7
PayoutOwed0x8891c9ea04d540f2768c4fafb2942f8cab15fefcafe4b16d9e08bd494557afb6
OwedPaid0xb62a184d3f60e5d527e834fd6d66873cec28c9ac5c34a0b1c0f102520583187f

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

EventMeaning
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, ModuleCancelledA 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.