Chapter 05 · Blocks
Blocks
Anti-Snipe, Auto Burn, Nth-buy Pot and Halt Guard: parameters, limits, execution order.
Blocks are optional rules a creator switches on at launch. They are stored in the hook per pool, validated once by Blocks.validate, and can never be changed, added or removed afterwards. Four exist: Anti-Snipe, Auto Burn, Nth-buy Pot and Halt Guard. A bit mask selects them; each has a handful of parameters with hard ranges.
The config
struct Config {
uint8 mask; // ANTI_SNIPE = 1, AUTO_BURN = 2, NTH_BUY_POT = 4, HALT_GUARD = 8
uint32 snipeWindowBlocks; // AntiSnipe
uint16 snipeMaxBuyBps; // AntiSnipe
uint16 snipeTaxBps; // AntiSnipe
uint16 burnBps; // AutoBurn
uint16 potBps; // NthBuyPot
uint32 potN; // NthBuyPot
uint128 potMinBuy; // NthBuyPot, raw quote units
uint32 haltMaxStaleness; // HaltGuard, seconds
}
validate rejects unknown mask bits and, for every block, requires its fields to be inside the range when the block is on and exactly zero when it is off. The web app mirrors the same rules in validate() and zeroes disabled fields in normalize() before sending the transaction, so a config that passes in the browser passes on chain.
Execution order on a buy
Blocks only act on buys. Sells are never capped, taxed, counted or refused. The order is fixed in _beforeSwap and _afterSwap:
Anti-Snipe
For snipeWindowBlocks blocks after launch, two things happen on every buy: the cumulative quote bought in the current block must stay under a cap, and an extra fee is charged and credited to the creator.
| Parameter | Range | Default | Meaning |
|---|---|---|---|
snipeWindowBlocks | 1 to 50,000 blocks (≈ 0.5 s each on Arc) | 100 (≈ 50 s) | Window length counted from the launch block. Active while block.number < launchBlock + window. |
snipeMaxBuyBps | 1 to 1,000 bps of opening FDV | 50 (0.5 %) | Per-block cumulative cap. snipeCapQuote = fdvQuote × bps / 10,000 is fixed at register in raw quote units. |
snipeTaxBps | 0 to 5,000 bps | 4,000 (40 %) | Extra fee on the quote input while the window is open. Goes to creatorFees, not the protocol. |
The cap is cumulative per block and per pool, not per address: boughtInBlock[id][block.number] + quoteIn must not exceed snipeCapQuote, otherwise the buy reverts OverSnipeCap. It never trims the order down. Because the cap is expressed as a share of the opening FDV in quote units, it means the same thing for a USDC pool and a cirBTC pool. Arc's blocks are about 0.5 s apart, so a window of 100 blocks is under a minute; size the window in seconds first and divide by two.
Auto Burn
After every buy, burnBps of the token output is taken from the PoolManager and burned through LaunchToken.burn, reducing totalSupply. The buyer receives tokensOut − burned; the event already reflects that.
| Parameter | Range | Default | Meaning |
|---|---|---|---|
burnBps | 1 to 1,000 bps of tokensOut | 100 (1 %) | Outputs below 2 wei are skipped. A zero burn amount after rounding is skipped. |
Auto Burn does not touch sells and does not change the price directly; it shrinks the supply that exists outside the pool. The burn is an ERC-20 supply reduction inside LaunchToken, never a native transfer, so Arc's rule against sending value to address(0) does not apply. The indexer keeps pool.tokensBurned.
Nth-buy Pot
A share of every buy's quote input goes into a pot. A public counter advances once per block for buys that qualify; every potN-th advance wins the whole pot. Winnings are claim-backed: they move to potOwed[id][winner] and the winner (only the winner) calls claimPot(id, to).
| Parameter | Range | Default | Meaning |
|---|---|---|---|
potBps | 1 to 500 bps of quoteIn | 50 (0.5 %) | Taken on every buy, qualifying or not. Zero in the dust branch. |
potN | ≥ 2 | 500 | Every Nth qualifying buy wins. The count is per pool and never resets. |
potMinBuy | any raw quote units | set per quote in the Builder | A buy below this fills the pot but does not advance the counter. 10 USDC is 10_000_000; 0.0001 cirBTC is 10_000. |
Three rules keep the pot honest:
- One advance per block.
potLastBlock[id]stops a single transaction, or a bundle, from advancing the counter several times in one block to land on N. - The recipient wins. The counter records the swap's recipient as reported by a trusted router (
hookData), ortx.originotherwise. A zero recipient never qualifies. - The counter is public. Anyone can read
potCountandpoton chain or in the indexer. Whoever makes the Nth qualifying buy wins; there is no randomness and no hidden state. That also means Arc'sPREVRANDAO, which is always 0, is never consulted.
Halt Guard
Refuses buys while the quote's oracle is unusable: feed stale beyond haltMaxStaleness, non-positive answer, or the quote token reporting paused(). Sells always pass, so holders can exit during a halt. It applies equally to USDC and cirBTC pools: for USDC it is mostly a Circle-pause guard, for cirBTC it also catches a stalled BTC / USD feed.
| Parameter | Range | Default | Meaning |
|---|---|---|---|
haltMaxStaleness | 1 to 604,800 seconds | 90,000 (25 h) | Both Chainlink feeds on Arc have a 24 h heartbeat, so 90,000 s leaves an hour of slack. |
The read is registry.quoteHalted(quote, haltMaxStaleness), detailed in Quotes and the oracle.
Gas and drag
The Builder shows an estimated gas figure (base 240k plus roughly 45k for Halt Guard, 20k for Anti-Snipe, 45k for the pot and 35k for Auto Burn) and a nominal buy drag of 80 bps plus the pot share, plus the snipe tax while the window is open. At Arc's 20 to 100 gwei base fee (paid in USDC) a fully loaded buy costs well under $0.05 in gas. Both figures are client-side estimates; the chain is the truth.
contracts/src/blocks/Blocks.solConfig, limits, validate()contracts/src/HookarcHook.sol_beforeSwap (HaltGuard, AntiSnipe, pot), _autoBurn, claimPotcontracts/test/Blocks.t.solvalidation and each block in isolationcontracts/test/invariant/BlocksInvariant.t.solburn only shrinks supply; pot counter monotoneweb/lib/blocks.tsthe browser mirror of validate() and normalize()