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.
On this page
A buy's rate: at most 99% at launch, falling in a straight line
Launch≤ 30 minat most 30 minutes
earlyBuyFeeUntil − earlyBuyFeeWindow
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.
Pointers
- Each pool sets its own window, or none. Never assume either: read
earlyBuyFeeUntil,earlyBuyFeeStartPipsandearlyBuyFeeWindowfrom the chain or the API. On chainearlyBuyFeeUntilis 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()andsellTaxRate()are a sale's rate; a buy's rate now isbuyTaxRate(),currentBuyFeeRate(poolId)or the lens'sbuyFee. - Show the live buy rate, with a countdown to
earlyBuyFeeUntilon 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.
Swapalone understates a buy. Inside a window v4'sSwapshows up to a hundred times less than the buyer paid, so count fromSwapandFeeAccruedtogether, and expire any high-buy-tax flag atearlyBuyFeeUntil. 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% pool | A buy pays | Times the pool's price |
|---|---|---|
| In the launch second | 99% | 100× |
| 1 minute after | 79.4% | 4.9× |
| 2 minutes after | 59.8% | 2.5× |
| 3 minutes after | 40.2% | 1.7× |
| 4 minutes after | 20.6% | 1.26× |
| 5 minutes after | 1% | 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 terms | At 10 s | At 1 min | At 5 min | At 15 min | At 30 min |
|---|---|---|---|---|---|
| 99% falling to a 1% pool fee over 30 seconds | 66.33% | 1% | 1% | 1% | 1% |
| 99% falling to a 1% pool fee over 5 minutes | 95.73% | 79.4% | 1% | 1% | 1% |
| 99% falling to a 1% pool fee over 30 minutes | 98.46% | 95.73% | 82.67% | 50% | 1% |
| 50% falling to a 2% pool fee over 10 minutes | 49.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 chain
hook.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 chain
hook.poolConfig(poolId).earlyBuyFeeStartPips, pips - From the API (
/api/tokens/{token})pool.earlyBuyFeeStartPips - The window's length
- On chain
hook.poolConfig(poolId).earlyBuyFeeWindow, seconds; the launch wasearlyBuyFeeUntil − earlyBuyFeeWindow - From the API (
/api/tokens/{token})pool.earlyBuyFeeWindow - What a buy pays now, the rate
- On chain
hook.currentBuyFeeRate(poolId)in pips,token.buyTaxRate()in basis points rounded up, the lens'sbuyFee - 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 chain
token.sellTaxRate(),taxBps(), the lens'stotalFee - From the API (
/api/tokens/{token})pool.totalFeePips,live.totalFee - All of the schedule in one read
- On chain
LaunchLens.buyFeeSchedule(hook, poolId): start, end, the seconds it starts and ends, the rate now; all zero for none - From the API (
/api/tokens/{token})
| What | On chain | From the API (/api/tokens/{token}) |
|---|---|---|
| The window's end, the first second a buy pays the pool's fee | hook.poolConfig(poolId).earlyBuyFeeUntil, a Unix second; zero for none | pool.earlyBuyFeeUntil, in milliseconds; zero for none |
| The start, what a buy paid in the launch second | hook.poolConfig(poolId).earlyBuyFeeStartPips, pips | pool.earlyBuyFeeStartPips |
| The window's length | hook.poolConfig(poolId).earlyBuyFeeWindow, seconds; the launch was earlyBuyFeeUntil − earlyBuyFeeWindow | pool.earlyBuyFeeWindow |
| What a buy pays now, the rate | hook.currentBuyFeeRate(poolId) in pips, token.buyTaxRate() in basis points rounded up, the lens's buyFee | 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 | The lens's buyEffectiveTotalFee | live.buyEffectiveTotalFee, a string |
| What a sale pays | token.sellTaxRate(), taxBps(), the lens's totalFee | pool.totalFeePips, live.totalFee |
| All of the schedule in one read | LaunchLens.buyFeeSchedule(hook, poolId): start, end, the seconds it starts and ends, the rate now; all zero for none |
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.
- In these samples
client,wallet,account,lens,router,lensAbiandrouterAbiare as under Read, quote and trade,tokenis the token's address,tokenAbiandhookAbiareLaunchToken.jsonandLaunchHook.json, and the PoolManager's ABI isPoolManager.json. viem returns auint40or auint24as a number, auint256and 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 - 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" }); - The same terms from the public API.
pool.earlyBuyFeeUntilis in milliseconds: divide by 1,000 before comparing it with a block's time. The anti-snipe terms inpoolnever change, so read them once (pool.totalFeePipscan still be lowered).liveis 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.Shellcurl -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)orbuyFee, refreshed at least every block while the window is open, labelled as a buy's. Better still, the price it makes: a buy pays1 / (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()andtaxBps()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
Swapdraws the pool's price. Inside a window a buyer paid up to a hundred times theSwap's quote amount: Events and indexing. - Re-read on a change.
TotalFeeUpdatedlowers the pool's fee and with it the rest of the line;LpFeeActivatedchanges the all-in figure, not the rate. Cached figures err high, since the rate never rises, so expire any high-buy-tax flag atearlyBuyFeeUntilrather 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.
- 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 at | Lands 1 s later | Lands 3 s later | Lands 10 s later |
|---|---|---|---|
| The launch second | 25.4% (32.7% more in) | 50.0% (98.0% more in) | 76.8% (326.7% more in) |
| 60 s | 2.5% (1.6% more in) | 5.5% (4.8% more in) | 14.6% (15.9% more in) |
| 150 s | 1.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.quoteExactInquote 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
earlyBuyFeeUntilonce 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 asqrtPriceLimitX96set 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
hookDataor 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'samountInMaximumholds it: over it, the trade revertsV4TooMuchRequested. - Exact output, capped by a price limit. For a caller that swaps on the PoolManager itself, a
sqrtPriceLimitX96is 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:amountInMaximumdoes, and a hop'sminHopPriceX36if 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
earlyBuyFeeStartPipsandearlyBuyFeeWindowin itsregistration, both zero for none. The window starts at the time of its block, the launch's, and endsearlyBuyFeeWindowseconds later, atearlyBuyFeeUntil. FeeAccrued- Once per swap. Its
feeis the hook's whole take in the quote, the anti-snipe premium included;protocolPartstays the protocol's own pips of the leg, andfee − protocolPartis 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. Itsfeefield 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
earlyBuyFeeUntilon; 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 launch | Rate | The hook takes | Montra's 0.4% | The creator side's | Reaches the pool | Times the pool's price |
|---|---|---|---|---|---|---|
| 0, the launch second | 99% | 0.99 | 0.004 | 0.986 | 0.01 | 100× |
| 1 | 98.67% | 0.986734 | 0.004 | 0.982734 | 0.013266 | 75× |
| 10 | 95.73% | 0.957334 | 0.004 | 0.953334 | 0.042666 | 23× |
| 60 | 79.4% | 0.794 | 0.004 | 0.79 | 0.206 | 4.9× |
| 150 | 50% | 0.5 | 0.004 | 0.496 | 0.5 | 2.0× |
| 299 | 1.33% | 0.013267 | 0.004 | 0.009267 | 0.986733 | 1.01× |
300, earlyBuyFeeUntil | 1% | 0.01 | 0.004 | 0.006 | 0.99 | 1.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()andtaxBps()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?
earlyBuyFeeUntilis zero, and so are its start and window. Both rates read the same, and one integration covers both kinds of pool.
