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.

Block configuration: from the Builder to frozen stateThe same validation rules run in the browser and in Blocks.validate. After register() nothing can change them.
Builderoff-chain, no writesvalidate() · normalize()web/lib/blocks.ts mirrorlaunchWithBlocks(p, cfg)msg.value = 1 USDCBlocks.validate(cfg)BadBlockConfig on any bad fieldregister(): blockConfig[id] = cfgfrozen for the life of the poolsnipeCapQuote = fdvQuote × maxBuyBps / 10,000cap in raw quote units, per blockBlocksSet(id, mask, cap)indexed into pool.blockMask

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:

Blocks on one buyHaltGuard and AntiSnipe run first and can only revert. The pot fills and counts before the swap; AutoBurn acts on the output after it.
yesyesnonoyesyesno · record buynoyesyesnoyesnonoyesnobuy enters beforeSwapHALT_GUARDin mask?quoteHalted(quote,haltMaxStaleness)?revert QuoteHaltedANTI_SNIPE andblock < launch + window?boughtInBlock + in> snipeCapQuote?revert OverSnipeCap_buyFees · mint claimsprotocol · creator · potNTH_BUY_POTin mask?pot += potBps sharein ≥ potMinBuy · recipient ≠ 0first advance this block?count++ · PotAdvancedcount % potN== 0?PotWon → potOwed[winner]swap executesAUTO_BURNin mask?burn burnBps of tokensOutAutoBurned(id, tokens)HookSwap emitted

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.

ParameterRangeDefaultMeaning
snipeWindowBlocks1 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.
snipeMaxBuyBps1 to 1,000 bps of opening FDV50 (0.5 %)Per-block cumulative cap. snipeCapQuote = fdvQuote × bps / 10,000 is fixed at register in raw quote units.
snipeTaxBps0 to 5,000 bps4,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.

ParameterRangeDefaultMeaning
burnBps1 to 1,000 bps of tokensOut100 (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).

ParameterRangeDefaultMeaning
potBps1 to 500 bps of quoteIn50 (0.5 %)Taken on every buy, qualifying or not. Zero in the dust branch.
potN≥ 2500Every Nth qualifying buy wins. The count is per pool and never resets.
potMinBuyany raw quote unitsset per quote in the BuilderA 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), or tx.origin otherwise. A zero recipient never qualifies.
  • The counter is public. Anyone can read potCount and pot on chain or in the indexer. Whoever makes the Nth qualifying buy wins; there is no randomness and no hidden state. That also means Arc's PREVRANDAO, 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.

ParameterRangeDefaultMeaning
haltMaxStaleness1 to 604,800 seconds90,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.

read the code
  • contracts/src/blocks/Blocks.sol Config, limits, validate()
  • contracts/src/HookarcHook.sol _beforeSwap (HaltGuard, AntiSnipe, pot), _autoBurn, claimPot
  • contracts/test/Blocks.t.sol validation and each block in isolation
  • contracts/test/invariant/BlocksInvariant.t.sol burn only shrinks supply; pot counter monotone
  • web/lib/blocks.ts the browser mirror of validate() and normalize()