Skip to content
montra

For builders

The anti-snipe fee

A pool may launch with an anti-snipe fee, the contracts' early-buy fee: for at most 30 minutes from its launch, a buy pays a rate falling in a straight line to the pool's own fee. Read each pool's terms, track its window and show the rate.

A buy's rate: at most 99% at launch, falling in a straight line

the pool's fee, at most 10%

Launch≤ 30 min

earlyBuyFeeUntil

and every second after

A buy
Pays the falling ratePays the pool's fee
A sale
Pays the pool's fee, throughout

Not to scale. The creator's first buy, inside the launch transaction, pays the pool's fee. A pool launched without an anti-snipe fee has no window: every swap is as after it.

Figure 1. A pool's anti-snipe window, its start and length each pool's own.

Pointers

  • Each pool sets its own window, or none. Never assume either: read earlyBuyFeeUntil, earlyBuyFeeStartPips and earlyBuyFeeWindow from the chain or the API. On chain earlyBuyFeeUntil is a Unix second; the API gives it in milliseconds. Detect it.
  • The chain's clock decides. A window is open while the latest block's time is before earlyBuyFeeUntil, and a buy pays the rate of the second it lands. Detect it.
  • A buy prices from buyEffectiveTotalFee. taxBps() and sellTaxRate() are a sale's rate; a buy's rate now is buyTaxRate(), currentBuyFeeRate(poolId) or the lens's buyFee.
  • Show the live buy rate, with a countdown to earlyBuyFeeUntil on the chain's clock. A buy above the pool's fee is worth a confirmation, and auto-buy and sniping features read the rate, then skip or cap. Show it.
  • The rate keeps falling. Quote from the latest block, send at once and keep the deadline short: a minimum taken from a quote seconds old is looser than its percentage says. Slippage on a falling rate.
  • Swap alone understates a buy. Inside a window v4's Swap shows up to a hundred times less than the buyer paid, so count from Swap and FeeAccrued together, and expire any high-buy-tax flag at earlyBuyFeeUntil. Events and indexing.

What it is

"The pool's fee" on this page is a Montra pool's total fee, what every trade pays outside a window: the hook's totalFeePips (the lens's totalFee), in pips, 1,000,000 being 100%, at most 10%. The hook takes it in the quote asset, the asset the token is paired with. It is not the pool key's fee field, which is only v4's dynamic-fee flag.

Some pools launch with an anti-snipe fee, the early-buy fee in the contracts. From the second the pool is registered, in its launch, a buy pays more than the pool's fee: at most 99% in that second, falling in a straight line, counted in whole seconds, to the pool's own fee within at most 30 minutes. It makes taking the supply at the open expensive, and costs a later buyer less each second. Sales never pay it, and neither does the creator's first buy inside the launch transaction. What it takes above the pool's fee goes to the pool's creator side, whoever the pool's creator fees pay; Montra's part stays the pool's own protocol share of the trade. The terms are fixed in the launch and the rate never rises.

On a token whose fees go to its holders, that extra goes to them too, released over the next hour like any fee. So a large early buyer who keeps holding earns part of it back: one with a third of the tokens outside the pool earns about a third, for as long as it holds that share. The rest goes to the other holders, and one who sells earns no more from then on.

A launch sent through Elysium's inbox takes the time it was posted as its block's, so the window counts from then, not from when the launch runs, and its first seconds may pass before anyone can trade. A short window loses the most.

An example, a pool whose fee is 1%, with a 99% start falling over 5 minutes, minute by minute. "Times the pool's price" is what a buy pays for each token against the pool's price, 1 / (1 − fee).

Scroll sideways for every column

A 99% start over 5 minutes on a 1% poolA buy paysTimes the pool's price
In the launch second99%100×
1 minute after79.4%4.9×
2 minutes after59.8%2.5×
3 minutes after40.2%1.7×
4 minutes after20.6%1.26×
5 minutes after1%1.01×

Each pool's own terms

The start and the window are terms of the row a pool launches under, stamped into the pool in its launch. Which configurations launches are offered is Montra's choice and may change, there may be several, and a pool with none has no window at all. So read every pool's own terms and never hard-code one. These bounds are the hook's, and nothing can lift them:

The start
At most 99%
In the launch second, the pool's fee included (MAX_EARLY_BUY_FEE_PIPS). Always above the pool's fee.
The window
At most 30 min
From the launch to earlyBuyFeeUntil (MAX_EARLY_BUY_FEE_WINDOW), at least a second.
The rate
Only falls
A later second pays no more than an earlier one. The pool's fee lowered inside the window lowers it too.
Sales
Never
A sale pays the pool's fee, in the window and after it.

Montra's launch form today offers creators a 99% start falling over 5 seconds, 10 seconds, 30 seconds, 1 minute, 5 minutes, 15 minutes or 30 minutes, on the pairs with rows for them. That is the form's list, not a rule of the pools: it may change, and a pool launched under any other row carries that row's terms, within the bounds above.

Some shapes the terms can take, to show the range. These are examples, not the configurations launches are offered: read a pool's from the chain or the API.

Scroll sideways for every column

Example termsAt 10 sAt 1 minAt 5 minAt 15 minAt 30 min
99% falling to a 1% pool fee over 30 seconds66.33%1%1%1%1%
99% falling to a 1% pool fee over 5 minutes95.73%79.4%1%1%1%
99% falling to a 1% pool fee over 30 minutes98.46%95.73%82.67%50%1%
50% falling to a 2% pool fee over 10 minutes49.2%45.2%26%2%2%

The rule, in pips and whole seconds: at second t before earlyBuyFeeUntil a buy pays B = S − ⌊(S − T)·(t − t₀) / W⌋, where S is the start, T the pool's fee now, W the window and t₀ = earlyBuyFeeUntil − W the launch; from earlyBuyFeeUntil on, it pays T. The drop rounds down, so the rate never falls below the straight line, and a lowered pool fee moves the end of the line. More under Contract reference.

Detect it

What

The window's end, the first second a buy pays the pool's fee
On chainhook.poolConfig(poolId).earlyBuyFeeUntil, a Unix second; zero for none
From the API (/api/tokens/{token})pool.earlyBuyFeeUntil, in milliseconds; zero for none
The start, what a buy paid in the launch second
On chainhook.poolConfig(poolId).earlyBuyFeeStartPips, pips
From the API (/api/tokens/{token})pool.earlyBuyFeeStartPips
The window's length
On chainhook.poolConfig(poolId).earlyBuyFeeWindow, seconds; the launch was earlyBuyFeeUntil − earlyBuyFeeWindow
From the API (/api/tokens/{token})pool.earlyBuyFeeWindow
What a buy pays now, the rate
On chainhook.currentBuyFeeRate(poolId) in pips, token.buyTaxRate() in basis points rounded up, the lens's buyFee
From the API (/api/tokens/{token})live.buyFee
What a buy pays all in now: the rate compounded with the LP fee in force, a hair under it once that fee is on, equal before. Price a buy from this
On chainThe lens's buyEffectiveTotalFee
From the API (/api/tokens/{token})live.buyEffectiveTotalFee, a string
What a sale pays
On chaintoken.sellTaxRate(), taxBps(), the lens's totalFee
From the API (/api/tokens/{token})pool.totalFeePips, live.totalFee
All of the schedule in one read
On chainLaunchLens.buyFeeSchedule(hook, poolId): start, end, the seconds it starts and ends, the rate now; all zero for none
From the API (/api/tokens/{token})

The window is open while the latest block's time is before earlyBuyFeeUntil, and over once block.timestamp ≥ earlyBuyFeeUntil. Count on the chain's clock, never the wall clock: a block's time never goes back, so once a mined block is past the end, every later one is too. Nothing is emitted when a window ends. Which pools are Montra's, by their hook, is under Find and trust a pool.

  1. In these samples client, wallet, account, lens, router, lensAbi and routerAbi are as under Read, quote and trade, token is the token's address, tokenAbi and hookAbi are LaunchToken.json and LaunchHook.json, and the PoolManager's ABI is PoolManager.json. viem returns a uint40 or a uint24 as a number, a uint256 and a block's time as a bigint.
    TypeScript
    // tokenView(address token) view returns (PoolView)
    const view = await client.readContract({
      address: lens, abi: lensAbi, functionName: "tokenView", args: [token],
    });
    const { timestamp } = await client.getBlock();      // the chain's clock, never the wall clock
    const until = BigInt(view.config.earlyBuyFeeUntil); // a Unix second; 0 for a pool with none
    const open = timestamp < until;                     // a buy pays the falling rate
    const secondsLeft = open ? until - timestamp : 0n;  // the countdown
    view.config.earlyBuyFeeStartPips;                   // what a buy paid in the launch second, in pips
    view.config.earlyBuyFeeWindow;                      // the window's length, in seconds
  2. The hook's own rate, the one a buy is charged this second, and the lens's all-in figure, which a price is made from. The token answers too, for a reader that reads only tokens.
    TypeScript
    // currentBuyFeeRate(bytes32 poolId) view returns (uint256)
    // in pips, the LP fee included
    const poolId = await client.readContract({ address: token, abi: tokenAbi, functionName: "poolId" });
    const buyPips = await client.readContract({
      address: view.key.hooks, abi: hookAbi, functionName: "currentBuyFeeRate", args: [poolId],
    });
    view.buyFee;               // the same rate, read by the lens (a number; buyPips is a bigint)
    view.buyEffectiveTotalFee; // the all-in figure: a buy pays 1e6 / (1e6 - this) times the pool's price
    view.totalFee;             // what a sale pays, and a buy from earlyBuyFeeUntil on
    
    // buyTaxRate() view returns (uint256)
    // a buy's rate in basis points, rounded up
    const buyBps = await client.readContract({ address: token, abi: tokenAbi, functionName: "buyTaxRate" });
  3. The same terms from the public API. pool.earlyBuyFeeUntil is in milliseconds: divide by 1,000 before comparing it with a block's time. The anti-snipe terms in pool never change, so read them once (pool.totalFeePips can still be lowered). live is the pool read from the chain and cached: a few seconds old as a rule, older when the API is busy, and null if the read failed. Inside a window a cached rate errs high, since the rate only falls, but take a buy's rate from the chain when you send it.
    Shell
    curl -s "https://api.montramarkets.xyz/api/tokens/$TOKEN" | jq '{
      until: .pool.earlyBuyFeeUntil,         # milliseconds; 0 for none
      start: .pool.earlyBuyFeeStartPips,     # pips
      window: .pool.earlyBuyFeeWindow,       # seconds
      buyFee: .live.buyFee,                  # pips: what a buy pays now
      buyAllIn: .live.buyEffectiveTotalFee,  # pips, as a string
      saleFee: .pool.totalFeePips            # pips
    }'

Show it

  • The buy rate, live. buyTaxRate(), currentBuyFeeRate(poolId) or buyFee, refreshed at least every block while the window is open, labelled as a buy's. Better still, the price it makes: a buy pays 1 / (1 − buyEffectiveTotalFee / 1e6) times the pool's price, a hundred times at 99%.
  • A countdown to earlyBuyFeeUntil, from the latest block's time. At zero, read the pool again: the buy rate is the pool's fee from then on, for good.
  • A confirmation above the pool's fee. Ask the user to accept a buy that pays more than the pool's fee before it goes out, with the tokens it gets and its price against the pool's; Montra's site asks above 10%. Auto-buy, copy-trade and sniping features fire exactly when the rate is highest: make them read currentBuyFeeRate(poolId) and skip or cap.
  • The sale rate, unchanged. sellTaxRate() and taxBps() report a sale's rate, the pool's fee, in the window and after it. They never show the anti-snipe fee, so a sale's figure never looks like a high sell tax.
  • Both directions apart. A scanner or a terminal that reads only taxBps() under-reports a buy inside a window, by up to the start less the pool's fee. Show a buy's rate beside a sale's while they differ; outside a window they are equal.
  • What buyers paid. A chart built from v4's Swap draws the pool's price. Inside a window a buyer paid up to a hundred times the Swap's quote amount: Events and indexing.
  • Re-read on a change. TotalFeeUpdated lowers the pool's fee and with it the rest of the line; LpFeeActivated changes the all-in figure, not the rate. Cached figures err high, since the rate never rises, so expire any high-buy-tax flag at earlyBuyFeeUntil rather than keep it.

Quote and trade in a window

A quote and a swap include the rate of the second they run in, as they include the rest of the pool's fee: LaunchRouter's quoteExactIn returns the output after that second's rate, and a buy pays the rate of the second it lands. Trading through the Universal Router is under Trade through the Universal Router.

  1. Quote at the latest block and send at once, with a minimum from that quote and a deadline a few seconds past when it should land. With nothing else trading, a buy that lands a second later pays no higher a rate and gets no less, so the fee's fall alone never trips the minimum. An ERC-20 quote asset needs an approval for the router first, as under Read, quote and trade.
    TypeScript
    // quoteExactIn(PoolKey key, bool zeroForOne, uint128 amountIn) returns (uint256)
    const zeroForOne = view.config.quoteIsCurrency0;   // a buy
    const { result: amountOut } = await client.simulateContract({
      address: router, abi: routerAbi, functionName: "quoteExactIn",
      args: [view.key, zeroForOne, amountIn],
    });
    const input = zeroForOne ? view.key.currency0 : view.key.currency1; // the quote, on a buy
    await wallet.writeContract({
      address: router, abi: routerAbi, functionName: "swapExactIn",
      args: [{
        key: view.key, zeroForOne, amountIn,
        minAmountOut: amountOut * 99n / 100n,          // 1% slippage on a quote from the latest block
        recipient: account.address,
        deadline: timestamp + 10n,                     // short: the later it lands, the looser the minimum
      }],
      value: input === zeroAddress ? amountIn : 0n,
    });

Slippage on a falling rate

What each second changes
As the seconds pass inside a window, more of an exact-input buy reaches the pool, a lot more early on. A minimum set from a quote some seconds old therefore sits further below what the buy delivers than its percentage says.
What it risks
Tokens, never more of the spend: an exact-input buy spends exactly what it names and its fee is a fixed share of that, so a price pushed up before it lands can cost it tokens, down to its minimum, and nothing else. The tokens are the user's money all the same, so keep the gap small.
What to do
Quote from the latest block, never ahead of it, and send at once; keep the deadline short; never cache a quote across seconds. A quote from a clock ahead of the chain overstates the output, and the buy then reverts on its minimum.

The real tolerance of a buy whose minimum is set 1% under its quote, when it lands some seconds after the quote: how far under what it then delivers that minimum sits, and how much more of its input reaches the pool. An example, a pool whose fee is 1%, with a 99% start falling over 5 minutes and no LP fee, for an order too small to move the price much.

Scroll sideways for every column

Quoted atLands 1 s laterLands 3 s laterLands 10 s later
The launch second25.4% (32.7% more in)50.0% (98.0% more in)76.8% (326.7% more in)
60 s2.5% (1.6% more in)5.5% (4.8% more in)14.6% (15.9% more in)
150 s1.6% (0.7% more in)2.9% (2.0% more in)7.1% (6.5% more in)

Routers, quoters and aggregators

  • Quotes include the rate. Uniswap's V4Quoter and LaunchRouter.quoteExactIn quote a buy at the rate of the block they are asked at, as they quote the rest of the fee, so a quote from an earlier second errs high on the fee.
  • Track it by the chain's clock. Read earlyBuyFeeUntil once per pool, since it never changes, and compare it with the latest block's time, never the wall clock.
  • Model it off-chain, exactly. An engine that prices pools without the chain needs the schedule, in integers as the hook rounds it, and the fee's split: Contract reference. Price at no later than the latest block's time. A model fitted from quotes inside a window misprices both during it and after it.
  • Price limits bind more often in a window. A swap that fills less than it names reverts (PartialFillRejected), and each second more of an exact-input buy's input reaches the pool, so a sqrtPriceLimitX96 set from an earlier quote binds sooner. On exact input set no price limit, as V4Router does, and protect the trade with a minimum output.
  • Nothing else is gated. No allowlist, no per-wallet rule, nothing read from hookData or the transaction's origin: in a given second and pool state every buyer pays the same rate (the creator's first buy, inside the launch, aside), and a quote matches the trade made against the same block.

After the window, and sales

From earlyBuyFeeUntil on, a pool trades like any Montra pool: every buy and sale pays the pool's fee, which never rises. A sale never pays the anti-snipe fee, so what follows applies to a sale at any time and to a buy after the window.

  • Exact output, capped by a maximum. The fee is the pool's fixed rate, grossed up (quoteIn × h / (1e6 − h), h being the hook's part), so a maximum taken from a V4Quoter quote is as tight as its slippage, and the Universal Router's amountInMaximum holds it: over it, the trade reverts V4TooMuchRequested.
  • Exact output, capped by a price limit. For a caller that swaps on the PoolManager itself, a sqrtPriceLimitX96 is a hard bound: the swap fills whole with the price never past the limit, or reverts whole (PartialFillRejected), so a buy pays no more per token than the limit's price with the fee on top, and a sale gets no less than it with the fee taken off. A swap whose limit the price is already at or past is refused by v4. The Universal Router passes the extreme limits, so through it a price limit never binds: amountInMaximum does, and a hop's minHopPriceX36 if set.
  • Exact input, protected by a minimum. As on any pool: a minimum output from the quote, no price limit.

Events and indexing

PoolRegistered
Carries the pool's earlyBuyFeeStartPips and earlyBuyFeeWindow in its registration, both zero for none. The window starts at the time of its block, the launch's, and ends earlyBuyFeeWindow seconds later, at earlyBuyFeeUntil.
FeeAccrued
Once per swap. Its fee is the hook's whole take in the quote, the anti-snipe premium included; protocolPart stays the protocol's own pips of the leg, and fee − protocolPart is the creator side's, the premium all in it.
Swap
v4's: the pool's side only. A buyer paid its quote amount plus FeeAccrued.fee, up to a hundred times that amount inside a window. Its fee field is v4's own swap fee, the LP fee (and Uniswap's protocol fee, should that ever be switched on), never the hook's.
TotalFeeUpdated
The pool's fee was lowered: inside a window the rest of the line now ends at the new fee. Re-read both rates.
LpFeeActivated
The LP fee switched on: the hook's part falls by it, the rate does not.
The window's end
No event. A buy pays the pool's fee from earlyBuyFeeUntil on; schedule a re-read for then.

Count a pool's volume from Swap, and what traders spent from Swap and FeeAccrued together, and say which you show: inside a window the two differ a hundredfold at 99%. The API's volumes are the pool's side, and a trade's quoteAmount is what the trader paid (Public API).

A worked example

One exact-input buy of 1 HYPE, sent at different moments into the window of a pool whose fee is 1%, with a 99% start falling over 5 minutes, below its LP line, so no LP fee: an example, not an offered configuration. The rate is the hook's at that second; the hook takes it off the top, Montra keeps its 0.4% of the leg and the rest is the creator side's; what is left reaches the pool and buys the tokens.

Scroll sideways for every column

Seconds after launchRateThe hook takesMontra's 0.4%The creator side'sReaches the poolTimes the pool's price
0, the launch second99%0.990.0040.9860.01100×
198.67%0.9867340.0040.9827340.01326675×
1095.73%0.9573340.0040.9533340.04266623×
6079.4%0.7940.0040.790.2064.9×
15050%0.50.0040.4960.52.0×
2991.33%0.0132670.0040.0092670.9867331.01×
300, earlyBuyFeeUntil1%0.010.0040.0060.991.01×

Questions

Does a sell ever pay it?
No. A sale pays the pool's fee, in the window and after it. That is what sellTaxRate() and taxBps() report.
What does the creator's first buy pay?
The pool's own fee. It is the factory's swap inside the launch transaction, the only buy exempt. Any later buy of the creator's pays the rate.
Can the fee rise?
No. The start and the window are fixed in the launch and nothing can change, extend or raise them. The rate only falls as the seconds pass, and the pool's fee lowered inside the window lowers it further.
What happens at the window's end?
From the second earlyBuyFeeUntil, a buy pays the pool's fee, for good. No event marks it: read the pool again, or schedule it.
How do I tell a pool with none?
earlyBuyFeeUntil is zero, and so are its start and window. Both rates read the same, and one integration covers both kinds of pool.

For integrators

Terminals, wallets and apps can build on the pools without asking anyone: start with Build on Montra, Contract reference and Public API.

A product that brings launches earns a share of their fees: the Builder programme.

To talk to the team: @MontraXYZ on X.

For AI tools

Every page as Markdown in llms.txt, and all of it in one file at llms-full.txt.