# Montra docs, every page URL: https://montramarkets.xyz/docs 20 pages, in reading order. Each also stands alone at its own URL, and as Markdown at https://montramarkets.xyz/docs/.md (the overview at https://montramarkets.xyz/docs/index.md). --- # Montra docs URL: https://montramarkets.xyz/docs Launch a token in one transaction; it trades from its first block in its own Uniswap v4 pool, its liquidity locked for good. Montra runs on Elysium, Kinetiq's chain that settles to HyperEVM, with HYPE as gas, on the official Uniswap v4 there. It is three products: **Montra tokens**, the launchpad, open to anyone; and, coming soon, **Montra Partner** for established projects and **Montra Listings** for tokens from other chains. - **Montra tokens**: A token in one transaction: its own pool, the whole supply locked in it as liquidity, trading from its first block. ([Launching a token](https://montramarkets.xyz/docs/launching)) - **Montra Partner** (coming soon): Bespoke launches for designs that need more than a standard launch, every difference from one fixed at launch and readable on chain. ([Montra Partner](https://montramarkets.xyz/docs/partner)) - **Montra Listings** (coming soon): Tokens from other chains, and Elysium projects' own, given a market made by a professional market maker, its liquidity in place when trading opens. ([Montra Listings](https://montramarkets.xyz/docs/listings)) ## Montra tokens at a glance Most of these docs are about Montra tokens. > **No bonding curve. No migration.** The pool a token starts in is its market for life. Graduation is a line in that pool, so a terminal's new, about to graduate and graduated columns still work: [how to track it](https://montramarkets.xyz/docs/build#no-bonding-curve-no-migration). - **Launch:** 1 transaction. Token, pool, locked liquidity and an optional first buy. - **Supply in the pool:** 100%. One position, from the opening price up. - **Liquidity:** Locked for good. The hook has no way to remove it. - **Fee tiers:** 1% to 10%. 1% · 2% · 3% · 5% · 10% - **Token owner:** None. No mint, no tax, no pause, no blacklist. - **Venue:** Uniswap v4. On Elysium, gas in HYPE. ## Start here - [Getting on Elysium](https://montramarkets.xyz/docs/elysium) (for everyone): HYPE onto Elysium from any chain, reading it through the Montra RPC, and the six-day way back out. - [Launching a token](https://montramarkets.xyz/docs/launching) (for everyone): One transaction: the token, its pool, its locked liquidity and your first buy. - [Trading and the pool](https://montramarkets.xyz/docs/trading) (for everyone): Locked liquidity from the first block, two milestones on the way up, and where to trade. - [Fees](https://montramarkets.xyz/docs/fees) (for everyone): One fee on every trade, five tiers, where each part of it goes, and the anti-snipe fee a pool may carry. - [Holder rewards](https://montramarkets.xyz/docs/holders) (for everyone): How a token can pay its holders, by the second, in the asset it is paired with. - [The protocol's share](https://montramarkets.xyz/docs/protocol) (for everyone): Montra's part of every fee: the treasury's first, then a buyback that burns MONTRA. - [MONTRA](https://montramarkets.xyz/docs/montra) (for everyone): The launchpad's own token, and how to tell the real one from a copy. - [Build on Montra](https://montramarkets.xyz/docs/build) (for builders): Finding, reading, quoting and trading the pools, and the rules they keep. - [The anti-snipe fee](https://montramarkets.xyz/docs/anti-snipe) (for builders): A pool's anti-snipe window: reading its terms, tracking the window, showing the rate, and indexing it. - [Builder programme](https://montramarkets.xyz/docs/builders) (for builders): A share of Montra's fee on every token launched through your product: attribution, statements and weekly payouts. - [Builder terms](https://montramarkets.xyz/docs/builder-terms) (for builders): What a builder earns, when it is paid and what is not paid: the words it signs. - [Contract reference](https://montramarkets.xyz/docs/reference) (for builders): Each contract's functions and the arithmetic behind them. - [Run a keeper](https://montramarkets.xyz/docs/keepers) (for builders): The jobs anyone can do to keep the fees moving, what each pays, and how to build a keeper. - [Events to index](https://montramarkets.xyz/docs/events) (for builders): Every event, by contract, to rebuild Montra from the logs. - [Public API](https://montramarkets.xyz/docs/api) (for builders): The read-only JSON API this site runs on. - [The Montra RPC](https://montramarkets.xyz/docs/rpc) (for builders): Elysium's RPC: reads, sends through the inbox, its limits, and the inbox route for scripts. - [Contracts](https://montramarkets.xyz/docs/contracts) (for builders): The address book, read live from the registry on chain. - [Montra Partner](https://montramarkets.xyz/docs/partner) (coming soon): Bespoke launches for established projects: what a partner can choose, the rules every partner token keeps, and how it is told apart. - [Montra Listings](https://montramarkets.xyz/docs/listings) (coming soon): Tokens from other chains, landed on Elysium with a professionally made market, its liquidity in place when trading opens. ## The words - **Quote asset:** What a token is paired with and priced in, HYPE or another listed asset. Fees are paid in it. - **Pool:** Where a token trades: a Uniswap v4 market of its own, created at launch. - **Hook:** Montra's contract on every pool. It takes the fee and holds the locked liquidity. - **Seed:** The locked liquidity: the whole supply, placed in the pool at launch as one position that never comes out. - **Creator side:** The creator's share of each fee, paid where the creator chose: a wallet, a split or the token's holders. - **LP line:** A price milestone. Once the price has spent 10 minutes past it, the pool's liquidity providers earn a fee (experimental), its terms not final for launches to come ([Fees](https://montramarkets.xyz/docs/fees)). - **Keeper:** A bot that keeps the fees moving: it turns the protocol's fees into HYPE or MONTRA and delivers holder rewards. Montra runs one, and anyone can run their own ([Run a keeper](https://montramarkets.xyz/docs/keepers)). No trade waits on one. - **MONTRA:** The launchpad's own token, which the protocol's share buys back and burns. The official one is marked on this site. - **Montra Partner:** Coming soon: a token launched through Montra for an established project, with the rules its design needs, every difference from a standard launch disclosed ([Montra Partner](https://montramarkets.xyz/docs/partner)). - **Montra Listings:** Coming soon: a token from another chain, or already on Elysium, given a market made with a professional market maker ([Montra Listings](https://montramarkets.xyz/docs/listings)). ## Good to know Prices on Montra are set by trading alone, and a few figures on a token's page are worth reading the right way. - **Prices are set by trading.** Montra does not set or support any token's price, and a price can fall to almost nothing. - **Market caps are fully diluted.** The whole supply, including what still sits in the pool, at the last price. There is no team, presale or vesting allocation. - **Names, images and links are the creator's.** Montra does not check them; a token named after something is not endorsed by it. A token called MONTRA is the real one only where this site marks it official. - **Trading costs the pool's fee plus gas.** The fee is shown before you sign. A pool may charge buys more just after its launch, an anti-snipe fee falling to the pool's fee: it is shown as the price it makes, and a buy above 10% is asked about first. Claiming fees or rewards costs gas only. - **A fee only ever falls.** A pool's fee can be lowered by the address its creator's share pays, to its tier's floor, and never raised. - **Uniswap's own fee.** Uniswap could switch on a protocol fee of its own on a pool, on top of the pool's fee. Each token's fee card shows what its pool charges now. --- # Getting on Elysium URL: https://montramarkets.xyz/docs/elysium Bring HYPE from any chain to HyperEVM, deposit it into Elysium, and trade there through the Montra RPC: every step in your own wallet, and Montra never holds your funds. - **In:** Under a minute. Often seconds, from HyperEVM, through the rollup's own bridge. - **Out:** About 6 days. The rollup's challenge period, then a claim on HyperEVM. - **Montra holds:** Nothing. Every step is a transaction from your own wallet. ## How Elysium is reached Transactions reach Elysium through the rollup's own bridge on HyperEVM, which Elysium reads in order seconds later. Montra runs an RPC that lets an ordinary wallet use Elysium: it reads, and it posts what your wallet signs into the inbox, paying that HyperEVM gas ([The Montra RPC](https://montramarkets.xyz/docs/rpc)). The site adds it to your wallet in one click, or the first time you trade. On the site, the **Bridge** walks through every step below with your wallet, and a token's page offers the deposit from HyperEVM when your wallet has no HYPE on Elysium yet. - **Elysium is new.** Its operators may still change, pause or reset it. Put in what you mean to use. - **Getting out is slow.** About six days on the rollup's own bridge; no faster route exists. ## Ways in | From | How | Takes | Costs | | --- | --- | --- | --- | | Another chain (Base, Arbitrum, Ethereum, Optimism, BNB Chain, Polygon) | Relay brings ETH, USDC or USDT as HYPE on HyperEVM, to the same wallet. Montra relays the quote; your wallet sends to Relay's depository, and Relay fills it. | Usually under a minute | Relay's fee and price, shown before you sign (the site refuses a route costing over 3%) | | Any EVM chain, from any wallet or exchange | Relay gives a one-time deposit address on that chain: send at least the amount shown, and HYPE arrives on HyperEVM at your address. The site shows the address only once Relay's own answer, read by your browser from Relay, and the Montra RPC's answer to the same request agree. | About a minute after it arrives | Relay's fee and price, shown before you send (over 3% is refused) | | Solana, Bitcoin or Tron | Relay's own app (relay.link), to HYPE on HyperEVM at your address. Relay gives no deposit address on those chains that a browser can check with it directly, so the site doesn't offer one. | Relay's | Relay's | | Hyperliquid | Move HYPE from your spot balance to HyperEVM in the Hyperliquid app. | Seconds | None | | An exchange | Withdraw HYPE on the HyperEVM network, to your own wallet. | The exchange's | The exchange's | | HyperEVM, onto Elysium | A deposit into the rollup's own bridge, from your wallet on HyperEVM. The same address holds it on Elysium. | Usually under a minute (often seconds) | HyperEVM gas only | Deposit from a plain wallet. The bridge credits a deposit from an account with code on HyperEVM (a smart wallet, or one delegated under EIP-7702) to a different address on Elysium, which no key controls. And never send from an exchange straight to the bridge: the exchange's address would be credited, not yours. Keep 0.01 HYPE on HyperEVM: it pays gas there, including the claim of a withdrawal six days later. ## The way out 1. **Start it on Elysium.** A withdrawal of HYPE from your wallet on Elysium, sent like any Elysium transaction. (under a minute) 2. **Wait for the rollup.** Elysium's state is checkpointed on HyperEVM about every six hours, and each checkpoint is final about 5.5 days later. Six days in all, give or take half a day. (about 6 days) 3. **Claim on HyperEVM.** Anyone may send the claim, and it pays only you. The site builds it for you; it costs a little HyperEVM gas. (seconds) Nobody can speed a withdrawal up or stop it, Montra included. The site lists yours, with when each can be claimed: a date once a checkpoint covers it, a range before. ## Trading through the Montra RPC Add Elysium to your wallet with the Montra RPC (the site does it in one click) and your wallet reads and sends on Elysium: balances, trades, approvals, claims and withdrawals. A trade on the site is the same as anywhere: type an amount, confirm in your wallet, and the site follows it from the inbox to Elysium, usually within 30 seconds. A wallet that sends Elysium transactions through another RPC is warned before it signs, since most RPCs may not get them through. - **Sending, wherever you send.** Each transaction reaches Elysium through its bridge on HyperEVM, so send each after the last has landed: speed-up and cancel do nothing. It sits in public on HyperEVM from its post until it runs, usually under 30 seconds and sometimes minutes, where anyone can read it and a few parties with direct access to Elysium could trade ahead of it, so keep slippage tight. - **Only for Montra.** The Montra RPC forwards Montra's own transactions into Elysium's inbox rather than sending them straight to its sequencer. It isn't a general RPC: anything else is refused and nothing is sent, so to use another app on Elysium, switch your wallet to that app's connection. - **Still yours.** Elysium runs the transaction you signed, from your address: holders and rewards count it as yours. - **Privacy.** Your IP address is kept in memory only, for rate limits. - **Its limits**, and the inbox route a script can send by, are under [The Montra RPC](https://montramarkets.xyz/docs/rpc). ## Staying safe - **Check the bridge.** The site checks on HyperEVM that the address it deposits to leads to Elysium, chain 99901, before you sign. - **Fake bridges and RPCs are about.** `elysiumpump.com` was flagged as phishing by Cloudflare; `elysianspiritshieldbridge.com` has your wallet sign whatever its server returns rather than using the rollup's bridge; `elysium-earn.xyz`, a fake vote, is a wallet drainer. None is Kinetiq's or Montra's. - **Sign only what you can read.** A deposit is one call to the rollup's bridge; a withdrawal one call on Elysium. A site asking for anything else to "bridge" is not using Elysium's bridge. - **Use a fresh, plain wallet** holding only what you mean to use on Elysium. On a phone, open the site in your wallet app's own browser. --- # Launching a token URL: https://montramarkets.xyz/docs/launching Fill in a name, a picture, a supply, a pair and a fee tier, then sign once: that one transaction creates the token, puts all of it into a new pool, locks it there and can include your own first buy. Inside a launch. If any step fails the whole transaction reverts and nothing exists. 1. **The token.** A new ERC-20 at an address fixed by the creator's wallet and a salt. Its whole supply is minted to the pool's hook. (`LaunchToken.initialize`) 2. **Its terms.** The hook records the fee tier, the two lines and who the pool's fees pay. A split stream gets its splitter here. (`LaunchHook.register`) 3. **The pool.** A dynamic-fee Uniswap v4 pool is created at the opening price, with the launch hook attached. (`PoolManager.initialize`) 4. **The seed.** The hook places the whole supply in the pool as one position. It has no function that takes it out. (`LaunchHook.seed`) 5. **First buy.** Optional. The creator's own buy at the pool's normal fee, before anyone else can trade. (`PoolManager.swap`) 6. **The record.** One event describes the launch in full, for indexers and terminals. (`TokenLaunched`) ## What you choose - **Pair.** HYPE, or an asset Montra has listed. Fees are paid in it. - **Supply.** 1 billion to 1 quadrillion tokens, any whole number. The opening market cap is the same whichever you pick. - **Fee tier.** 1%, 2%, 3%, 5%, 10% of every trade. How each splits is under [Fees](https://montramarkets.xyz/docs/fees). - **Where your share goes.** Your wallet, another address, a split between two to four, or the token's holders: see [Fees](https://montramarkets.xyz/docs/fees). - **Anti-snipe fee.** Optional, on the pairs that offer it. Buys pay 99% at launch, falling to your fee over the length you choose; sales and your own first buy never pay it. The form today offers 5 seconds, 10 seconds, 30 seconds, 1 minute, 5 minutes, 15 minutes or 30 minutes, and the list may change. More under [Fees](https://montramarkets.xyz/docs/fees). - **First buy.** Optional. In the same transaction, at the pool's normal fee, refused below 99% of its estimate, before anyone else can trade. - **Name and picture.** Fixed at launch: the name, symbol, picture, description, links and metadata link cannot be changed. Every step happens or none does, so there is never a token without a pool, and nobody can trade before the creator's first buy. The contracts fix everything else: the opening price, the two milestones and how the fee splits all come from the tier you pick. An anti-snipe fee is a choice of its own, where a pair offers one: see [Fees](https://montramarkets.xyz/docs/fees). A launch fee, when one is set, is paid in HYPE and shown before you sign; gas is paid in HYPE. ## What a token can pair with Native HYPE is listed from the start. Montra lists any other asset after a checklist nothing on chain can run: a plain ERC-20 whose transfers move exactly the amount sent (no fee, no rebase, no cap per transfer), six to eighteen decimals, and a liquid market on Elysium to price it and sell it through. A Montra launch token is never listed as one. Switching a quote off stops new launches in it. A pool's quote never changes. ## The token you get - **Owner:** None - **Minting:** None. After launch. - **Transfer tax:** None - **Pause, blacklist:** None - **Name, symbol:** Fixed - **Decimals:** 18 - **Rebasing:** None - **Supply:** Only falls Each token is a standard ERC-20 that nobody is in charge of, and sending it between wallets costs only gas. Its supply can only go down: anyone can burn their own tokens, and the pool burns the token side of the fees its locked liquidity earns. A trade's fee is charged by the pool and its hook, not by the token, so a transfer moves exactly the amount sent. If the creator chose it, the token also pays its holders ([Holder rewards](https://montramarkets.xyz/docs/holders)); that bookkeeping never makes a transfer fail. --- # Trading and the pool URL: https://montramarkets.xyz/docs/trading There is no bonding curve and nothing migrates: each token trades from its first block in its own Uniswap v4 pool on Elysium, which starts with the whole supply in it and keeps it locked for good. - **Supply in the pool:** 100%. One position, from the opening price up. - **Half of it bought:** At 4×. The opening price, while the seed is the pool's only liquidity. - **Taken out:** Never. The hook has no function that removes it. - **Outside liquidity:** Open. Anyone can add and remove, as on any pool. ## The seed The seed: the whole supply as one position from the opening price up, drawn as how much token sits at each price on a log scale. With the price at 40× the opening, everything to its left has been bought. - **Graduation, 16.8× the opening price:** 76% of the supply bought out of the seed, about $10.8k of buying from a $3.5k opening, before fees. - **The LP line, 142.6× the opening price:** 92% of the supply bought out of the seed, about $38.3k of buying from a $3.5k opening, before fees. - **Each doubling of the price** takes about ×1.41 the buying the last doubling took, so early buys move the price most. At launch the supply is placed as one position from the opening price up to the highest price the pool can express. It holds only the token: as people buy, the price rises through it and what they pay stays in the pool. Half the supply has been bought by four times the opening price, and each doubling of the price takes about 1.4 times the buying of the one before, so early buys move the price the most. That position belongs to the pool's hook, which has no function that removes liquidity of its own. Anyone else can add liquidity and take it back out whenever they like; from the LP line on it earns the pool's LP fee. ## Two milestones Nothing moves at either one: no bonding curve ends and no liquidity migrates. They are lines in the pool the token has always traded in. Graduation is the one terminals sort their columns by: [how to track it](https://montramarkets.xyz/docs/build#no-bonding-curve-no-migration). - **Graduation:** 16.8×. The opening price: about $58.7k of market cap from a $3.5k opening. Changes no fee. - **LP line:** 142.6×. About $499k from the same opening. Switches on the LP fee (experimental). - **Time past the LP line:** 10 min. In total: a dip pauses the count, never resets it. - **Once reached:** For good. Neither milestone is ever undone. The stages a pool moves through, one at a time, and the LP line's hold: past the line for four minutes, a dip, past it again; at 10 minutes in all the next trade switches the LP fee on. 1. **Below.** Short of graduation. 2. **Crossed.** A swap left the price past the line. The first swap of a later transaction confirms it, whichever way that swap trades. 3. **Graduated.** Past the line and confirmed. Moves no fee. For good. 4. **LP crossed.** Past the LP line. The clock runs while the price stays there, up to 10 minutes in all. 5. **LP active.** The next swap after the clock completes switches the LP fee on, and pays it. For good. A sale in the same transaction can step a pool back from Crossed to Below; any later swap confirms it instead. A sell can step a pool back from LP crossed to Graduated while its clock runs. Nothing steps a pool back from Graduated or LP active. Both are confirmed by a later transaction, so a price pushed past a line and pulled back inside one transaction confirms nothing. Nobody has to call anything: the first trade after the 10 minutes switches the LP fee on, and that trade already pays it. Below the LP line that slice of the fee goes to the creator side instead. The wait is there because anyone may add liquidity: without it, someone could push the price past the line for a moment, switch the fee on and be the liquidity collecting it. **The LP fee is experimental.** Its rates, and the line it starts at, may change for launches to come; a pool keeps the LP fee and the line it launched with. ## Where to trade Here, or anywhere that trades Uniswap v4 pools: Uniswap's Universal Router or any other v4 router. A quote matches its trade exactly, and the fee is the pool's, shown before you sign. On every tier of the menu a pool trades from its first block, with no whitelist and no limit per wallet. A pool may carry an anti-snipe fee ([Fees](https://montramarkets.xyz/docs/fees)): for its first seconds or minutes a buy pays more than the pool's fee, falling in a straight line to it, and a sale never does. Here it is shown as the price it makes, how many times the pool's price a buy pays, and a buy above 10% asks you to accept it first. Elsewhere, check what you would receive before you sign: an on-chain quote includes it. More under [The anti-snipe fee](https://montramarkets.xyz/docs/anti-snipe). How a router or a terminal reads, quotes and trades a pool is under [Build on Montra](https://montramarkets.xyz/docs/build). --- # Fees URL: https://montramarkets.xyz/docs/fees Every trade pays one fee, the same on buys and sells, at the tier the creator picked: 1%, 2%, 3%, 5%, 10%. Part goes to the creator side, 0.4% to Montra, and from the LP line a part to the pool's liquidity providers. A pool may also carry an anti-snipe fee, on buys just after its launch. Who $100 of trading pays, by tier and by side of the LP line. Below the line there is no LP fee and its slice is the creator side's. ## The five tiers | Tier | Floor | Protocol | LP, from the line (experimental) | Creator, below the line | Creator, past it | Creator at the floor, past the line | | --- | --- | --- | --- | --- | --- | --- | | 1% (default) | 1% | 0.4% | 0.1% | 0.6% | 0.5% | 0.5% | | 2% | 1.1% | 0.4% | 0.3% | 1.6% | 1.3% | 0.4% | | 3% | 1.3% | 0.4% | 0.5% | 2.6% | 2.1% | 0.4% | | 5% | 1.8% | 0.4% | 1% | 4.6% | 3.6% | 0.4% | | 10% | 1.8% | 0.4% | 1% | 9.6% | 8.6% | 0.4% | **The LP fee is experimental.** Its rates, and the line it starts at, may change for launches to come; a pool keeps the LP fee and the line it launched with. ## Where a fee goes Where one trade's fee goes, on $100 traded at the 2% tier past the LP line. - **$2.00:** $100 traded at 2%, past the LP line. - **$1.70 hook fee:** the total less the LP fee, taken by the hook in the quote asset; of it **$0.40** to the protocol (the same on every tier, sent on to the protocol fee vault) and **$1.30** to the creator side (held for whoever the pool pays). - **About $0.30 LP fee (experimental):** charged by the pool itself on what the trader pays in, to the liquidity the trade used: the locked seed (quote side to the creator side, token side burned) and outside liquidity providers. - **Below the LP line** there is no LP fee: the hook takes the whole 2% and the creator side gets $1.60. - **All in**, the two fees compound, one charged on what the other leaves: this trade pays 1.9949%. The fee is split during the trade itself. The hook takes the creator side's and Montra's parts in the asset the token is paired with, such as HYPE, never in the token; the pool charges the liquidity providers' part on what the trader pays in, which on a sell is the token. The creator side's part waits in the hook until whoever the pool pays claims it, at any time; anyone can run the bookkeeping that moves fees along, but only the pool's current recipient can move that money. Montra's part goes on to the protocol fee vault: where it goes from there is under [The protocol's share](https://montramarkets.xyz/docs/protocol). ## The creator's share The four routes a creator can choose at launch. A split can include the holders as one of its slots. - **Your wallet:** The default. The creator claims whenever they like. Can lower the fee to its floor, or lock it. - **Another address:** A team wallet, a multisig, anyone. They hold the stream and its rights. Can lower the fee to its floor, or lock it. - **A split:** Two to four recipients on fixed shares, through a small splitter contract. Fee fixed from launch. - **The holders:** The token itself takes the stream and pays it to everyone holding it. Fee fixed from launch. - **Hand it on.** The current recipient, at any time. Anything unclaimed and the fee lever go with it; only the new address can move it again. - **Turn it into a split.** Create a splitter for the pool, then hand the share to it: two to four recipients on fixed shares. - **Give it to the holders.** The token itself takes the share and pays everyone holding it ([Holder rewards](https://montramarkets.xyz/docs/holders)). The fee is then locked for good. - **Lower the fee first.** If you mean to: a splitter or the holders cannot pull the lever. Handing on has no accept step, so make sure the new address is one its owner controls. In a split the shares never change; each recipient can hand on only its own slot. ## The anti-snipe fee A pool may carry an anti-snipe fee: from the launch second a buy pays more than the pool's fee, at most 99%, falling in a straight line, second by second, to the pool's own fee within at most 30 minutes. A sale never pays it, and neither does the creator's first buy, which is part of the launch. What it takes above the pool's fee goes to the creator side, whoever the pool pays; Montra's part stays its usual share of the trade. It never rises, and the pool's recipient lowering the pool's fee only lowers it. 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. The window counts from when a launch is posted to Elysium, not from when it runs, so its first seconds may pass before anyone can trade, and a short window loses the most. | 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× | Montra's site shows it as the price it makes, how many times the pool's price a buy pays, never as a countdown, and asks you to accept a buy above 10% before it goes through. How an app or a terminal reads, tracks and shows it is under [The anti-snipe fee](https://montramarkets.xyz/docs/anti-snipe). ## The one lever When the creator's share is paid to one address, that address can lower the pool's fee down to its tier's floor, and can lock it, at any level, for good. A fee can be lowered but never raised, and the liquidity providers' part never changes. On every tier today, the creator side still earns at least what the protocol does at the floor. Paid to a split or to the holders, the fee is fixed from launch. --- # Holder rewards URL: https://montramarkets.xyz/docs/holders When a token's fees go to its holders, every wallet holding it earns a share in proportion to its balance, paid in the asset the token is paired with, building up by the second. - **Paid in:** The pair's asset. HYPE for a HYPE pair, and so on. - **Released over:** 1 hour. Each payment streams out over the hour after it arrives. - **Claiming:** Any time. No fee, gas only. - **Delivery:** 0.25%. To whoever pushes payouts to holders' wallets. ## How it builds up Fees that arrive are released over the next hour, so buying just before a payout earns only for the time you hold. Selling or sending settles what you have earned up to that moment, so moving tokens loses nothing. The bookkeeping makes no outside call while tokens move, so it can never stop a transfer. ## Who earns Every holder earns, but for a few addresses no one can claim for: the pool's own balances, the hook, the token contract, the common burn address `0x…dEaD` and address one, the name a launch gives the holders. Nothing is ever paid to address one. Other contracts that hold the token, such as a pool elsewhere, a bridge or a smart-contract wallet, earn like anyone else, but pushed payouts go to ordinary wallets only. A contract is paid only when it calls `claimRewards` itself: one that can't, as most pools and bridges can't, leaves its share in the token. A push treats any address with no code as a wallet, and that includes a contract not yet deployed: a Safe or an escrow whose address holds the token before its contract exists is paid like a wallet. A contract built for such an address must be able to take out and move what was paid to it before it existed, in the asset the token is paired with. ## Getting paid - **Claim.** Whenever you like, to yourself or to any address you name. Gas only. - **Or be pushed.** Anyone can push payouts to holders' wallets and is paid 0.25% of each for doing it ([Run a keeper](https://montramarkets.xyz/docs/keepers)). A push goes out only when that pay covers its gas, so on a very small token payouts may not come by themselves: use the claim button on the token's page or in your portfolio. - **See it.** What you have earned shows on the token's page and in your portfolio. --- # The protocol's share URL: https://montramarkets.xyz/docs/protocol Montra's part of every fee, 0.4% of each trade on every tier, goes to the treasury until the launchpad's own token, MONTRA, exists. Once MONTRA's buyback is switched on, 70% buys MONTRA back and burns it, and 30% goes to the treasury. The order of things. The launchpad runs from its first day; the split changes once, when the buyback is switched on. 1. **From day one: The launchpad.** Montra runs on the official Uniswap v4 on Elysium, and anyone can launch. The protocol's share: 100% to the treasury, in HYPE. 2. **Later: MONTRA.** The launchpad's own token launches. Its creator is the team's multisig. The protocol's share: still the treasury's. 3. **Once switched on: The buyback.** The team's multisig switches the buyback on: it buys MONTRA in its own pool and burns all it buys, each of its sales capped at 5% of the pool's depth a day. The protocol's share: 70% buyback and burn · 30% treasury. - **The protocol's share:** 0.4%. Of every trade, on every tier. It can be lowered for a pool, never raised. - **Until MONTRA:** 100% treasury. Paid in HYPE. - **Once its buyback starts:** 70% / 30%. Buyback and burn of MONTRA / treasury. - **The buyback's pace:** 5% a day. Of MONTRA's pool depth, for each of its sales: a cap the team sets. ## How fees become HYPE or MONTRA Fees arrive in whatever each pool is paired with. The fee market turns them into HYPE for the treasury, and the buyback's into MONTRA, with one standing sale per currency rather than one per launch. - **A price from the pools.** The keeper reads the rate the pools pay for a currency, net of their fees, and posts it as the sale's reference. A sale for HYPE sells nothing on a reference more than 5 minutes old. - **Falling to a floor.** Each sale's price starts just above the reference and falls toward a floor a little under it: 1% for most currencies, 3% for memes and stock tokens, 1.5% for the buyback. - **Filled through the pools.** The keeper fills the sales through Uniswap pools. Anyone may fill the treasury's too ([Run a keeper](https://montramarkets.xyz/docs/keepers)), and the sale is paid its price either way. ## The buyback - **Switched on by the multisig.** The team's multisig switches it on. Until then all of the protocol's share is the treasury's. - **Buys and burns.** Only the keeper fills the buyback's sales: it buys MONTRA in MONTRA's own pool and burns all it buys in the same transaction, so MONTRA's supply falls. - **Paced.** Each of its sales is capped at 5% of the depth of MONTRA's pool a day, a cap the team sets, so the buyback does not spike the price. Anything above it waits in the vault for later days. More on MONTRA itself is under [MONTRA](https://montramarkets.xyz/docs/montra). --- # MONTRA URL: https://montramarkets.xyz/docs/montra MONTRA is the launchpad's own token. Its creator is the team's multisig, and once its buyback is switched on, 70% of the protocol's share of every fee buys it back and burns it. - **Its creator:** Multisig. MONTRA's creator is the team's multisig. - **The buyback:** 70%. Of the protocol's share of every fee, once the buyback is switched on: it buys MONTRA and burns it. - **On this site:** Official. The real MONTRA is marked as official here. A token anywhere else under its name is a copy. ## What it is MONTRA is a token like any launched here, with no owner, no minting and no tax, and a supply that only falls; its creator is the team's multisig. Where the protocol's share goes, and how the buyback buys MONTRA and burns it, is under [The protocol's share](https://montramarkets.xyz/docs/protocol). ## Telling it apart Anyone can launch a token called MONTRA. The official one is marked on this site, and nothing else is official, whatever its name, symbol or picture. How an app or a terminal checks it is under [Build on Montra](https://montramarkets.xyz/docs/build). --- # Build on Montra URL: https://montramarkets.xyz/docs/build Montra pools are standard Uniswap v4 pools with the Montra hook attached: they trade through Uniswap's Universal Router or any other v4 router, a quote matches its trade exactly, and Montra's lens and its own simple router make reading, quoting and trading a pool one call each. - **Registry:** `get(name) · hooks() · isKnownHook(hook)`. Names every other address and lists every hook. - **Lens:** `tokenView(token)`. A pool's key, terms, price, fees and progress in one read. - **Router:** `quoteExactIn · swapExactIn`. LaunchRouter, the site's simple single-pool router: a quote and a trade, one code path. - **Any v4 router:** `Universal Router · V4Quoter`. Uniswap's own, multi-hop included, with the key the hook gives. - **Hook:** `poolKey(poolId) · poolConfig(poolId)`. A pool's key and its terms. ## No bonding curve, no migration A Montra token has no bonding curve, and nothing migrates. There is no second pool, no liquidity to move and no new address: the Uniswap v4 pool a token launches in is its market for life, and its `poolId` and `PoolKey` never change. A graduated token trades as it did before. Graduation is still there, as a line in the pool: 16.8× the opening price on the menu's rows, about $58.7k of market cap from a $3.5k opening. Passing it moves nothing and changes no fee. It is there so a terminal's usual columns work on Montra as they do anywhere else. | Column | Which pools | API `stage` | On chain | | --- | --- | --- | --- | | New | Less than half way to the line | `new` | `graduationProgress` under 5,000, `PoolConfig.stage` 0 | | About to graduate | Half way or more, not yet graduated | `graduating` | `graduationProgress` of 5,000 or more, `PoolConfig.stage` 0 or 1 | | Graduated | Past the line and confirmed, for good | `graduated` | `PoolConfig.stage` 2, 3 or 4 | Half way is the cut Montra's own board uses. `progressBps` runs from 0 to 10,000, so a terminal can draw the column wherever it likes. - **A column.** `GET /api/tokens?stage=graduating&sort=progress` lists the pools closest to the line first. `stage` takes `new`, `graduating` or `graduated`, and `sort=graduated` puts the latest graduates first. - **A progress bar.** `graduation.progressBps` on a card, or `LaunchLens.graduationProgress(address hook, bytes32 poolId) view returns (uint256)` on chain: the quote the seed has collected over the quote it holds at the line, the figure a bonding curve's bar shows. - **The moment it graduates.** The hook's `Graduated` event, or `graduation.graduatedAt` in the API. There is no migration transaction to watch for: the first swap of a later transaction confirms the line, and nobody has to call anything. - **Past the line, not yet confirmed.** `graduationProgress` reads 10,000 at stage `Crossed` (1). The next transaction's swap graduates the pool, whichever way it trades, so a pool at `Crossed` is almost always about to move: keep it at the top of About to graduate until `Graduated`. A sale can graduate a pool and leave the price back under the line. Only a sale in the transaction that crossed the line steps the pool back, before any read can see it. - **A pool with no line.** `graduationTicks` of zero in `PoolRegistered` means graduated from birth, with no `Graduated` event. The API already counts it as `graduated`. ## Find and trust a pool Find tokens from the factory's `TokenLaunched` events or from the [public API](https://montramarkets.xyz/docs/api). Accept a pool only if its hook is one the factory announced (`ModuleSetPublished`, emitted once the factory has checked the hook), which the registry mirrors (`hooks()`, `isKnownHook(hook)`): filter by it rather than taking the first pool you find for a token. An indexer pins the factory's proxy, whose address never moves; an app pins the registry and reads every other address from it by name. The addresses themselves are under [Contracts](https://montramarkets.xyz/docs/contracts). Trust a token by its code, never by its name: a launch is an EIP-1167 clone of a token master the factory published, with a supply. Anyone can clone a master, but only the factory it names can initialize one, so a clone with a supply is a launch. MONTRA, the launchpad's own token, is the token the registry names under `montra` (`get("montra")`, the name's ASCII right-padded to 32 bytes), and nothing else is, whatever its name or symbol. Until the registry names one, there is no MONTRA. An indexer starts at the factory proxy's deployment block: no launch comes before it, and the creator's first buy is logged inside the launch's own transaction, before its `TokenLaunched`. The pool key (PoolKey, from hook.poolKey(bytes32 poolId)): ```text PoolKey({ address currency0: the lower address (native HYPE = 0x0000…0000), address currency1: the higher address, uint24 fee: 0x800000, // dynamic: the LP fee lives in the pool's slot0 int24 tickSpacing: 200, // the row's; every row of the menu's address hooks: // one the factory announced; this one's low 14 bits are 0x38EC }) ``` ## The registry's keys `get(bytes32 key) returns (address)` takes the key's ASCII right-padded to 32 bytes (viem's `stringToHex(key, { size: 32 })`, cast's `--format-bytes32-string`), and answers zero for a key not set. Every change is announced by `AddressSet(key, previous, current)`; hooks are not keys but a list (`hooks()`, `isKnownHook(hook)`, `HookAdded`), from which none is ever removed. | Key | What it names | Set | | --- | --- | --- | | `factory` | The factory's proxy: launches, rows and module sets. Once deployed, its address never moves. | At deployment | | `router` | LaunchRouter: a quote and an exact-input swap on one pool. | At deployment | | `lens` | LaunchLens: stateless reads, replaceable, so read it here. | At deployment | | `quoteRegistry` | The quote assets a launch can pair with. | At deployment | | `protocolVault` | ProtocolFeeVault: the protocol's share and the launch fees. | At deployment | | `feeMarket` | FeeMarket: the sales of the protocol's fees. | At deployment | | `routeFiller` | RouteFiller: fills a sale through pools. | At deployment | | `buybackFiller` | BuybackFiller: the buyback's only taker. | At deployment | | `poolManager` | Uniswap v4's PoolManager, where every pool lives. | At deployment | | `custodian` | Where the venue keeps every pool's balances, which holder rewards leave out: on v4 the PoolManager itself. | At deployment | | `universalRouter` | Uniswap's Universal Router, for exact-output and multi-hop trades through a launch pool. The registry's own address here, or under `permit2` or `quoter`, means withdrawn: none. | By the governor; zero while unset, which is not an error | | `permit2` | Canonical Permit2, which that router pulls every ERC-20 input with: a sale's token, and the quote of a buy in an ERC-20. Each token needs two approvals: the token's to Permit2, then Permit2's to the router, with an expiry. | With `universalRouter`; zero while unset | | `quoter` | Uniswap's V4Quoter, for quoting a swap by either side: `quoteExactIn` quotes exact input alone. | By the governor; zero while unset, which is not an error | | `gatewayRouter` | The chain's token bridge router, which Montra's API asks whether a listed quote is the bridge's canonical token. Nothing on chain calls it. | By the governor; zero while unset | The launchpad's own token's key is set by the governor's last call in the batch that launches it, and reads zero until then. ## The ABIs Every Montra contract's ABI, and Uniswap's PoolManager's, is published from the contracts' build: [`/docs/abis.json`](https://montramarkets.xyz/docs/abis.json), an object of `from`, the build they came from, and `contracts`, each contract's ABI by its name; `/docs/abis/.json`, one contract's as a plain array, ready for viem or ethers; and [`/docs/abis/events.json`](https://montramarkets.xyz/docs/abis/events.json), every event once, to decode any log an indexer reads. Each contract change refreshes them (`npm run pull:abis` in the site's repo), and every function, event, error, argument and field these pages name is checked against them. | File | What it is | | --- | --- | | [`LaunchpadRegistry.json`](https://montramarkets.xyz/docs/abis/LaunchpadRegistry.json) | LaunchpadRegistry: the address book | | [`LaunchFactory.json`](https://montramarkets.xyz/docs/abis/LaunchFactory.json) | LaunchFactory: launches and module sets (the proxy's ABI) | | [`LaunchToken.json`](https://montramarkets.xyz/docs/abis/LaunchToken.json) | LaunchToken: every launch token | | [`LaunchHook.json`](https://montramarkets.xyz/docs/abis/LaunchHook.json) | LaunchHook: the pools' hook | | [`LaunchLens.json`](https://montramarkets.xyz/docs/abis/LaunchLens.json) | LaunchLens: the read views | | [`LaunchRouter.json`](https://montramarkets.xyz/docs/abis/LaunchRouter.json) | LaunchRouter: quotes and exact-input swaps | | [`QuoteRegistry.json`](https://montramarkets.xyz/docs/abis/QuoteRegistry.json) | QuoteRegistry: the quote assets | | [`FeeSplitter.json`](https://montramarkets.xyz/docs/abis/FeeSplitter.json) | FeeSplitter: a split's clone | | [`ProtocolFeeVault.json`](https://montramarkets.xyz/docs/abis/ProtocolFeeVault.json) | ProtocolFeeVault: the protocol's share | | [`FeeMarket.json`](https://montramarkets.xyz/docs/abis/FeeMarket.json) | FeeMarket: the fee market's sales | | [`RouteFiller.json`](https://montramarkets.xyz/docs/abis/RouteFiller.json) | RouteFiller: filling a sale through pools | | [`BuybackFiller.json`](https://montramarkets.xyz/docs/abis/BuybackFiller.json) | BuybackFiller: the buyback's filler | | [`TokenBurner.json`](https://montramarkets.xyz/docs/abis/TokenBurner.json) | TokenBurner: the buyback's burner | | [`PoolManager.json`](https://montramarkets.xyz/docs/abis/PoolManager.json) | PoolManager: Uniswap v4's, for Swap, Initialize and ModifyLiquidity, and the errors a refusal arrives in, the V4Quoter's wrapper among them | The Universal Router, the V4Quoter and Permit2 are Uniswap's, and their ABIs are not published here. Take each from Uniswap's own repository (`Uniswap/universal-router`, `Uniswap/v4-periphery`, `Uniswap/permit2`) at the version the address the registry names is running, or from the chain's explorer where that address is verified. The calls these pages use are written out under [Trade through the Universal Router](https://montramarkets.xyz/docs/build#trade-through-the-universal-router). A script or an AI tool reads the docs' machine files as plain files: these ABIs, `/llms.txt`, `/llms-full.txt` and each page's Markdown at `/docs/.md`. Fetching an ABI: ```sh curl -s https://montramarkets.xyz/docs/abis/LaunchRouter.json ``` ## Read, quote and trade ### Find the addresses An app hard-codes the registry and reads the rest from it. Names are `bytes32` strings. In these samples `client` is a viem public client, `wallet` a wallet client for `account`, and `registryAbi`, `lensAbi` and `routerAbi` the registry's, the lens's and the router's ABIs. ```ts // get(bytes32 key) view returns (address) import { stringToHex, zeroAddress } from "viem"; const registry = "0xF1E2FA75422F889e565F3142bdbf60B28dce2aa7"; const name = (s: string) => stringToHex(s, { size: 32 }); const lens = await client.readContract({ address: registry, abi: registryAbi, functionName: "get", args: [name("lens")], }); const router = await client.readContract({ address: registry, abi: registryAbi, functionName: "get", args: [name("router")], }); ``` ### Read a token's pool One call returns the pool key, its terms (`config`), the price, what the next trade pays and the pool's progress: a `PoolView` of 50 words, field by field under [Contract reference](https://montramarkets.xyz/docs/reference). Check the hook against the registry. An address that is not a launch token makes it revert with no data, so there is no error to decode. ```ts // tokenView(address token) view returns (PoolView) const view = await client.readContract({ address: lens, abi: lensAbi, functionName: "tokenView", args: [token], }); view.key; // the pool key to trade with view.config.quoteIsCurrency0; // which side the quote asset is view.sqrtPriceX96; // the price now view.effectiveTotalFee; // what the next sale pays all in, in pips view.buyEffectiveTotalFee; // what a buy pays all in now: more inside an anti-snipe window await client.readContract({ address: registry, abi: registryAbi, functionName: "isKnownHook", args: [view.key.hooks], }); // true ``` ### Quote a trade Buying the token spends the quote asset, so a buy swaps from the quote's side. `quoteExactIn` returns the output after every fee, as any call returns: it runs the swap inside the PoolManager and undoes it, so it changes nothing, but it is not marked view, so make it an `eth_call` (viem's `simulateContract`). On a Montra pool its quote is exactly what the trade then pays out. A swap the pool or the hook refuses makes the quote revert `UnexpectedRevert`, carrying the refusal. The amounts are `uint128`. ```ts // quoteExactIn(PoolKey key, bool zeroForOne, uint128 amountIn) returns (uint256) const zeroForOne = view.config.quoteIsCurrency0; // a buy; a sell is the opposite const { result: amountOut } = await client.simulateContract({ address: router, abi: routerAbi, functionName: "quoteExactIn", args: [view.key, zeroForOne, amountIn], }); ``` ### Read a refusal How a refusal arrives depends on the call, so take each layer only if it is there. `quoteExactIn` reverts `UnexpectedRevert(bytes reason)`, selector `0xc47bc67c`, with the refusal as its `reason`. The V4Quoter does the same in `UnexpectedRevertBytes(bytes revertData)`, selector `0x6190b2b0`. `swapExactIn` adds no layer: the refusal arrives as it is, as it does through the deployed Universal Router, and another router may wrap it in an error of its own. Under that, a pool manager's own refusal is the error itself. A hook's arrives wrapped, as ERC-7751 has it, in `WrappedError(address target, bytes4 selector, bytes reason, bytes details)`, selector `0x90bfb865`: `target` is the hook, `selector` the callback that refused, `reason` the hook's own error and `details` the selector of `HookCallFailed()`, `0xa9e35b2f`. An exact-output buy for more than the pool can deliver reverts `SafeCastOverflow()`, selector `0x93dafdf1`. `UnexpectedRevert` is in `LaunchRouter.json`, the hook's errors in `LaunchHook.json`, and the rest in `PoolManager.json`. ```ts // quoteExactIn: UnexpectedRevert(bytes reason) // V4Quoter: UnexpectedRevertBytes(bytes revertData) // swapExactIn: no layer // WrappedError(address target, bytes4 selector, bytes reason, bytes details) // PartialFillRejected() import { decodeErrorResult } from "viem"; // revertData: the bytes the call reverted with const abi = [...routerAbi, ...poolManagerAbi, ...hookAbi]; let refusal = decodeErrorResult({ abi, data: revertData }); if (refusal.errorName === "UnexpectedRevert" || refusal.errorName === "UnexpectedRevertBytes") { refusal = decodeErrorResult({ abi, data: refusal.args[0] }); } if (refusal.errorName === "WrappedError") { refusal = decodeErrorResult({ abi, data: refusal.args[2] }); } refusal.errorName; // "PartialFillRejected" ``` ### Trade One exact-input swap with a minimum and a deadline. A native input is sent as value; an ERC-20 input needs an approval for the router first. Reverts `Expired`, `ZeroAddress`, `InvalidRecipient` (the router itself as `recipient`), `IncorrectValue` or `TooLittleReceived`, or with the pool's or the hook's refusal, read as above; emits `Swapped`. ```ts // swapExactIn((PoolKey key, bool zeroForOne, uint128 amountIn, uint128 minAmountOut, address recipient, uint256 deadline) s) // payable returns (uint256 amountOut) const input = zeroForOne ? view.key.currency0 : view.key.currency1; await wallet.writeContract({ address: router, abi: routerAbi, functionName: "swapExactIn", args: [{ key: view.key, zeroForOne, amountIn, minAmountOut: amountOut * 99n / 100n, // 1% slippage recipient: account.address, deadline: BigInt(Math.floor(Date.now() / 1000) + 300), }], value: input === zeroAddress ? amountIn : 0n, }); ``` ## Trade through the Universal Router LaunchRouter makes one exact-input swap on one pool. For exact output, or a route through several pools, use the Universal Router the registry names under `universalRouter`, and the V4Quoter under `quoter` to quote it. A trade is one `execute` call: `commands` has a byte per command and `inputs` an entry per command. A `V4_SWAP` command's input is `abi.encode(bytes actions, bytes[] params)`, with a byte per action in `actions` and an entry per action in `params`. | Kind | Name | Byte | Input | | --- | --- | --- | --- | | Command | `V4_SWAP` | `0x10` | Its actions and their params. | | Command | `SWEEP` | `0x04` | `(address token, address recipient, uint256 amountMinimum)`. Pays out all the router holds of `token`, the zero address being the native currency, to `recipient`, address 1 being the caller. | | Action | `SWAP_EXACT_IN_SINGLE` | `0x06` | `ExactInputSingleParams`: one pool, exact input. | | Action | `SWAP_EXACT_IN` | `0x07` | `ExactInputParams`: a route, exact input. | | Action | `SWAP_EXACT_OUT_SINGLE` | `0x08` | `ExactOutputSingleParams`: one pool, exact output. | | Action | `SWAP_EXACT_OUT` | `0x09` | `ExactOutputParams`: a route, exact output. | | Action | `SETTLE_ALL` | `0x0c` | `(address currency, uint256 maxAmount)`. Pays what the swap owes, from the caller: through Permit2, or out of the call's value. | | Action | `TAKE_ALL` | `0x0f` | `(address currency, uint256 minAmount)`. Delivers what the swap made, to the caller. | - **The structs depend on the router's build.** The samples below are what the deployed router requires: a `uint256 minHopPriceX36` before `hookData` in a one-pool swap, and a `uint256[] minHopPriceX36` after `path` in a route. The structs in many older samples have neither, and builds of the Universal Router differ, so check the router the registry names before you encode. - **How to check.** `execute` takes bytes, so no ABI shows these structs, but a build's ABI lists its errors: `InvalidHopPriceLength` is there on a build that takes the field. Confirm it in the router's source. - **The wrong shape does not fail cleanly.** Sent to the deployed router, the older struct reverts with no data where `currency0` is an ERC-20. Where `currency0` is native it goes through, so a test on a native-quoted pool proves nothing. - **What the field does.** It is the least a hop may pay, as `amountOut × 1e36 / amountIn`, and a hop under it reverts `V4TooLittleReceivedPerHopSingle` or, in a route, `V4TooLittleReceivedPerHop`; on exact output, `V4TooMuchRequestedPerHopSingle` or `V4TooMuchRequestedPerHop`. Zero, or an empty list, turns it off; a list has one entry per pool, or the swap reverts `InvalidHopPriceLength`. The samples turn it off: `amountOutMinimum` already holds the whole trade. ### Set up the encoders In these samples `universalRouter`, `permit2` and `quoter` are the registry's addresses, read as under [Read, quote and trade](https://montramarkets.xyz/docs/build#read-quote-and-trade), and `view`, `zeroForOne` and `amountIn` are as they are there. `trade` sends one `V4_SWAP`. With a native input it adds a `SWEEP`, so nothing the swap did not spend stays behind. ```ts // execute(bytes commands, bytes[] inputs, uint256 deadline) payable import { encodeAbiParameters, pad, parseAbi, parseAbiParameters, zeroAddress } from "viem"; const poolKey = "(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) poolKey"; const pathKeys = "(address intermediateCurrency, uint24 fee, int24 tickSpacing, address hooks, bytes hookData)[] path"; // the swap actions' params, as the deployed router takes them const exactInSingle = parseAbiParameters(`(${poolKey}, bool zeroForOne, uint128 amountIn, uint128 amountOutMinimum, uint256 minHopPriceX36, bytes hookData)`); const exactIn = parseAbiParameters(`(address currencyIn, ${pathKeys}, uint256[] minHopPriceX36, uint128 amountIn, uint128 amountOutMinimum)`); const exactOutSingle = parseAbiParameters(`(${poolKey}, bool zeroForOne, uint128 amountOut, uint128 amountInMaximum, uint256 minHopPriceX36, bytes hookData)`); const exactOut = parseAbiParameters(`(address currencyOut, ${pathKeys}, uint256[] minHopPriceX36, uint128 amountOut, uint128 amountInMaximum)`); // SETTLE_ALL's and TAKE_ALL's params const currencyAmount = parseAbiParameters("address, uint256"); // one V4_SWAP command's input: its actions, a byte each, and their params const v4Swap = (actions, params) => encodeAbiParameters(parseAbiParameters("bytes, bytes[]"), [actions, params]); // a SWEEP of the native currency to the caller, which the router reads from address 1 const sweep = encodeAbiParameters(parseAbiParameters("address, address, uint256"), [zeroAddress, pad("0x01", { size: 20 }), 0n]); const universalRouterAbi = parseAbi(["function execute(bytes commands, bytes[] inputs, uint256 deadline) payable"]); const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); const trade = (swap, input, value) => wallet.writeContract({ address: universalRouter, abi: universalRouterAbi, functionName: "execute", args: input === zeroAddress ? ["0x1004", [swap, sweep], deadline] : ["0x10", [swap], deadline], value: input === zeroAddress ? value : 0n, }); ``` ### Approve an ERC-20 input Every ERC-20 the router spends goes through Permit2: the launch token on a sale, and the quote on a buy in an ERC-20. Each token needs two approvals: the token's own `approve` to Permit2, then Permit2's `approve` to the router, with an amount and an expiry. Read the second with `allowance` and renew it in time: an expired one reverts `AllowanceExpired`, and one too small `InsufficientAllowance`. An expiry of zero lasts only the block. A native input needs neither. ```ts // approve(address token, address spender, uint160 amount, uint48 expiration) import { erc20Abi, maxUint256 } from "viem"; const permit2Abi = parseAbi([ "function approve(address token, address spender, uint160 amount, uint48 expiration)", "function allowance(address user, address token, address spender) view returns (uint160 amount, uint48 expiration, uint48 nonce)", ]); const input = zeroForOne ? view.key.currency0 : view.key.currency1; // once a token await wallet.writeContract({ address: input, abi: erc20Abi, functionName: "approve", args: [permit2, maxUint256], }); // again when it runs out or expires: an hour here await wallet.writeContract({ address: permit2, abi: permit2Abi, functionName: "approve", args: [input, universalRouter, amountIn, Math.floor(Date.now() / 1000) + 3600], }); ``` ### Quote with the V4Quoter The V4Quoter's structs have no `minHopPriceX36`. It quotes either side and returns the amount and a gas estimate. It is not marked view, so make it an `eth_call`. What it answers is what the swap then does, and it reverts as under [Read a refusal](https://montramarkets.xyz/docs/build#read-a-refusal). ```ts // quoteExactInputSingle(params) returns (uint256 amountOut, uint256 gasEstimate) const quoterAbi = parseAbi([ `function quoteExactInputSingle((${poolKey}, bool zeroForOne, uint128 exactAmount, bytes hookData) params) returns (uint256 amountOut, uint256 gasEstimate)`, `function quoteExactOutputSingle((${poolKey}, bool zeroForOne, uint128 exactAmount, bytes hookData) params) returns (uint256 amountIn, uint256 gasEstimate)`, `function quoteExactInput((address exactCurrency, ${pathKeys}, uint128 exactAmount) params) returns (uint256 amountOut, uint256 gasEstimate)`, `function quoteExactOutput((address exactCurrency, ${pathKeys}, uint128 exactAmount) params) returns (uint256 amountIn, uint256 gasEstimate)`, ]); const { result: [quoted] } = await client.simulateContract({ address: quoter, abi: quoterAbi, functionName: "quoteExactInputSingle", args: [{ poolKey: view.key, zeroForOne, exactAmount: amountIn, hookData: "0x" }], }); ``` ### Swap one pool A buy or a sale, as `zeroForOne` says. `SETTLE_ALL` pays the input and `TAKE_ALL` delivers the output, each with its limit. The caller is both the payer and who receives. ```ts const input = zeroForOne ? view.key.currency0 : view.key.currency1; const output = zeroForOne ? view.key.currency1 : view.key.currency0; const minimum = quoted * 99n / 100n; // 1% slippage const swap = v4Swap("0x060c0f", [ // SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL encodeAbiParameters(exactInSingle, [{ poolKey: view.key, zeroForOne, amountIn, amountOutMinimum: minimum, minHopPriceX36: 0n, hookData: "0x", }]), encodeAbiParameters(currencyAmount, [input, amountIn]), encodeAbiParameters(currencyAmount, [output, minimum]), ]); await trade(swap, input, amountIn); ``` ### Swap through several pools `path` lists the pools in order, each entry naming the currency its hop ends in. This one buys a token quoted in an ERC-20 with native HYPE: through a plain pool into `quote`, then through the launch pool into `token`. A sale runs the same pools the other way: the launch pool's entry first, ending in `quote`, then the plain pool's, ending in the zero address. The hook's fee is charged once, on the launch pool's hop. ```ts const path = [ { intermediateCurrency: quote, fee: plain.fee, tickSpacing: plain.tickSpacing, hooks: plain.hooks, hookData: "0x" }, { intermediateCurrency: token, fee: view.key.fee, tickSpacing: view.key.tickSpacing, hooks: view.key.hooks, hookData: "0x" }, ]; const { result: [routeQuote] } = await client.simulateContract({ address: quoter, abi: quoterAbi, functionName: "quoteExactInput", args: [{ exactCurrency: zeroAddress, path, exactAmount: amountIn }], }); const minimum = routeQuote * 99n / 100n; // 1% slippage const swap = v4Swap("0x070c0f", [ // SWAP_EXACT_IN, SETTLE_ALL, TAKE_ALL encodeAbiParameters(exactIn, [{ currencyIn: zeroAddress, path, minHopPriceX36: [], amountIn, amountOutMinimum: minimum }]), encodeAbiParameters(currencyAmount, [zeroAddress, amountIn]), encodeAbiParameters(currencyAmount, [token, minimum]), ]); await trade(swap, zeroAddress, amountIn); ``` ### Exact output You name what you receive and cap what you pay. A native input sends the whole cap as the call's value and pays only what the swap costs. **Without a `SWEEP`, the surplus stays in the router, and any caller's `SWEEP` takes it.** An ERC-20 input is pulled for exactly what the swap costs, so it leaves nothing. A route uses `SWAP_EXACT_OUT` (`0x09`) and `ExactOutputParams`, whose `path` names each hop by the currency it starts from. ```ts const input = zeroForOne ? view.key.currency0 : view.key.currency1; const output = zeroForOne ? view.key.currency1 : view.key.currency0; const { result: [cost] } = await client.simulateContract({ address: quoter, abi: quoterAbi, functionName: "quoteExactOutputSingle", args: [{ poolKey: view.key, zeroForOne, exactAmount: wanted, hookData: "0x" }], }); const maximum = cost * 102n / 100n; // 2% slippage const swap = v4Swap("0x080c0f", [ // SWAP_EXACT_OUT_SINGLE, SETTLE_ALL, TAKE_ALL encodeAbiParameters(exactOutSingle, [{ poolKey: view.key, zeroForOne, amountOut: wanted, amountInMaximum: maximum, minHopPriceX36: 0n, hookData: "0x", }]), encodeAbiParameters(currencyAmount, [input, maximum]), encodeAbiParameters(currencyAmount, [output, wanted]), ]); await trade(swap, input, maximum); // a native input sends the cap, and the SWEEP returns the rest ``` A trade through this router can also revert `V4TooLittleReceived`, under its minimum; `V4TooMuchRequested`, over its cap; or `TransactionDeadlinePassed`. A native input whose value does not cover the swap reverts with no data. ## Launch a token One call to the factory makes the token, its pool, its locked liquidity and the first buy. Whoever calls `launch` is the creator. A product that sends launches can earn a share of the fees they go on to pay: [Builder programme](https://montramarkets.xyz/docs/builders). In these samples `factory` is the registry's `factory`, read as above, `factoryAbi` is [`LaunchFactory.json`](https://montramarkets.xyz/docs/abis/LaunchFactory.json), and `zeroAddress`, `zeroHash` and `parseEventLogs` are viem's. ### Upload its image and metadata **A token carries its image in two places, and both are fixed at launch, so set both.** `metadataURI` names a document, the usual place wallets and explorers look; `profile.logo` keeps the image on the token itself, in the form terminals read through `getTokenInfo()`. The site shows the document's image, and the token's `logo` where there is no document or it names none. These two routes are what the site itself uses, and a script calls them the same way, with no key: the image first, as its raw bytes (PNG, JPEG, WebP or GIF, 2 MB at most, 20 an hour per connection), then a document naming it (`name`, `symbol`, `description`, `image`, and any of `website`, `twitter`, `telegram`, `discord`; 30 an hour). Upload shortly before you launch: anything no launch has named after about an hour may be cleared, and uploading the same file again gives the same URI, so it is safe to repeat. Once the launch is seen, both are pinned to IPFS. Or host your own: any `ipfs://` or `https://` URI to a JSON document with those fields works, naming an `ipfs://` or `https://` image. ```ts import { readFileSync } from "node:fs"; const api = "https://api.montramarkets.xyz"; const posted = async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body.error ?? r.statusText); return body; }; const image = await fetch(`${api}/api/metadata/image`, { method: "POST", headers: { "content-type": "image/png" }, body: readFileSync("logo.png"), }).then(posted); const metadata = await fetch(`${api}/api/metadata`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ name: "Example", symbol: "EXMPL", description: "What it is.", image: image.uri, website: "https://example.com", }), }).then(posted); ``` ### Choose a row A launch names a row of the factory's menu by `configId`. Ids count from 0, `configCount()` says how many there are and `getConfig(id)` returns a row: one tier for one quote asset, launchable while `enabled`. Rows are added and switched off, so read them rather than hard-coding an id. The row picks the hook: `getModuleSet(row.moduleSetId).hook`, not the newest of the registry's `hooks()`. That list holds every hook ever published and never loses one, so a pool's hook is checked against it, and a launch's is chosen by its row. ```ts // ids run from 0 to count - 1: read each, and offer those with row.enabled const count = await client.readContract({ address: factory, abi: factoryAbi, functionName: "configCount" }); const row = await client.readContract({ address: factory, abi: factoryAbi, functionName: "getConfig", args: [configId] }); const { hook } = await client.readContract({ address: factory, abi: factoryAbi, functionName: "getModuleSet", args: [row.moduleSetId], }); ``` ### Build the launch `LaunchParams` has twelve fields, in the order below; each is in the [structs](https://montramarkets.xyz/docs/reference#structs-and-the-stages). The launch fee is native and separate: on a native-quoted row `value` is the fee plus the first buy, and on an ERC-20 row it is the fee alone, with the first buy pulled from the sender, who approves the factory first or passes a `permit`. `firstBuyAmount` is the whole first buy, its fee included, and `firstBuyMinOut` the fewest tokens you accept for it, 0 for any: it lands in the launch's own transaction, so nothing can trade ahead of it. There is no pool to quote yet: to show what it will buy, `LaunchLens.seedPreview(factory, configId, supply)` gives the pool it opens on. ```ts const params = { name: "Example", symbol: "EXMPL", metadataURI: metadata.uri, // the document, with the image in it profile: { logo: image.uri, // the same image, kept on the token description: "What it is.", socials: { telegram: "", twitter: "", discord: "", website: "", extra: "" }, }, configId, supply: 0n, // the row's own salt: "0x…", // any 32 bytes of your own feeRecipients: [], // the creator's own wallet feeShares: [], firstBuyAmount, firstBuyMinOut, permit: { deadline: 0n, v: 0, r: zeroHash, s: zeroHash }, // none }; const launchFee = await client.readContract({ address: factory, abi: factoryAbi, functionName: "launchFee" }); const value = row.quote === zeroAddress ? launchFee + firstBuyAmount : launchFee; ``` ### Send it and read it back `TokenLaunched` describes the launch in full. Its `firstBuyIn` is `firstBuyAmount`, the swap's fee included, its `firstBuyOut` the tokens the creator received after that fee, and the launch fee is in neither. A product's builder code goes on as `dataSuffix`: [Attributing a launch](https://montramarkets.xyz/docs/builders#attributing-a-launch). ```ts await client.simulateContract({ address: factory, abi: factoryAbi, functionName: "launch", args: [params], account, value }); const hash = await wallet.writeContract({ address: factory, abi: factoryAbi, functionName: "launch", args: [params], value }); const receipt = await client.waitForTransactionReceipt({ hash }); const [launched] = parseEventLogs({ abi: factoryAbi, eventName: "TokenLaunched", logs: receipt.logs }); const { token, poolId, firstBuyIn, firstBuyOut } = launched.args; ``` ## Link to a token A token's page is `https://montramarkets.xyz/token/
`, the address in lowercase; any other spelling redirects there. A wallet's is `/wallet/
`, and an approved builder's `/builders/`. ## Which fee to show | Read | What it is for | | --- | --- | | `LaunchLens.feeBreakdown(hook, poolId).total` | **Show this as the pool's fee.** In pips (1,000,000 is 100%), the whole fee a sale pays, and a buy outside an anti-snipe window, the LP fee included once it is on; the same read gives its split. | | `LaunchLens.poolView(hook, poolId).buyFee` | What a buy pays now, the hook's own `currentBuyFeeRate(poolId)`: the total, or inside the pool's anti-snipe window the higher rate falling to it. **Show a buyer its price**: a buy pays `1 / (1 − buyEffectiveTotalFee / 1e6)` times the pool's price, before its move. Montra's site asks before a buy above 10%, which only a window can charge. Showing a window: [The anti-snipe fee](https://montramarkets.xyz/docs/anti-snipe#show-it). | | `LaunchToken.taxBps()` | The total in basis points, rounded up, from the token alone, for a scanner that reads only tokens (`sellTaxRate()` says the same; `buyTaxRate()` says what a buy pays now, more than the total inside an anti-snipe window; `taxRatePips()` gives the total in exact pips). | | `lp`, `protocol`, `creator` | Where the total goes: the pool's liquidity providers, Montra, and the creator side. | | `effectiveTotal` | The hook's fee and the LP fee compounded, a hair under `total` once the LP fee is on: what a trade's amounts work out to, for exact arithmetic. | | `LaunchRouter.quoteExactIn` | What one trade of a given size delivers after every fee and the price's move: the trade's own figure. | | `coreProtocolFeeZeroForOne`, `coreProtocolFeeOneForZero` (`poolView`) | Uniswap's own fee, on top and zero unless Uniswap switches it on: show it beside the total when it is not zero. | Never show `Swap.fee` or slot0's LP fee, which read zero until the LP line while every trade pays the total, nor the key's `0x800000`, which is only the dynamic-fee flag. There is no transfer tax. ## The rules a pool keeps No tier of the menu has a whitelist, per-wallet logic or a trading window: a pool trades from its first block, and the hook sees the router as the sender, never the trader. The fee is netted into the swap's deltas, so standard routers and quoters handle it, and quote exactly what the trade pays. | Rule | What to expect | | --- | --- | | Partial fills | Refused: a swap must fill the whole amount it names, whichever currency it names. One stopped part-way by its price limit, or run out of liquidity, reverts `PartialFillRejected`, so no swap can set a price without trading. Set no price limit (the extremes, as V4Router does) and express slippage as a minimum output; a price limit is fill-or-revert, never fill-up-to. | | The opening | A sale may take the price back to the pool's opening and no further: one that would leave it past the opening reverts `PastOpening`. No ordinary sale meets it: every swap rounds in the pool's favour, so the tokens outstanding never carry the price back past the opening. | | Donations | Refused (`DonationsLocked`), so nothing pays a pool's liquidity outside a trade. A position that others' trades fill pays no hook fee on what it sells or buys that way; the trades that fill it pay theirs. | | Liquidity | Anyone may add; removals have no hook callback and always work. Positions earn the LP fee (experimental) from the LP line on. | | The fee now | `poolConfig(poolId).totalFeePips` is the advertised total, which only ever falls; the hook takes it less the LP fee in force, and the pool charges that LP fee from slot0. Model the next trade with `LaunchLens.feeBreakdown(hook, poolId)`, not slot0 alone: the LP line switches on inside the `beforeSwap` of the first trade after the hold, so that trade already pays the LP fee while slot0 still reads zero. | | Terms per pool | Read every pool's terms from the chain (`poolConfig(poolId)`, `feeBreakdown(hook, poolId)`) and never hard-code the tiers: a pool keeps the row it launched with, a total its recipient lowered and a protocol share the owner lowered, and a row off the menu is the governor's to publish. The [menu](https://montramarkets.xyz/docs/fees) is what launches on mainnet are offered. | | hookData | No tier of the menu reads it. Send empty bytes. | | Anti-snipe fee | A pool's terms may carry one (`poolConfig(poolId).earlyBuyFeeUntil` not zero). From the launch second a buy pays `earlyBuyFeeStartPips` (at most 99%), falling in a straight line to the total at `earlyBuyFeeUntil` (at most 30 minutes after launch), the drop rounded down; a sale never pays it, and neither does the factory's own first buy in the launch transaction. Read it as `currentBuyFeeRate(poolId)` at the latest block's time, never the wall clock's: it only falls, so a figure read earlier errs high. The hook takes it in the swap's deltas like the rest of its fee, so v4's `Swap` never shows it: what a buyer paid is the `Swap`'s quote amount plus `FeeAccrued`'s `fee`. The formula is under [Contract reference](https://montramarkets.xyz/docs/reference); how to read, track and show it, under [The anti-snipe fee](https://montramarkets.xyz/docs/anti-snipe). | | Other routers | Uniswap's Universal Router (`V4_SWAP`), V4Router and V4Quoter trade and quote a Montra pool as any v4 pool, multi-hop included, the hook's fee charged once where the route crosses its pool. The quoter's answer is exactly what the router then trades (the contracts' parity suite holds the two together), as is `LaunchRouter.quoteExactIn`'s on a Montra pool. The registry names their addresses under `universalRouter`, `permit2` and `quoter`, and `contracts` in [`/api/config`](https://montramarkets.xyz/docs/api) repeats them, each null until it is set. An exact-output quote needs the V4Quoter: `quoteExactIn` quotes exact input alone. How to encode a trade is under [Trade through the Universal Router](https://montramarkets.xyz/docs/build#trade-through-the-universal-router). | | Gas | Through LaunchRouter about 140-250k for a buy and 140-210k for a sell, by what the trade does: crossing or confirming a line, settling holder rewards mid-stream, moving an ERC-20 quote (measured over 60 trades); a buy inside an anti-snipe window about 3k more. A plain test router on a local chain takes 164k and 173k (the contracts' gas snapshot). Estimate each trade and add a margin. | **The LP fee is experimental.** Its rates, and the line it starts at, may change for launches to come; a pool keeps the LP fee and the line it launched with. Where the hook takes its fee in each of the four shapes a swap can take; h is the hook fee in pips (the swap's rate less the LP fee in force: the total, or a buy's anti-snipe rate inside its window). Floors in every case. | Swap | Fee taken in | How | Fee | | --- | --- | --- | --- | | Exact-input buy | `beforeSwap` | The quote is the amount you name. The hook takes its fee off it first; the pool swaps the rest and charges its LP fee on that. | `fee = amountIn × h / 1e6` | | Exact-output buy | `afterSwap` | You name the tokens. The quote is worked out by the swap; the hook adds its fee to what you pay. | `fee = quoteIn × h / (1e6 − h)` | | Exact-input sell | `afterSwap` | You name the tokens. The quote is what the pool pays out; the hook's fee comes off it. | `fee = quoteOut × h / 1e6` | | Exact-output sell | `beforeSwap` | You name the quote you want. The pool pays out that plus the hook's fee, which the hook keeps. | `fee = amountOut × h / (1e6 − h)` | The fee is always in the quote asset and returned as a hook delta (`beforeSwapReturnDelta` when the quote is the specified currency, `afterSwapReturnDelta` when it is not), so the caller's delta already includes it. Exact-output swaps gross the fee up, so the rate is the same in both directions. Every shape fills the whole amount it names or reverts (`PartialFillRejected`). | Hook callback | Bit | | --- | --- | | `beforeInitialize` | 1<<13 | | `afterInitialize` | 1<<12 | | `beforeAddLiquidity` | 1<<11 | | `beforeSwap` | 1<<7 | | `afterSwap` | 1<<6 | | `beforeDonate` | 1<<5 | | `beforeSwapReturnDelta` | 1<<3 | | `afterSwapReturnDelta` | 1<<2 | v4 reads a hook's permissions from the low 14 bits of its address; this hook's are 0x38EC: nothing on removing liquidity and nothing after a donation. A later hook may carry more, so trust a hook because the factory announced it, not by its bits. --- # The anti-snipe fee URL: https://montramarkets.xyz/docs/anti-snipe 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 pool's anti-snipe window, its start and length each pool's own. | Swap | From the launch until `earlyBuyFeeUntil` | From `earlyBuyFeeUntil` on | | --- | --- | --- | | A buy | Pays the rate: at most 99% in the launch second, falling in a straight line to the pool's fee | Pays the pool's fee | | A sale | Pays the pool's fee | Pays the pool's fee | 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`, `earlyBuyFeeStartPips` and `earlyBuyFeeWindow` from the chain or the API. On chain `earlyBuyFeeUntil` is a Unix second; the API gives it in milliseconds. [Detect it](https://montramarkets.xyz/docs/anti-snipe#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](https://montramarkets.xyz/docs/anti-snipe#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](https://montramarkets.xyz/docs/anti-snipe#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](https://montramarkets.xyz/docs/anti-snipe#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](https://montramarkets.xyz/docs/anti-snipe#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)`. | 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. | 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](https://montramarkets.xyz/docs/reference#fees-launchhook-launchlens). ## Detect it | 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](https://montramarkets.xyz/docs/build#find-and-trust-a-pool). ### Read the window In these samples `client`, `wallet`, `account`, `lens`, `router`, `lensAbi` and `routerAbi` are as under [Read, quote and trade](https://montramarkets.xyz/docs/build#read-quote-and-trade), `token` is the token's address, `tokenAbi` and `hookAbi` are [`LaunchToken.json`](https://montramarkets.xyz/docs/abis/LaunchToken.json) and [`LaunchHook.json`](https://montramarkets.xyz/docs/abis/LaunchHook.json), and the PoolManager's ABI is [`PoolManager.json`](https://montramarkets.xyz/docs/abis/PoolManager.json). viem returns a `uint40` or a `uint24` as a number, a `uint256` and a block's time as a bigint. ```ts // 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 ``` ### Read what a buy pays now 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. ```ts // 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" }); ``` ### From the API 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. ```sh 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](https://montramarkets.xyz/docs/anti-snipe#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](https://montramarkets.xyz/docs/build#read-quote-and-trade)'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](https://montramarkets.xyz/docs/build#trade-through-the-universal-router). ### Quote and send a buy 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](https://montramarkets.xyz/docs/build#read-quote-and-trade). ```ts // 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. | 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.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](https://montramarkets.xyz/docs/reference#fees-launchhook-launchlens). 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 | Event | What it says of the window | | --- | --- | | `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](https://montramarkets.xyz/docs/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. | 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()` 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. --- # Builder programme URL: https://montramarkets.xyz/docs/builders Montra Builders rewards products that bring new launches to Montra: 25% of the protocol fee on every trade in a token launched through a builder's product, for 90 days, paid every week in HYPE. Trading itself is not rewarded. - **Launch share:** 25%. Of Montra's fee on every trade in a token launched through your product, for 90 days: $0.10 of every $100 traded. - **For:** 90 days. From each token's launch, through whatever interface it is traded. - **Paid:** Weekly. In HYPE, from Montra's treasury multisig, after 48 hours of review. - **Statements:** Public. Line by line, so anyone can recompute them from the chain. ## What it is Every trade on Montra pays the protocol $0.40 of each $100 traded, on every fee tier. A builder (a launch site, a bot, an agent, a wallet, a terminal's launch button) that brings new launches earns a share of that fee on the tokens launched through it, worked out from on-chain data and paid from Montra's treasury each week. Nothing in the contracts changes: a token, its pool and its fees work the same whether or not a launch is attributed. A builder registers a code with the wallet that will be paid, by signing the programme's [terms](https://montramarkets.xyz/docs/builder-terms) with it, on the site; signing costs no gas and moves no funds. Credit starts at once. Builders with a working product and users are approved and paid; until then what they earn is held, not lost. [Join the programme](https://montramarkets.xyz/builders/join), or read [the programme's page](https://montramarkets.xyz/builders) first. - **Launch share:** 25% of the protocol's fee on every trade in a token launched with your code, for 90 days from its launch. - **Through any interface:** the share is of the fee each trade of the token paid, through whatever interface the trade was made, except the trades under what is not paid; the builder is paid for bringing the token, never for trading it. - **Never on volume alone:** every payout is a share of a fee a trader actually paid, and trading volume itself earns nothing. ## Registering [One form](https://montramarkets.xyz/builders/join) and one signature. What it asks for: | Step | What it asks | | --- | --- | | Your wallet | The wallet that signs the terms, and the one the programme pays. A wallet registers once. | | Your code | The word your product's launches carry. | | Your product | Its name and what it is: a terminal, a bot, an agent, a launch site, a wallet or a tool. A website and a sentence or two on what it does are optional. These show in the directory once you are approved. | | How to reach you | An email, or a Telegram or X handle: at least one. Only the programme's team sees these. Who is responsible is optional until payouts pass the identity threshold. | | The terms | You accept the [terms](https://montramarkets.xyz/docs/builder-terms), then sign the registration with your wallet. | **Your code** is 3 to 32 characters: lowercase letters, digits and dashes, starting with a letter or a digit (`^[a-z0-9][a-z0-9-]{2,31}$`). It can't be changed, but a builder can hold up to five. The programme's own words, the ecosystem's names and anything that reads as the launchpad's are reserved. ## Attributing a launch Put your code on the launch transaction as an ERC-8021 suffix: the code's ASCII, its length in a byte, the schema (`00`) and the 16-byte marker `0x8021…8021`, appended after the factory call's calldata. The factory never reads it, so the launch is exactly the same; Montra's indexer reads it from the transaction. Building the launch itself, every field of it, is under [Launch a token](https://montramarkets.xyz/docs/build#launch-a-token). [The guide](https://montramarkets.xyz/builders/guide) has these lines, and a builder's desk has them with its own code in them. viem: the launch with your code: ```ts // "my-terminal" in ASCII, its length (11 bytes), schema 0, then the ERC-8021 marker const dataSuffix = "0x6d792d7465726d696e616c" + "0b" + "00" + "80218021802180218021802180218021"; const { request } = await client.simulateContract({ address: factory, abi: factoryAbi, functionName: "launch", args: [params], account, value, dataSuffix, }); // then send `request` from your wallet client, as any write: the suffix goes with it ``` ethers v6: the same suffix: ```ts const tx = await factory.launch.populateTransaction(params, { value }); tx.data = tx.data + dataSuffix.slice(2); // after everything the factory reads await signer.sendTransaction(tx); ``` - A code counts only on launches made after it was registered, and one builder is credited for each launch: a launcher contract registered to you that sent it, otherwise the first registered code in the calldata, read from its start. - A smart wallet that wraps the call carries the factory's calldata, suffix and all, inside its own, and the code is still found. A transaction that launches several tokens is credited only through a launcher contract. - Montra's own site tags the launches it sends with a code of its own: those are Montra's, and never paid. ## A launcher contract A launch your registered contract sends is yours, with or without a suffix. The factory takes whoever calls `launch` as the token's creator, salts its address with it and sends the first buy to it, so through your contract the creator is your contract: name your user in `feeRecipients` and pass the first buy on in the same call. Register the contract on your desk; it is proven yours by its deployment transaction from your wallet or its `owner()` being your wallet, or checked by hand. A launch whose user is you, or whose fees pay you, earns nothing; a slot of your own in the creator's fee split, at most 20%, is your own fee and allowed. ## Trading integrations Everything a trading integration needs is free: the docs, the ABIs, the API and, once it is published, the SDK. Trading volume itself isn't rewarded: a product that only trades, in any volume, earns nothing under the programme. Quoting and trading a pool are in [Build on Montra](https://montramarkets.xyz/docs/build), the tokens, wallets and live trades in the [Public API](https://montramarkets.xyz/docs/api), and every address and ABI under [Contracts](https://montramarkets.xyz/docs/contracts). ## Statements and payouts | Rule | What it is | | --- | --- | | Period | A week, from Monday 00:00 UTC to the next. | | Review | 48 hours after a week closes, its statements are published, then paid. | | Currency | HYPE. A share earned in another quote is converted at the rate the treasury itself got for that currency in the week, as the vault's `HubSold` events record it; without one, at the last such rate, never above what Montra's fee market prices the currency at; or at a rate the programme sets and states on the statement. | | Cap | Launch shares together at most 6% of the week's protocol fees; above it, every builder's share is scaled by the same factor. | | Minimum | 0.5 HYPE; a smaller balance carries to the next week. | | New builders | At most 25 HYPE a payout until the builder's identity is verified or it has been paid 4 times; the rest stays owed. | | Identity | Asked for before a builder's payouts pass 250 HYPE in twelve months. | | Wallet | The address the builder registered, an ordinary account or a contract wallet. Each batch tries the transfer first; a wallet that refuses it is left out of that batch, and its balance stays owed. | | Not paid | Trading volume itself; a builder's own launches; trades by the builder, or by a token's creator, in the tokens launched through its product; buys inside a token's anti-snipe window; anything judged to be wash trading or self-dealing. | Each statement lists every pool a builder earned in, the trades counted, the protocol's part, the share, the rate and the cap, and carries the SHA-256 of its document. Every figure is from public data: the hook's `FeeAccrued` events for each pool, the launch transaction's calldata and the vault's `HubSold`. Payouts are plain HYPE transfers from the treasury multisig in one batch a week; each shows on the builder's desk with its transaction. A builder may object to a statement within 7 days. ## Your desk and your public page [The site's builders page](https://montramarkets.xyz/builders) has the programme and the directory of approved builders. [A builder's desk](https://montramarkets.xyz/builders/me), found by its wallet, gathers what it needs: its code in lines it can paste, a check of any launch transaction, the tokens credited to it, its earnings week by week, its payouts and what the next one will be, files of all three, and its profile, contacts, codes and contracts, each change a signature of its own. Each approved builder has a public page, `/builders/`, made to be posted: the tokens launched through it, what they have traded and what it has been paid, with a share card drawn from the same figures. The terms are version 2026-11-01; changes that lower a share or a cap apply from a week that starts at least 7 days after they are announced. --- # Builder terms URL: https://montramarkets.xyz/docs/builder-terms What a builder earns, when and how it is paid, and what is not paid: the words a builder signs when it registers. _Version 2026-11-01, of 1 Nov 2026._ ## What the programme is Montra Builders rewards products that bring new launches to Montra: launch sites, bots, agents, wallets, and the launch buttons of terminals. A builder earns a share of the protocol fee from every token launched through its product. These terms say what a builder earns, when and how it is paid, and what is not paid. Trading is not rewarded. A product that only trades Montra's tokens, however much, earns nothing under these terms; everything it needs to integrate is free: the docs, the ABIs, the API and, once it is published, the SDK. ## Joining You register a builder code with the wallet that will be paid, by signing these terms with it. Signing costs no gas and moves no funds. Credit starts from that moment. Only approved builders are paid. To be approved you need a working product with users, a contact we can reach, and these terms accepted. We may ask for more information before approving, and may decline without giving a reason. ## What you earn Launch share: 25% of Montra's protocol fee on every trade in a token launched with your code, for 90 days from the token's launch. A launch counts when it is sent by a launcher contract registered to you, or when its transaction carries your code as an ERC-8021 suffix, and was made after you registered that contract or code. One builder is credited for each launch: a registered launcher that sent it first; otherwise the first registered code in the transaction's calldata, read from its start. A launch made from Montra's own site, or carrying Montra's code, is Montra's. A transaction that launches several tokens is credited through launcher contracts only. Your share is of the protocol fee each trade of the token actually paid, as the chain records it, through whatever interface the trade was made, except the trades under What is not paid; the protocol fee is 0.40% of every trade today. You are paid for bringing the token, never for trading it. ## The cap In each period, launch shares earned by all builders together are at most 6% of that period's protocol fees. Above that, every builder's earnings for the period are scaled down by the same factor. ## What is not paid Launches or trades made by you or by addresses linked to you: your wallet, your registered contracts, and addresses we know to be yours. A launch whose fees are paid to you, except a slot of your own in the creator's fee split of at most 20%, which is your fee and allowed. Trades in a token by its creator, its fee recipients or the wallet that launched it, and trades made by Montra's own contracts. Buys inside a token's early-buy window, which runs from its launch to the time its pool records on chain, earn nothing. A sale counts, inside the window or not. Anything we judge to be wash trading, self-dealing or an attempt to game the programme. Nothing is ever paid on volume alone: every payout is a share of fees actually paid. ## Periods and payouts A period is one week, from Monday 00:00 UTC to the next. After 48 hours of review, each period's statements are published, and you are paid in HYPE from Montra's treasury multisig to your registered wallet, usually within the week that follows. Earnings in a currency other than HYPE are converted at the rate Montra's treasury received for that currency in that period, as recorded on chain, or, without one, the most recent such rate, never above what Montra's fee market prices the currency at when the period is settled; or at a rate we set and state on the statement. Below 0.5 HYPE, your balance carries to the next period. Until your identity is verified, or you have been paid four times, each payout is at most 25 HYPE and the rest stays owed for the next. Your wallet must be a plain account, or a contract wallet, able to receive HYPE on Elysium. ## Statements Every statement lists the pools, the trades counted, the protocol fees, the rates and the cap that make it up, so that anyone can recompute it from public chain data. You may object to a statement within 7 days of its publication. A correction is made in a later period. ## Review, holds and removal We review every period before it is paid. The earnings of a builder awaiting approval or under review are held, not lost, until the review ends. We may exclude specific lines from a statement, with the reason on it. We suspend a builder at once only with evidence connecting it to the abuse we are looking into, and remove a builder for abuse, which forfeits its unpaid earnings. You may leave at any time, and what you earned before is paid under these terms. ## Identity Before your payouts pass 250 HYPE in any twelve months, we ask for the identity of the person or company responsible. You are responsible for your own taxes. ## Changes A lower share or a lower cap applies from a period that starts at least 7 days after we announce it; an increase applies from the next period. We may end the programme with 14 days' notice. What was earned until then is paid, and a builder still awaiting approval then is paid what it earned if it is approved within 30 days. ## Your own fees You may charge your own users your own fee, in your own product or as a slot in a creator's fee split shown to the creator. The programme changes no contract: tokens, pools and fees work the same whether or not a launch is attributed. ## No promise Montra runs the programme as it is. Payouts are made under these terms at Montra's discretion; nothing here is an investment, a security or a promise of earnings. Using Montra's contracts is at your own risk. --- # Contract reference URL: https://montramarkets.xyz/docs/reference What each part of Montra does, function by function, with each signature's types, the structs a read returns field by field, and the arithmetic behind the figures the other pages show. - **Fees in pips:** 1,000,000. Is 100%, of the quote leg. - **Pool fee field:** 0x800000. Dynamic: the LP fee lives in the pool's slot0. - **Tick spacing:** 200. On every published tier. - **Hook flags:** 0x38EC. The low 14 bits of the hook's address. ## The ABIs Every contract's ABI is published from the contracts' build: [`/docs/abis.json`](https://montramarkets.xyz/docs/abis.json) for all of them, one per contract at `/docs/abis/.json`, and every event once at [`/docs/abis/events.json`](https://montramarkets.xyz/docs/abis/events.json) (the list, and reading them with a header, under [Build on Montra](https://montramarkets.xyz/docs/build)). The types below are the ABI's: a pool id is `bytes32`, a currency `address` (native HYPE is the zero address), a struct a tuple by its name. ## Launching · LaunchFactory LaunchFactory.launch: ```solidity // in this order, which is the ABI's: selector 0x579ba2d5 LaunchFactory.launch(LaunchParams { string name; string symbol; string metadataURI; Profile profile; // logo, description, socials { telegram, twitter, discord, website, extra } uint256 configId; // a published tier for one quote asset; ids count from 0 uint256 supply; // whole tokens × 1e18; 0 = the config's 1e9 bytes32 salt; // token address = clone(keccak256(abi.encode(msg.sender, salt))) address[] feeRecipients; // see "The creator's routes" uint16[] feeShares; uint256 firstBuyAmount; uint256 firstBuyMinOut; Permit permit; // deadline, v, r, s: an optional EIP-2612 for an ERC-20 first buy, all zero for none }) payable returns (address token, PoolId poolId) ``` - **Reads.** `configCount() view returns (uint256)`, `getConfig(uint256 id) view returns (LaunchConfig cfg)`, `getModuleSet(uint256 id) view returns (ModuleSet mods)` (`hook`, `tokenMaster`, `splitterMaster`, `exists`), `launchFee() view returns (uint256)`, `launchEnabled() view returns (bool)`. - **Value.** For a native-quoted tier `msg.value` is the launch fee plus the first buy; for an ERC-20 quote it is the launch fee alone and the first buy is pulled from the caller. - **The creator is `msg.sender`.** The token is an EIP-1167 clone at an address derived from the sender and the salt (`predictTokenAddress(address sender, bytes32 salt, uint256 configId) view returns (address)`), so nobody can take a pending address or pin a token on a wallet that never signed. - **Order in the transaction.** Clone and mint the supply to the hook; resolve the fee route (deploying a splitter for a split); `hook.register`; `PoolManager.initialize` at the opening tick; `hook.seed`; `token.initializePool` (pool id and reward terms); the first buy; `TokenLaunched`. - **Opening price.** Each tier is published for a base supply of 1e9 tokens at a start tick. A different supply shifts the tick by log(supply / 1e9) / log(1.0001), rounded to the tick spacing of 200, so the opening market cap stays the same to within about 1%. `LaunchLens.seedPreview(address factory, uint256 configId, uint256 supply) view returns (int24 startTick, Seed seed)` checks a custom supply before launching (its `Seed` is `(int24 tickLower, int24 tickUpper, uint128 liquidity)`). - **Quote assets.** `QuoteRegistry` lists them (`QuoteRegistered`, `QuoteEnabled`), six to eighteen decimals; native HYPE is listed from the start. Disabling a quote stops new launches in it; a pool's quote never changes. A Montra launch token is never listed. - **Reverts.** Among them: `LaunchesPaused`, `ConfigDisabled`, `InvalidConfigId`, `QuoteNotEnabled`, `IncorrectValue`, `FirstBuySlippage`, `SupplyOutOfRange`, `SupplyNotWholeCoins`, `InvalidFeeRoute`, `InvalidFeeRecipient`; a split's recipients and shares are checked by the splitter (`InvalidRecipients`). ## The token · LaunchToken - **A clone that cannot change.** Each token is an EIP-1167 clone of one master whose address is fixed in the clone's bytecode; the master locks itself against initialization. There is no proxy admin and no upgrade path. - **Verifying a token.** Compare its code hash with a clone of the master the factory published (`ModuleSetPublished`). Anyone can clone a master, but only the factory it names can initialize one, so a clone with a supply is a launch. - **Metadata.** One URI, fixed at launch, answers `contractURI()`, `tokenURI()` and `metaURI()`, each `returns (string)` (ERC-7572; there is no `tokenURI(uint256)`). The profile, also fixed, answers `logo()`, `description()` (`string`), `socials()` (`Socials`) and `getTokenInfo() returns (address creator_, string logo_, string description_, Socials socials_)`, Socials being `(string telegram, string twitter, string discord, string website, string extra)`, its outputs named as here, underscores included. `deployer() returns (address)` is the creator under another name. The strings are stored as the launcher sent them: bound and escape them, and render only `http(s)://` and `ipfs://` links. - **The metadata document.** A launch through Montra's site names an `ipfs://` document whose JSON has `name`, `symbol` and `description` (strings), `attributes` (an array of `{ "trait_type": string, "value": string }`: `launch_provider: montra` and `chain: elysium`) and, where given, `image` (`ipfs://`, the same string as `logo()`), `website` (also as `external_url`), `twitter`, `telegram` and `discord` (links). A launch sent to the factory by other means may name anything, so prefer the on-chain profile. To give a launch of your own its image the same way, see [Launch a token](https://montramarkets.xyz/docs/build#launch-a-token). Read `ipfs://` through a gateway that serves it: `https://ipfs.filebase.io/ipfs/` does; `ipfs.io` and `dweb.link` answer 429 to most requests and `w3s.link` redirects, so use filebase or your own. - **Tax surface.** Each direction its own, read live from the hook, in basis points rounded up. `sellTaxRate()` and `taxBps()` (under the name many terminals already read) return the pool's total fee, which a sale always pays, and `taxRatePips()` the total's exact figure; `buyTaxRate()` returns what a buy pays now, the total, or inside the pool's anti-snipe window the higher rate falling to it. It is the pool's fee on the quote leg, not a token tax: transfers between wallets pay nothing. - **Also readable.** `creator()`, `hook()`, `custodian()` and `factory()` (each `address`), `poolId()` (`bytes32`), `decimals()` (18), `burn(uint256 amount)`; `launchBlock()` (`uint256`) is the launch's `block.number`, which on an Arbitrum chain is the parent chain's block, so take the chain's own from the `TokenLaunched` log. - **Gas.** A transfer between two wallets costs about 47k gas once both are settled, and up to about 170k the first time two wallets earning holder rewards settle mid-stream (the contracts' gas snapshot). Do not hard-code transfer gas. ## The pool and its lines · LaunchHook - **The pool.** A Uniswap v4 pool with the dynamic-fee flag (`fee = 0x800000`), tick spacing 200 and the launch hook. Only the factory can create a pool on the hook (`beforeInitialize` refuses anyone else), and only one it registered. - **The seed.** One position, owned by the hook: from the opening tick to the highest usable tick when the token is currency0, from the lowest usable tick to the opening tick when it is currency1. Either way it holds only the token. Its liquidity is rounded down so the pool never asks for more tokens than exist; the remainder is burned (`PoolSeeded(poolId, tickLower, tickUpper, liquidity, dustBurned)`). The hook never calls `modifyLiquidity` with a negative delta for its own position, and has no function that could. - **The seed's fees.** `harvestSeed(poolId)`, which anyone can call and `claim` runs first, collects them with a zero-delta poke: the quote side to the creator side's balance, the token side burned (`SeedFeesHarvested`). - **Outside liquidity.** `beforeAddLiquidity` refuses an add only before the pool is seeded, when its tier seals the pool, or when its tier names a liquidity policy and that policy refuses; no published tier does either. Removing has no hook callback at all, so outside liquidity can always come back out, and zero-delta pokes that collect fees work. - **The arithmetic.** With the seed as the only liquidity, the share of supply bought by a price multiple m is 1 − 1/√m, and the quote it takes is (√m − 1) × the opening market cap, before fees. - **Where the lines are.** Tick offsets from the opening, in the direction the token's price rises: graduation 28,200 ticks (1.0001^28,200 ≈ 16.77), the LP line 49,600 ticks (≈ 142.56). Placed when the pool is seeded, so a custom supply graduates at the same market cap. From a $3.5k opening, reaching them takes about $10.8k and $38.3k of buying, before fees. The dollar figures depend on each quote's opening; the multiples are fixed. - **Stages.** `Below → Crossed → Graduated → LpCrossed → LpActive`, in `hook.poolConfig(poolId).stage`. `afterSwap` marks a crossing (and the transaction that made it, in transient storage keyed by the pool id); the first `beforeSwap` of a later transaction confirms it, before that swap's fee is chosen. A sell back under an unconfirmed line uncrosses it. A pool confirmed past graduation with the price already past the LP line starts the hold there. - **The hold and the switch.** `lpLineHold` is immutable on each hook (600 seconds on the first; the address book reads every hook's, and `LaunchLens.poolView` a pool's as `lpLineHoldSeconds`), counted cumulatively (`lpHeldSeconds` plus the current stretch). On confirmation the hook calls `updateDynamicLPFee(key, lpFee)` once and emits `LpFeeActivated(poolId, lpFee)`; the pool's stored LP fee is zero before and the tier's LP fee after, never changing again. Graduation emits `Graduated(poolId)`. - **Reads.** `LaunchLens.graduationProgress(address hook, bytes32 poolId) view returns (uint256)` and `lpLineProgress(address hook, bytes32 poolId) view returns (uint256)` (basis points of the quote the seed must collect to reach the line), `lpLineHeld(address hook, bytes32 poolId) view returns (uint256 held, uint256 hold)` (seconds held, and the hold). ## Fees · LaunchHook, LaunchLens - **Units.** Pips of the quote leg: 1,000,000 is 100%. The tiers in pips (total / floor / protocol / LP): 10,000 / 10,000 / 4,000 / 1,000; 20,000 / 11,000 / 4,000 / 3,000; 30,000 / 13,000 / 4,000 / 5,000; 50,000 / 18,000 / 4,000 / 10,000; 100,000 / 18,000 / 4,000 / 10,000. - **The rule.** The factory refuses to publish, and the hook to register, any terms outside `protocol + lp ≤ floor ≤ total ≤ 100,000`, with the LP line at or past graduation (`FeeTerms.valid`): at the floor, with the LP fee on, the creator side's remainder is never negative, and may be zero. The hook's ceiling of 10% is a constant in its code. How a floor divides beyond that is the menu's choice, not a rule: today's tiers keep the creator side at least the protocol's share at the floor, and no LP fee above 1%. - **Hook fee and LP fee (experimental).** The hook takes `total − lpFee` on the quote leg, where the LP fee in force is zero until the LP line is confirmed; the pool charges the LP fee itself, on the swap's input. The two compound, so the all-in rate is `h + lp − h·lp / 1e6`, a hair under the advertised total once the LP fee is on: on an exact-input buy the hook's fee comes off the input and the pool charges its LP fee on the rest; on a sell the pool charges its LP fee on the token in and the hook takes its fee from the quote out. - **The LP fee is experimental.** Its rates, and the line it starts at, may change for launches to come; a pool keeps the LP fee and the line it launched with. - **The anti-snipe fee.** A row may carry one, `earlyBuyFeeStartPips` and `earlyBuyFeeWindow`. Both are zero, or the start is above the row's total and at most 990,000 (99%) and the window 1 to 1,800 seconds (`FeeTerms.validEarlyBuyFee`, which the factory applies at publication and the hook at registration; the hook's `MAX_EARLY_BUY_FEE_PIPS` and `MAX_EARLY_BUY_FEE_WINDOW`). The hook stamps the window from the second it registers the pool, t₀, and stores its end, `earlyBuyFeeUntil` = t₀ + W. A buy at second t < t₀ + W pays B = S − ⌊(S − T)·(t − t₀) / W⌋, with S the start and T the pool's total now, so the rate is rounded up, never below the line; equally, B = T + ⌈(S − T)·(t₀ + W − t) / W⌉. From t₀ + W a buy pays T. A clock behind t₀ is charged S. B never rises: a later second, or a total lowered inside the window, only lowers it, the line then running from S at t₀ to the new total at the window's end. - **Who it pays.** The hook takes `B − lpFee` on the quote leg, as it takes the total outside a window; the protocol's part stays its pips of the leg, and everything above goes to the creator side. A sale never pays it, and neither does the factory's own first buy in the launch transaction: the hook exempts the factory's swaps inside the transaction that registered the pool, and nothing else. It is netted into the swap's deltas, so routers and quoters charge it as they charge the rest of the fee. A quote taken in an earlier second overstates the fee, so a minimum output taken from it is looser than its percentage: [The anti-snipe fee](https://montramarkets.xyz/docs/anti-snipe#slippage-on-a-falling-rate). - **Reading it.** `LaunchHook.currentBuyFeeRate(bytes32 poolId) view returns (uint256)`, what any buy pays this second in pips; `LaunchHook.currentFeeRate(bytes32 poolId, address) view returns (uint256)`, which stays the total, its second argument unnamed and ignored, so any address gives the same answer (the token passes zero) and the call does not encode without one; `LaunchLens.poolView`'s `buyFee` and `buyEffectiveTotalFee`; `LaunchLens.buyFeeSchedule(address hook, bytes32 poolId) view returns (uint24 startPips, uint24 endPips, uint40 startsAt, uint40 endsAt, uint24 nowPips)`, all zero for a pool without one. `PoolRegistered` carries the start and the window, the event's block time is t₀, and `TotalFeeUpdated` moves T: nothing is emitted when a window ends. How an app or a router reads, tracks and shows it: [The anti-snipe fee](https://montramarkets.xyz/docs/anti-snipe). - **Levers.** `lowerTotalFee(bytes32 poolId, uint24 totalFee)` (the pool's recipient; strictly lower, at least the floor, not once locked) emits `TotalFeeUpdated`; `lockFee(bytes32 poolId)` emits `FeeLocked`; `lowerProtocolFee(bytes32 poolId, uint24 protocolFee)` (the hook's owner; strictly lower, the difference going to the creator side) emits `ProtocolFeeUpdated`. None can raise. - **Reads.** `LaunchLens.feeBreakdown(address hook, bytes32 poolId)` returns `(uint24 total, uint24 lp, uint24 protocol, uint24 creator, uint256 effectiveTotal)` for the next sale, and a buy outside an anti-snipe window, in pips; `LaunchToken.taxRatePips()` the total (`uint256`), `taxBps()` the same in basis points rounded up (`uint24`). Which to show is under [Build on Montra](https://montramarkets.xyz/docs/build). - **Uniswap's own fee.** Uniswap v4 can charge a protocol fee of its own on a pool if Uniswap switches one on; it would come on top of the pool's fee. `LaunchLens.poolView` reports it per direction (`coreProtocolFeeZeroForOne`, `coreProtocolFeeOneForZero`). ## Claims and collection · LaunchHook | Call | What it does | | --- | --- | | `during the swap` | The hook mints claims on the pool manager for its fee, in the quote, and books the creator and protocol parts as pending. `FeeAccrued(poolId, zeroForOne, amountSpecified, fee, protocolPart)` is emitted once per swap, even when the fee rounds to zero, so it pairs one-to-one with the pool manager's `Swap`: before it when the fee is taken in `beforeSwap` (the quote is the amount named), after it otherwise. `amountSpecified` is the swap's own, signed as v4 signs it: negative for exact input, positive for exact output, which is what chooses the fee's formula. | | `sweep(bytes32 poolId) returns (uint256 creatorAmount, uint256 protocolAmount)` | Anyone. Redeems the pool's claims: the creator part to `tab[poolId]`, the protocol part to `protocolTab[quote]`. `FeesSwept`. | | `harvestSeed(bytes32 poolId) returns (uint256 quoteToCreator, uint256 tokensBurned)` | Anyone. Collects the locked seed's LP fees: quote to the tab, token burned. `SeedFeesHarvested`. | | `claim(bytes32 poolId, address to) returns (uint256 amount)` | The pool's recipient only. Sweeps, harvests and pays the whole tab to `to`, never to address one. `CreatorFeesClaimed`. | | `claimTab(bytes32 poolId, address to, uint256 amount) returns (uint256)` | The recipient only. Pays from the tab alone, without sweeping first; zero means all of it. | | `collectProtocol(address currency) returns (uint256 amount)` | Anyone. Sends the protocol's balance in a currency to the vault. `ProtocolCollected`. | | `pendingCreator(bytes32 poolId) · pendingProtocol(bytes32 poolId)` | Each `returns (uint256)`: what a sweep would move now, of each part. | | `LaunchLens.creatorClaimable(address hook, bytes32 poolId) returns (uint256)` | What `claim` would pay right now: tab, pending and the seed's unharvested quote. | ## The creator's routes · LaunchFactory, FeeSplitter - **At launch.** `feeRecipients` and `feeShares`: none for `msg.sender`; one address with share 10,000 for that address; two to four with shares summing to 10,000 for a `FeeSplitter` clone (`FeeSplitterDeployed`). `HOLDERS = address(1)` in any slot names the token itself, as it does for the hook's `updateRecipient` and a splitter's `rotate`; nothing is ever paid to address one (`claim`, `claimTab`, a splitter's `collect` and `claimRewards(to)` refuse it). `feeLocked` is true from birth when the recipient is the token or a splitter. System contracts (the factory, the pool manager, the vault, the quote, another launch's token or splitter) are refused. - **After launch.** `LaunchHook.updateRecipient(bytes32 poolId, address newRecipient)`, the current recipient only, emits `RecipientUpdated`; naming the pool's own token sets `feeLocked`. `LaunchFactory.deploySplitter(uint256 moduleSetId, bytes32 poolId, bytes32 salt, address[] recipients, uint16[] shares) returns (address)`, the current recipient only, deploys a splitter for that pool, which the recipient then names with `updateRecipient`. The hook checks neither that a splitter was made for the pool nor that the fee was lowered first. - **FeeSplitter.** Ownerless. `distribute() returns (uint256 amount)` (anyone) pulls the pool's claim and divides what it holds; `collect(address to, uint256 amount) returns (uint256)` pays a recipient its share; `rotate(address to)` hands on the caller's own slot, refusing the hook, the factory, the vault, the quote and other launches' tokens and splitters; `recipients() view returns (address[])`, `shares() view returns (uint16[])`, `owed(address) view returns (uint256)`. - **Before a handover**, an interface should have the new address prove it controls the key (a signature, or ERC-1271 for a multisig): there is no accept step, and the hook cannot tell a typo. ## Holder rewards · LaunchToken - **`distribute() returns (uint256 received)`**, anyone: pulls what the pool owes the token (from the hook, or from a splitter that lists it) and starts a stream of `streamDuration` (3,600 seconds on every published tier), folding in what is still streaming. New money that, with anything already parked, comes to under 1% of what is still streaming is parked for a later distribution rather than delaying the current one. - **`claimRewards() returns (uint256)`** and `claimRewards(address to) returns (uint256 amount)` pay the caller everything earned, never to address one. `earned(address holder) view returns (uint256)` reads it. `isExcluded(address account) view returns (bool)` names who earns nothing: the PoolManager, the hook, the token, `0x…dEaD` and address one. - **`pushRewards(address[] holders) returns (uint256 pushed, uint256 bounty)`**, anyone: pays each listed wallet less the pusher's fee, which is `pushBountyBps` of the payout (25 basis points on every published tier) or `pushFloor` in quote units, whichever is more. The floor is zero in the mainnet configuration; a payout under `PUSH_GATE` (20) floors is left to accrue, so the fee never passes 5%. Only accounts with no code, or an EIP-7702 delegation, are pushed to; a holder that cannot take the payment is skipped (`RewardsPushSkipped`), never blocking a batch. **No code includes a contract not yet deployed**: a counterfactual Safe, a smart account or an escrow that holds the token before its contract exists is pushed to like a wallet, so the contract later deployed there must be able to take out and move what it was paid in the reward asset. - **The ledger.** One running index in Q128; a transfer settles only the two accounts it touches, so a distribution costs the same at ten holders or a hundred thousand. - **Reads.** `LaunchLens.rewardView(address token, address holder) view returns (RewardView v)`, `earnedMany(address token, address[] holders) view returns (uint256[] amounts)`; `streaming() view returns (uint256 remaining, uint256 perSecond, uint256 unplaced)` on the token. ## The protocol's share · ProtocolFeeVault, FeeMarket, fillers - **The vault.** `collectProtocol(currency)` on the hook, which anyone can call, sends the protocol's balance to the `ProtocolFeeVault`; `allocate(currency)`, also anyone's, divides it among the lanes by their shares: all of it the treasury's until the buyback is pointed at MONTRA, 70% the buyback's and 30% the treasury's from then on. The treasury is paid in HYPE (`deliver(uint256 lane) returns (uint256 amount)`, anyone). - **The fee market** (`FeeMarket`). One standing sale per currency, never per launch or per lot: whatever has accrued is the lot, and a take may be partial. A hub sale sells for HYPE what the converting lanes hold of a currency and may sell (`ProtocolFeeVault.sellable(currency)`); the buyback's sales sell what its lane holds for MONTRA. The price is the keeper's reference, a rate net of every fee on the sale's route that moves only as fast as time allows, times a level that falls from just above it to the floor: 1% under it for most currencies on mainnet, 3% for memes and stock tokens, 1.5% for the buyback. A hub sale sells nothing on a reference more than 5 minutes old. How to take one is under [Run a keeper](https://montramarkets.xyz/docs/keepers). - **Filling.** The keeper fills hub sales through `RouteFiller`, which trades through v4 pools with a minimum rate from the keeper's own quote; anyone may fill one, through it or from their own funds, and the market is paid its price either way. What the keeper's fills make above that price goes to the protocol. - **The buyback.** `BuybackFiller` is the only taker of the buyback's sales and serves the keeper's keys alone. It buys MONTRA in MONTRA's own pool, the last hop of every route, and the `TokenBurner` destroys what it buys in the same transaction, so MONTRA's supply falls (`Bought`, `Burned`). Each of its sales releases at most a daily cap, accruing by the second, that the team's multisig sets (`FeeMarket.setDrip`) and its signing tools hold to 5% of the real depth of MONTRA's pool, retuned as the pool deepens: a rule of the team's, not one the contracts enforce. What arrives above a cap waits in the vault and is released at the cap on later days. ## Structs and the stages Each struct a read returns or an event carries, field by field in the ABI's order, as viem decodes it into an object and ethers into a named tuple. The pool's stage, `PoolConfig.stage`, is a `uint8` numbered as below. | stage | LaunchHook.Stage | What it means | | --- | --- | --- | | 0 | `Below` | Short of graduation. | | 1 | `Crossed` | A swap left the price past the graduation line; the first swap of a later transaction confirms it, whichever way that swap trades. Only a sale in the same transaction returns the pool to `Below`. | | 2 | `Graduated` | Confirmed past graduation, for good. | | 3 | `LpCrossed` | Past the LP line, its hold counting (`lpHeldSeconds`, `lpCrossedAt`). A sell back under it returns the pool to `Graduated`, the time held kept. | | 4 | `LpActive` | The LP fee is on, for good. | **`PoolKey`**: `LaunchHook.poolKey(poolId)`, `PoolView.key`, Uniswap v4's own. | Field | Type | What it is | | --- | --- | --- | | `currency0` | `address` | The lower address; native HYPE is the zero address. | | `currency1` | `address` | The higher address. | | `fee` | `uint24` | `0x800000`, the dynamic-fee flag: the LP fee lives in the pool's slot0, never here. | | `tickSpacing` | `int24` | The row's tick spacing. | | `hooks` | `address` | The launch hook. | **`ExactIn`**: `LaunchRouter.swapExactIn`'s one argument. | Field | Type | What it is | | --- | --- | --- | | `key` | `PoolKey` | The pool's key, from `poolKey(poolId)`. | | `zeroForOne` | `bool` | True to sell `currency0` for `currency1`. A buy is `zeroForOne == quoteIsCurrency0`. | | `amountIn` | `uint128` | Exactly what the trade spends, in the input's base units. A native input is sent as the call's value, equal to it. | | `minAmountOut` | `uint128` | The least the trade may deliver, or it reverts `TooLittleReceived`. | | `recipient` | `address` | Who receives the output: never zero (`ZeroAddress`) or the router itself (`InvalidRecipient`). | | `deadline` | `uint256` | A Unix time in seconds, after which it reverts `Expired`. | **`PoolView`**: `LaunchLens.tokenView(token)` and `poolView(hook, poolId)`: 50 words, the key's 5 and the config's 23 inline. | Field | Type | What it is | | --- | --- | --- | | `key` | `PoolKey` | The pool's key. | | `config` | `PoolConfig` | The pool's terms and state, as `poolConfig(poolId)` returns them (below). | | `sqrtPriceX96` | `uint160` | The pool's price now, from slot0. | | `tick` | `int24` | The pool's tick now. | | `poolLpFee` | `uint24` | The LP fee slot0 holds, in pips: zero until the LP line's switching trade has run. | | `activeLiquidity` | `uint128` | The liquidity in range now, the seed's and anyone else's. | | `totalFee` | `uint24` | What the next sale pays, in pips, and a buy outside an anti-snipe window: `feeBreakdown`'s `total`. | | `lpFee` | `uint24` | The LP fee the next trade pays: `feeBreakdown`'s `lp`, already the tier's once the hold is done, while slot0 may still read zero. | | `protocolFee` | `uint24` | The protocol's absolute share, in pips. | | `creatorFee` | `uint24` | The creator side's rest: `totalFee − lpFee − protocolFee`, never negative. | | `effectiveTotalFee` | `uint256` | The all-in rate with the two fees compounded, `h + lp − h·lp / 1e6`, in pips: a hair under `totalFee` once the LP fee is on. | | `buyFee` | `uint24` | What a buy pays now, in pips (the hook's `currentBuyFeeRate`): `totalFee`, or inside the pool's anti-snipe window the higher rate falling to it. | | `buyEffectiveTotalFee` | `uint256` | What a buy pays all in: the hook's part of `buyFee` compounded with the LP fee in force, as `effectiveTotalFee` is, so equal to `buyFee` until that fee is on. A buy pays `1 / (1 − buyEffectiveTotalFee / 1e6)` times the pool's price, before its move. | | `coreProtocolFeeZeroForOne` | `uint24` | Uniswap's own protocol fee on a `zeroForOne` swap, in pips: zero unless Uniswap switches it on, then charged on top. | | `coreProtocolFeeOneForZero` | `uint24` | The same, the other way. | | `pendingCreator` | `uint256` | The creator side's fees a sweep would move, in the quote. | | `tab` | `uint256` | The creator side's swept balance, in the quote. | | `harvestableQuote` | `uint256` | The seed's LP fees in the quote not yet harvested (they go to the creator side). | | `harvestableToken` | `uint256` | The seed's LP fees in the token not yet harvested (they are burned). | | `pendingProtocol` | `uint256` | The protocol's fees a sweep would move, in the quote. | | `graduationProgressBps` | `uint256` | How far to graduation, in basis points of the quote the seed must collect: 10,000 once past it, `Crossed` included. | | `lpLineProgressBps` | `uint256` | The same toward the LP line. | | `lpLineHeldSeconds` | `uint256` | Seconds spent past the LP line so far, up to the hold. | | `lpLineHoldSeconds` | `uint256` | The hold: the LP fee starts at the next trade once the two are equal. | **`PoolConfig`**: `LaunchHook.poolConfig(poolId)` and `PoolView.config`: this generation's layout (a later hook's may differ; decode it with that hook's ABI). | Field | Type | What it is | | --- | --- | --- | | `totalFeePips` | `uint24` | The pool's total fee now, in pips: only ever lowered, never under `totalFeeMinPips`. | | `lpFeePips` | `uint24` | The pool's LP fee, fixed; charged from the LP line on. | | `protocolFeePips` | `uint24` | The protocol's absolute share, only ever lowered. | | `exists` | `bool` | The hook registered this pool. | | `quoteIsCurrency0` | `bool` | The quote asset is `currency0`. | | `liquiditySealed` | `bool` | Outside liquidity is refused (no tier of the menu seals). | | `seeded` | `bool` | The seed is placed. | | `feeLocked` | `bool` | The total can never be lowered again. | | `earlyBuyFeeUntil` | `uint40` | The first second a buy pays the total alone, a Unix time; zero with no anti-snipe fee. The window began `earlyBuyFeeWindow` seconds before it, at launch. | | `graduationTick` | `int24` | The graduation line's tick (the registered offset until the seed places it). | | `lpLineTick` | `int24` | The LP line's tick, likewise. | | `stage` | `uint8` | The pool's stage, `LaunchHook.Stage`: the numbers below. | | `lpCrossedAt` | `uint32` | When the current stretch past the LP line began, while `LpCrossed`. | | `lpHeldSeconds` | `uint16` | Time spent past the LP line in earlier stretches, up to the hold. | | `totalFeeMinPips` | `uint24` | The floor the total may be lowered to. | | `totalFeeMaxPips` | `uint24` | The total registered at launch, for readers. | | `liquidityPolicy` | `address` | A module asked about outside adds; zero for none (no tier names one). | | `seedTickLower` | `int24` | The seed's lower tick. | | `seedTickUpper` | `int24` | The seed's upper tick. | | `earlyBuyFeeStartPips` | `uint24` | What a buy paid in the launch second, in pips; zero for none. | | `earlyBuyFeeWindow` | `uint32` | The seconds the rate takes to fall to the total; zero for none. | | `seedLiquidity` | `uint128` | The seed's liquidity. | | `recipient` | `address` | Who the creator side pays: a wallet, a splitter, or the token itself for its holders. | **`Registration`**: `PoolRegistered`'s `registration`: the terms a launch stamps into its pool. | Field | Type | What it is | | --- | --- | --- | | `recipient` | `address` | Who the creator side pays from launch. | | `quote` | `address` | The quote asset; zero for native HYPE. | | `totalFeePips` | `uint24` | The total at launch, in pips. | | `totalFeeMinPips` | `uint24` | The floor. | | `protocolFeePips` | `uint24` | The protocol's share. | | `lpFeePips` | `uint24` | The LP fee from the LP line on. | | `feeLocked` | `bool` | Fixed from birth (the holders or a split). | | `earlyBuyFeeStartPips` | `uint24` | What a buy pays in the launch second, in pips; zero for none. | | `earlyBuyFeeWindow` | `uint32` | The seconds it falls over, from the block's time; zero for none. | | `liquidityPolicy` | `address` | The liquidity policy, or zero. | | `liquiditySealed` | `bool` | Outside liquidity refused. | | `graduationTicks` | `uint24` | Graduation's offset from the opening, in ticks; zero is graduated from birth. | | `lpLineTicks` | `uint24` | The LP line's offset; zero pays the LP fee from birth. | **`LaunchConfig`**: `ConfigPublished`'s `config` and `LaunchFactory.getConfig(id)`: a row a launch picks by its `configId`. | Field | Type | What it is | | --- | --- | --- | | `moduleSetId` | `uint256` | The module set (hook, token master, splitter master) it launches with. | | `quote` | `address` | The quote asset; zero for native HYPE. | | `baseSupply` | `uint256` | The supply its start tick is set for, in base units (1e9 tokens × 1e18). | | `tickSpacing` | `int24` | The pool's tick spacing. | | `startTick` | `int24` | The opening tick for the base supply, with the token as currency1; mirrored when the token sorts first. | | `graduationTicks` | `uint24` | Graduation's offset from the opening, in ticks. | | `lpLineTicks` | `uint24` | The LP line's offset. | | `totalFeePips` | `uint24` | The total, in pips. | | `totalFeeMinPips` | `uint24` | The floor. | | `protocolFeePips` | `uint24` | The protocol's share. | | `lpFeePips` | `uint24` | The LP fee. | | `earlyBuyFeeStartPips` | `uint24` | An anti-snipe fee's start, above the total and at most 99%; zero for none. | | `earlyBuyFeeWindow` | `uint32` | The seconds it falls over, at most 30 minutes; zero for none. | | `liquidityPolicy` | `address` | A liquidity policy, or zero. | | `liquiditySealed` | `bool` | Outside liquidity refused. | | `streamDuration` | `uint32` | Seconds a holders' stream runs. | | `pushBountyBps` | `uint16` | A pusher's share of each pushed payout, in basis points. | | `pushFloor` | `uint128` | A pusher's least fee, in the quote's base units. | | `enabled` | `bool` | Launches may use it. Always false in `ConfigPublished`: a row is published off and switched on by `ConfigEnabled`. | | `exists` | `bool` | Published. | **`LaunchParams`**: `LaunchFactory.launch`'s one argument, in this order. | Field | Type | What it is | | --- | --- | --- | | `name` | `string` | The token's name. Not empty. | | `symbol` | `string` | Its symbol. Not empty. | | `metadataURI` | `string` | The metadata JSON's URI, fixed at launch. | | `profile` | `Profile` | The image, description and links the token keeps on chain. | | `configId` | `uint256` | The row launched under. Ids count from 0: [Choose a row](https://montramarkets.xyz/docs/build#choose-a-row). | | `supply` | `uint256` | Whole tokens × 1e18; 0 for the row's own. | | `salt` | `bytes32` | Any value: the token's address comes from it and the sender. | | `feeRecipients` | `address[]` | Where the creator's stream goes; empty for the sender's own wallet. [The creator's routes](https://montramarkets.xyz/docs/reference#the-creator-s-routes-launchfactory-feesplitter). | | `feeShares` | `uint16[]` | Their shares of 10,000, in the same order; empty with them. | | `firstBuyAmount` | `uint256` | The quote spent on the first buy, its fee included; 0 for none. | | `firstBuyMinOut` | `uint256` | The fewest tokens the first buy may return, or the launch reverts `FirstBuySlippage`. | | `permit` | `Permit` | An EIP-2612 allowance for an ERC-20 first buy; all zero for none. | **`Profile`**: `LaunchParams.profile`, in this order. | Field | Type | What it is | | --- | --- | --- | | `logo` | `string` | The image's URI. | | `description` | `string` | The token's description. | | `socials` | `Socials` | Its links, below. | **`Permit`**: `LaunchParams.permit`, in this order: one transaction for an ERC-20 first buy. | Field | Type | What it is | | --- | --- | --- | | `deadline` | `uint256` | When the signature ends. 0: none supplied, and the allowance must already exist. | | `v` | `uint8` | The signature's `v`. | | `r` | `bytes32` | Its `r`. | | `s` | `bytes32` | Its `s`. | **`Socials`**: `LaunchParams.profile.socials`, `getTokenInfo()`'s `socials_` and `socials()`, in this order. | Field | Type | What it is | | --- | --- | --- | | `telegram` | `string` | A link, or empty. | | `twitter` | `string` | A link, or empty. | | `discord` | `string` | A link, or empty. | | `website` | `string` | A link, or empty. | | `extra` | `string` | Empty for a launch through Montra's site. | **`RewardView`**: `LaunchLens.rewardView(token, holder)`. | Field | Type | What it is | | --- | --- | --- | | `asset` | `address` | What holders are paid in: the pool's quote. | | `directlyRouted` | `bool` | The pool's creator side pays the token itself (a splitter slot naming it is not counted). | | `eligibleSupply` | `uint256` | The supply that earns: every balance but the excluded addresses'. | | `streamingRemaining` | `uint256` | What the current stream still has to pay out. | | `streamingPerSecond` | `uint256` | Its rate, a second. | | `periodFinish` | `uint64` | When it ends, a Unix time. | | `parked` | `uint256` | Arrived too small to restart the stream, waiting for the next. | | `totalReceived` | `uint256` | Every unit the token has received for its holders. | | `totalClaimed` | `uint256` | Every unit paid out, claimed and pushed. | | `claimableFromVenue` | `uint256` | What `distribute()` would pull from the hook now, where the creator side pays the token. | | `earned` | `uint256` | What `holder` could claim now. | --- # Run a keeper URL: https://montramarkets.xyz/docs/keepers Montra's fees reach the treasury, the buyback and holders through a few calls that anyone can make, and two of them pay: buying the protocol's fees for HYPE as they come up for sale, and pushing holder rewards to wallets. Montra runs a keeper; outside keepers add redundancy for taking sales, pushing rewards and the upkeep, though every sale still depends on Montra's reporter keys to post its price. - **Hub sales:** Open to anyone. Buy the protocol's fees in a currency for HYPE, for about the floor's discount under the reference at best: 1% for most currencies. - **Reward pushes:** 0.25%. Of each payout pushed to a holder's wallet. - **Upkeep:** Unpaid. Sweeping, collecting, allocating, distributing: open to anyone, paid nothing, and what keeps the fees flowing. - **Not open:** 2 jobs. Posting the sales' references, and filling the MONTRA buyback. ## The jobs, and what each pays | Job | Who may | Pays | Call | | --- | --- | --- | --- | | Take a hub sale | Anyone | What your route returns over the sale's price | `FeeMarket.take` or `RouteFiller.fill` | | Push holder rewards | Anyone | 0.25% of each payout | `LaunchToken.pushRewards(address[] holders)` | | Sweep a pool's fees | Anyone | Nothing | `LaunchHook.sweep(bytes32 poolId)` | | Collect the protocol's share | Anyone | Nothing | `LaunchHook.collectProtocol(address currency)` | | Divide it among the lanes | Anyone | Nothing | `ProtocolFeeVault.allocate(address currency)` | | Deliver the treasury's HYPE | Anyone | Nothing | `ProtocolFeeVault.deliver(uint256 lane)` | | Start a holders' stream | Anyone | Nothing | `LaunchToken.distribute()` | | Divide a split | Anyone | Nothing; each recipient collects its own | `FeeSplitter.distribute()` | | Harvest a seed's fees | Anyone | Nothing | `LaunchHook.harvestSeed(bytes32 poolId)` | The unpaid calls are how fees move along: a trade's fee waits in its pool's hook until someone sweeps it, the protocol's part waits in the hook until someone collects it to the vault, and it is for sale only once someone allocates it to the lanes. A keeper taking hub sales does these first, in the same transaction, because each grows the lot it buys. ## Not open to keepers - **Posting a sale's reference.** `FeeMarket.postAnchor` and `postAnchors` are for reporters the team's multisig approves (`NotReporter` for anyone else). A sale whose reference is more than its `maxAge` old sells nothing, so while no reporter posts, there is nothing to take. - **The MONTRA buyback.** Its sales name `BuybackFiller` as their only taker (`NotTaker` for anyone else), and the filler serves reporters only; it buys MONTRA in MONTRA's own pool and burns everything it buys. Once the buyback is on, 70% of the protocol's share ends in these sales, and none of them is a third party's to fill; the hub sales stay open to anyone. - **Pausing, retuning or resetting a sale.** The multisig's alone; a guardian key can only stop things, by pausing a sale or removing a reporter, and removing a reporter that has posted stops every sale until another posts. Which sales are open is itself a setting. The multisig can name a sale's only taker (`setTaker`, as the buyback's do; `NotTaker` for anyone else) or cap what it releases a day (`setDrip`; listing a quote may set one for its hub sale). Read `takerOf(id)` and `drip(id)` before you count on a sale. ## How a hub sale prices There is one standing sale per quote currency (`FeeMarket.hubMarket(currency)`), selling what the protocol has earned in it for HYPE. Its price per unit is the reporter's reference (the anchor: what the sale's route returns, net of every pool's and hook's fee on it) times a level. The level starts 3% above the reference, halves every 30 minutes from where the last take left it, and never falls under the floor. - **Starts:** +3%. Over the reference, when a sale opens or after a spell it could not sell; a take lifts it from wherever it is. - **Reaches the reference:** ≈77 s. Halving every 30 minutes from the top. - **Reaches the floor:** ≈103 s. 1% under the reference for most currencies; ≈156 s to the 3% floor of memes and stock tokens. - **Sells on a reference:** ≤ 5 min old. Older, nothing sells until a reporter posts again. - **A take lifts the price.** Taking a share of the lot multiplies the level by one plus the bump times that share (the whole lot: 3% up from wherever it was), never past twice the reference, and it falls again from there. A take that lands ahead of yours makes your `maxPrice` too low: `TooExpensive`, and you read again. - **The lot grows as fees arrive.** Whatever the lanes hold in the currency is for sale (`ProtocolFeeVault.sellable`), once it has been swept, collected and allocated. A take may be partial. - **The reference moves slowly.** A post may lower it by at most half every 6 hours on mainnet's terms (the team's multisig may set a sale to fall as fast as half every 20 minutes, never faster) and raise it at most fourfold over an hour; a reading a little under is followed as far as it may fall, and one above its reach, or far under it, halts the sale (`MarketHalted`) until one in reach comes. After any spell it could not sell (stale, halted, paused), the level starts again from the top. - **Prices are scaled by 1e36** (`PRICE_ONE`): HYPE wei per base unit of the currency sold. `FeeMarket.price(id)` returns the price now, the floor price and whether the reference is fresh. **Where the edge comes from.** You buy at the sale's price and sell through your own route: your margin per unit is what your route returns less the price. Because the reference is what the sale's route returns, the margin is about the floor's discount once the level has fallen that far; references are posted about once a minute, so in a rising market it can be more, by up to about the bump. **Busy and quiet currencies.** On a busy currency, Montra's own keeper fills a sale as soon as its route returns about 0.2% over the price (today; this may change) and sends that surplus to the protocol, so a third party wins there by having a better route or venue, or by being faster. On a quiet one, the keeper's round sweeps, collects and allocates every currency about every 30 seconds and its fill step runs about every 5 (today; this may change): fees swept into an idle sale already at its floor can be taken by a keeper that watches every block, for up to about the floor's discount (1%, or 3% for memes and stock tokens). That is the incentive for outside keepers, by design. A sale sits at its floor with no taker only when its reference stands above what the pools pay. A currency with no route the keeper can read gets no reference at all: its sale goes stale, and nothing sells until a reporter posts again. ## Taking a hub sale 1. **Read the sale.** `id = hubMarket(currency)`; `price(id)` for the price, the floor and freshness; `available(id)` for the lot; `market(id)` for `paused`, `halted`, `anchorAt` and `maxAge`. Skip one that is not fresh, paused or halted. 2. **Grow the lot.** `LaunchHook.pendingProtocol(poolId)` says what a sweep would move in each pool; `sweep` the pools worth it, `collectProtocol(currency)` on each hook, then `ProtocolFeeVault.allocate(currency)`. Each is open to anyone; batch them with a `RouteFiller.fill` (Multicall3, at its canonical address `0xcA11bde05977b3631167028862bE2a173976CA11`), which sends no value. 3. **Price your route.** Quote selling the lot along your route back to HYPE (Uniswap's V4Quoter, or an `eth_call` of the fill itself). What the sale costs is `owe = ceil(amount × price / 1e36)`. 4. **Take it.** Through `RouteFiller.fill(id, maxAmount, maxPrice, route, minRate, surplusTo, deadline)`: it takes the lot, sells it along `route` (v4 pools on Uniswap's PoolManager, the one Montra's pools live in, the first holding the currency, the last paying HYPE), pays the sale and sends what is left over to `surplusTo`, you, in one transaction with no capital of your own. Or with inventory: `FeeMarket.take(id, maxAmount, maxPrice, recipient, deadline, "")` paying HYPE as the call's value, the currency sent to `recipient` to sell wherever you like. Call `take` directly, not through Multicall3: it returns any value unused to its caller, which Multicall3 cannot take, so a batched take fails unless it pays the exact price. 5. **Set the guards.** `maxPrice` at the price you read, so a take ahead of yours makes yours fail rather than pay more; `minRate` from your own quote less your slippage (scaled by 1e36), so a sandwich of your route makes it fail rather than lose; a short `deadline`. Simulate before you send: a fill reverts on a sale that is stale (`StaleAnchor`), halted, paused, empty (`NothingToSell`), dearer than your `maxPrice` (`TooExpensive`) or on a route that returns less than your minimum (`ShortRoute`) or that a pool fills only partly (`PartialFill`), and a reverted fill still costs its gas. ## Pushing holder rewards A token that pays its holders streams what arrives over an hour; each holder can claim, and anyone can push what a holder has earned to its wallet for a fee. `pushRewards(holders)` pays each listed wallet its earnings less the pusher's fee, the larger of `pushBountyBps` of the payout (25, so 0.25%) and `pushFloor` (zero in the mainnet configuration). A payout under `PUSH_GATE` (20) floors is left to accrue, so with a zero floor only an empty one is. - **Wallets only.** An address with code (other than an EIP-7702 delegation) is passed over; a wallet that cannot take the payment is skipped (`RewardsPushSkipped`) and keeps its balance. Nothing in a batch blocks the rest. An address with no code is pushed to as a wallet, a contract not yet deployed included, such as a counterfactual Safe or an escrow that held the token first; once a contract is deployed there it is passed over and claims for itself. - **The fee is paid to the caller** in the token's quote asset, once per batch (`RewardsPushed` for each holder); if the caller cannot take it, it is credited to claim later (`RewardsCredited`). - **Find the holders** from the token's `Transfer` events, read what each has earned with `earned(holder)` or `LaunchLens.earnedMany(token, holders)`, and call `distribute()` first when new fees are owed to the token. - **Gas** has two parts, so a small batch costs far more a wallet. Over 451 real batches it was about 78k a batch, plus about 30k for each wallet pushed before: about 108k for one wallet, 199k for four and 745k for 22, which is 33.9k a wallet. A wallet's first push adds about 20k as a rule and up to about 85k: one new wallet alone is about 128k, and a batch of four with two new wallets took 222,871. The contracts' gas snapshot, 86k a wallet the first time and 33k once settled, is for ten wallets a batch. - **Push a batch when it pays.** Set 0.25% of the whole batch's payouts against the gas of the whole batch, the batch's own cost included. A wallet pushed alone pays only if 0.25% of its payout covers about 108k, so gather small payouts into a batch or leave them to accrue. ## What can I make? Honest arithmetic, not a promise: every figure below scales with how much trades on Montra, and competition, Montra's own keeper first, drives margins down. Gas is assumed at 0.1 gwei and HYPE at $40: a hub fill through one pool (about 156k gas) then costs about $0.00062, a sweep in a batch (about 47k) less, so on Elysium gas rarely decides a fill; check the chain's price when you run. **Hub sales.** The protocol takes 0.4% of every trade. Fees in HYPE need no sale; fees in every other currency go through its hub sale, before MONTRA and after, and `ProtocolFeeVault.sellable(currency)` says how much of a currency is for sale. A taker's margin is about the floor's discount on what it buys (1%; 3% for memes and stock tokens) on fees swept into an idle sale, less its route's slippage beyond the reference and its gas; on a busy currency, where Montra's keeper competes, nearer 0.2%. Per day, across every taker together, with all of it at one rate or the other: | Traded a day in non-HYPE pairs | Protocol fees sold through hubs | Quiet currencies, about the 1% floor | Busy currencies, Montra's keeper at ≈0.2% (today; may change) | | --- | --- | --- | --- | | $100,000 | $400 | $4.00 | $0.80 | | $1,000,000 | $4,000 | $40 | $8.00 | | $10,000,000 | $40,000 | $400 | $80 | **Reward pushes.** The bounty is 0.25% of what is pushed. What holders are paid depends on trading in tokens that pay their holders: $1M a day traded in such tokens on the 1% tier, below the LP line, pays their holders 0.6% of it, $6,000 a day, and pushing all of it would earn $15. Holders also claim for themselves, and Montra's keeper pushes where the fee covers its gas, so this is a ceiling: | Rewards pushed a day | At 0.25% | Least payout worth a push alone | Least payout in a batch of 22 | | --- | --- | --- | --- | | $1,000 | $2.50 | $0.17 | $0.05 | | $10,000 | $25 | $0.17 | $0.05 | | $100,000 | $250 | $0.17 | $0.05 | ## Risks - **Competition.** Montra's keeper and every other keeper read the same sales; the first valid transaction wins a lot, and a take ahead of yours lifts the price and fails your fill. A failed transaction still costs its gas. - **Ordering.** Elysium has a single sequencer, and how it orders transactions is the sequencer's to set and may change. Nothing here promises you the same treatment as Montra's keeper, or that pending transactions stay unseen: assume a competitor sees what you see, and keep your guards (`maxPrice`, `minRate`, `deadline`) tight. - **Stale or stopped sales.** With no fresh reference, or a sale paused or halted, nothing sells. Your keeper cannot fix that: only reporters post. - **Your route.** Quote at the size you take; a thin route returns less than its probe price. A sandwich of your route is stopped by your `minRate`, at the cost of the fill. - **Holding inventory.** Taking with `take` leaves you holding the currency until you sell it: a stock token's market can close, a meme can fall, and an issuer can pause or take back its token. ## The keeper spec Everything above in one block, to hand to a developer or paste into an AI. Montra keeper spec: ```text MONTRA KEEPER SPEC Chain: Elysium, gas in HYPE. Every address comes from the LaunchpadRegistry (/docs/contracts): get("feeMarket"), get("routeFiller"), get("protocolVault"), get("factory"), get("lens"), get("quoteRegistry"), hooks(). Fee-market prices are scaled by PRICE_ONE = 1e36: HYPE wei per base unit of the currency sold. PAID JOBS 1. Hub sales (FeeMarket, taker open to anyone) hubMarket(address sell) pure returns (bytes32) the sale's id price(bytes32 id) view returns (uint256 current, uint256 floor, bool fresh) current = anchor * level available(bytes32 id) view returns (uint256) the lot market(bytes32 id) view returns (Market) Market fields: anchor, anchorAt, maxAge, level, startedAt, halfLife, bumpBps, floorBps, paused, halted, ... take(bytes32 id, uint256 maxAmount, uint256 maxPrice, address recipient, uint256 deadline, bytes data) payable returns (uint256 amount, uint256 owe) RouteFiller.fill(bytes32 id, uint256 maxAmount, uint256 maxPrice, PoolKey[] route, uint256 minRate, address surplusTo, uint256 deadline) returns (uint256 amount, uint256 owe) terms on mainnet: level starts at 1 + 0.03, halves every 1800 s, floor 1 - 0.01 (1 - 0.03 for memes and stock tokens), a take sets level = level * (1 + 0.03 * amount / lot) (max level 2), nothing sells on an anchor older than 300 s, the anchor falls at most by half every 21600 s (fallHalfLife; never under 1200 s on any sale) takerOf(bytes32 id) view returns (address): the sale's only taker (zero: anyone) drip(bytes32 id) view returns (uint256 perDay, uint256 left): a daily cap (perDay zero: none) events: MarketOpened, Take, AnchorPosted, AnchorReset, PausedSet, ParamsSet, TakerSet, DripSet, ReporterSet (FeeMarket); Filled (RouteFiller); HubSold, Allocated (vault) errors: StaleAnchor, MarketHalted, MarketPaused, NothingToSell, TooExpensive, ZeroPrice, NotTaker, Expired (FeeMarket); ShortRoute, PartialFill (RouteFiller) 2. Holder-reward pushes (each LaunchToken; launches from the factory's TokenLaunched) earned(address holder) view returns (uint256) distribute() returns (uint256 received) pushRewards(address[] holders) returns (uint256 pushed, uint256 bounty) fee per holder = max(payout * pushBountyBps / 10000, pushFloor); pushBountyBps = 25, pushFloor = 0 on mainnet payout < PUSH_GATE (20) * pushFloor is skipped; only accounts with no code or an EIP-7702 delegation are pushed events: Transfer, RewardsDistributed, RewardsPushed, RewardsPushSkipped, RewardsCredited UNPAID UPKEEP (anyone) LaunchHook: pendingProtocol(bytes32 poolId), sweep(bytes32 poolId), collectProtocol(address currency), harvestSeed(bytes32 poolId) ProtocolFeeVault: sellable(address currency), unallocated(address currency), allocate(address currency), deliver(uint256 lane) LaunchToken.distribute(), FeeSplitter.distribute() NOT OPEN FeeMarket.postAnchor/postAnchors (approved reporters only; every sale depends on them) a sale whose takerOf(id) is not you (the MONTRA buyback's, or any the multisig names) BuybackFiller.fill (reporters only; the MONTRA buyback's sales name it their only taker; everything bought is burned) LOOP (every block: on a quiet currency the edge goes to whoever sees new fees first) for each listed quote currency c other than HYPE: id = hubMarket(c); skip unless price(id).fresh, not paused or halted, and takerOf(id) is zero or you lot = available(id) + what sweeping, collecting and allocating c would add, at most drip(id).left where drip(id).perDay is not zero out = your route's quote for selling lot of c into HYPE owe = ceil(lot * price / 1e36) if out - owe > gas cost * your margin: one transaction (Multicall3 at 0xcA11bde05977b3631167028862bE2a173976CA11, no value): sweep(pool...) on pools with pendingProtocol, collectProtocol(c) on each hook, allocate(c), RouteFiller.fill(id, lot, price, route, minRate = 1e36 * out * (1 - slippage) / lot, you, now + 60) (with inventory instead: FeeMarket.take called directly, value = owe; never through Multicall3, which cannot take a refund) on TooExpensive (a take landed first): read again, retry for each token paying its holders: if fees are owed to it: distribute() batch = wallets by earned, largest first, taken while the whole batch still pays: the batch's earned * 25 / 10000 > (78,000 + 30,300 * wallets) gas * gas price * your margin (a wallet never pushed before adds about 20,000 gas, up to about 85,000) if batch is not empty: pushRewards(batch) ``` --- # Events to index URL: https://montramarkets.xyz/docs/events Everything that happens on Montra is announced on chain as an event, so an indexer can rebuild every token, trade and fee from the logs alone. Montra's own API is built that way. - **Every token:** `TokenLaunched`. From the factory: the token, its pool, its terms and the first buy. - **Every trade:** `Swap`. From the PoolManager, filtered by pool id. - **Each trade's fee:** `FeeAccrued`. From the hook: the fee and the protocol's part, in the quote. - **A new hook:** `ModuleSetPublished`. From the factory, once it has checked the hook. The registry's HookAdded mirrors it. ## Where to start Start from the factory's `TokenLaunched`: it describes a launch in full, so describing a token never needs the factory's state. Trades are the pool manager's own `Swap` events. The hook's `FeeAccrued` comes once per swap, even when the fee rounds to zero, so it pairs with `Swap` one-to-one. Read from the factory proxy's deployment block: nothing of the launchpad's comes before it. Read the whole of each launch's transaction, not from `TokenLaunched` on: the creator's first buy, its `FeeAccrued` and `Swap` among them, is logged before it. Every ABI to decode them with is at `/docs/abis/events.json` ([Build on Montra](https://montramarkets.xyz/docs/build)). The owned contracts also emit OpenZeppelin's ownership events, the factory its proxy's `Upgraded`, and every token the ERC-20 `Transfer` and `Approval`, and OpenZeppelin's `Initialized` once, as the launch sets it up. ## What a trader paid: Swap and FeeAccrued A v4 `Swap` shows the pool's side of a trade, and the hook's fee is outside it. Its amounts are signed as the swapper sees them, negative paid into the pool and positive paid out. On a buy, the pool sees what is left of the input once the hook has taken its fee; on a sell, the pool pays out the whole and the hook takes its fee from that. So a trader's own amounts are the `Swap`'s quote amount plus `FeeAccrued.fee` on a buy and less it on a sell; the token amounts are exact either way. Montra's router reports a trader's amounts directly (`Swapped`), but a trade through any other router has only the two. | On the 2% tier, below the LP line, native quote as currency0 | A buy of 0.25 HYPE | A sell of tokens | | --- | --- | --- | | `Swap.amount0` (the quote) | −245000000000000000: what reached the pool | +130124515828329726: what the pool paid out | | `FeeAccrued.fee` | 5000000000000000 = ⌊250000000000000000 × 20,000 / 1e6⌋ | 2602490316566594 = ⌊130124515828329726 × 20,000 / 1e6⌋ | | The trader's quote | Paid 245000000000000000 + 5000000000000000 = 250000000000000000 | Received 130124515828329726 − 2602490316566594 = 127522025511763132 | | `Swapped` (Montra's router only) | `amountIn` 250000000000000000 | `amountOut` 127522025511763132 | `FeeAccrued` comes before its `Swap` when the quote is the amount named (an exact-input buy, an exact-output sell) and after it otherwise: pair them by transaction, pool and `zeroForOne`, in order. Its `amountSpecified` is signed as v4 signs it, negative for exact input and positive for exact output. `Swap.fee` is v4's own swap fee, the LP fee (zero until the LP line) and Uniswap's protocol fee should that ever be switched on: never show it as the pool's fee. A buy inside its pool's anti-snipe window pays a higher rate, and the same sum holds: its `FeeAccrued.fee` is that rate's fee, so the trader paid the `Swap`'s quote amount plus it. `protocolPart` stays the protocol's own pips of the leg; the rest, the premium included, is the creator side's. The `Swap` itself never shows the premium, so a chart built from `Swap` events draws the pool's price, not what buyers paid. The window starts at the block time of the launch's `PoolRegistered`, which carries its start and length ([The anti-snipe fee](https://montramarkets.xyz/docs/anti-snipe#events-and-indexing)). ## Every event ### Factory | Event | Emitted when | | --- | --- | | `TokenLaunched(address indexed token, address indexed creator, bytes32 indexed poolId, uint256 configId, address quote, bool quoteIsCurrency0, uint256 supply, int24 startTick, address feeRecipient, bool feeLocked, uint256 firstBuyIn, uint256 firstBuyOut)` | A token launches. Everything an indexer needs to describe it. `firstBuyIn` is the whole first buy, the swap's fee included, and `firstBuyOut` the tokens it bought after that fee; both are 0 with no first buy. The launch fee, native and apart, is in neither. | | `FeeSplitterDeployed(bytes32 indexed poolId, address indexed splitter, address[] recipients, uint16[] shares)` | A split is created, at launch or after. | | `ConfigPublished(uint256 indexed id, LaunchConfig config) · ConfigEnabled(uint256 indexed id, bool enabled)` | A launch tier is published, or switched on or off. A tier is published off, so `enabled` in `ConfigPublished` is always false: follow it with `ConfigEnabled`, or read `getConfig(id)`. | | `ModuleSetPublished(uint256 indexed id, address indexed hook, address indexed tokenMaster, address splitterMaster)` | A hook and the token and splitter masters it launches with are published. | | `LaunchEnabled(bool enabled) · LaunchFeeUpdated(uint256 oldFee, uint256 newFee) · LaunchFeeRecipientUpdated(address indexed oldRecipient, address indexed newRecipient) · QuoteRegistryUpdated(address indexed oldRegistry, address indexed newRegistry)` | Launches are switched on or off, or the launch fee, where it is paid, or the quote registry changes. | | `LaunchFeesCollected(address indexed recipient, uint256 amount)` | Launch fees are collected. | ### Launch hook | Event | Emitted when | | --- | --- | | `PoolRegistered(bytes32 indexed poolId, address indexed recipient, Registration registration) · PoolSeeded(bytes32 indexed poolId, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 dustBurned)` | A pool's terms are recorded, and its seed placed, during its launch. | | `FeeAccrued(bytes32 indexed poolId, bool zeroForOne, int256 amountSpecified, uint256 fee, uint256 protocolPart)` | Every swap, with the hook's fee in the quote and the protocol's part of it. | | `FeesSwept(bytes32 indexed poolId, address indexed caller, uint256 creatorAmount, uint256 protocolAmount) · SeedFeesHarvested(bytes32 indexed poolId, address indexed caller, uint256 quoteToCreator, uint256 tokensBurned)` | Collected fees are booked, and the seed's LP fees harvested. | | `CreatorFeesClaimed(bytes32 indexed poolId, address indexed to, uint256 amount) · ProtocolCollected(address indexed currency, address indexed vault, uint256 amount)` | The creator side is paid out, and the protocol's share sent to the vault. | | `RecipientUpdated(bytes32 indexed poolId, address indexed oldRecipient, address indexed newRecipient)` | A pool's fees are handed to a new recipient. | | `TotalFeeUpdated(bytes32 indexed poolId, uint24 oldFee, uint24 newFee) · FeeLocked(bytes32 indexed poolId) · ProtocolFeeUpdated(bytes32 indexed poolId, uint24 oldFee, uint24 newFee)` | A pool's fee is lowered or locked. | | `Graduated(bytes32 indexed poolId) · LpFeeActivated(bytes32 indexed poolId, uint24 lpFee)` | A pool graduates, and its LP fee switches on. | | `ProtocolVaultUpdated(address indexed oldVault, address indexed newVault)` | The vault the protocol's share is sent to changes. | ### Launch token | Event | Emitted when | | --- | --- | | `RewardsDistributed(uint256 received, uint256 streaming, uint64 periodFinish)` | Fees arrive for the holders and a new stream starts. | | `ReceiptParked(uint256 received, uint256 parked) · ReleaseParked(uint256 amount)` | A payment too small to restart the stream is parked, and later released into one. | | `RewardsClaimed(address indexed holder, uint256 amount) · RewardsPushed(address indexed holder, address indexed by, uint256 amount, uint256 bounty)` | A holder claims, or is paid by a push. `RewardsPushed`'s `amount` is what reached the holder, net of `bounty`, the pusher's fee. | | `RewardsPushSkipped(address indexed holder, uint256 amount) · RewardsCredited(address indexed pusher, uint256 fee)` | A push could not pay a holder, or its pusher, who is credited the fee instead. | ### Fee splitter | Event | Emitted when | | --- | --- | | `SplitterInitialized(address indexed feeSource, bytes32 indexed poolId, address quote, address[] recipients, uint16[] shares)` | A splitter is set up for its pool. | | `Distributed(uint256 amount) · ClaimFailed(bytes reason)` | It pulls and divides the pool's fees, or could not pull them. | | `Collected(address indexed recipient, address indexed to, uint256 amount) · Rotated(uint256 indexed slot, address indexed oldRecipient, address indexed newRecipient)` | A recipient collects its share, or hands on its slot. | ### Protocol fee vault | Event | Emitted when | | --- | --- | | `Allocated(address indexed currency, uint256 amount) · Delivered(uint256 indexed lane, address indexed currency, uint256 amount)` | Revenue in a currency is divided among the lanes, or a lane's own asset sent to its destination. | | `HubSold(address indexed currency, uint256 amount, uint256 received, address indexed recipient, uint256[] sold, uint256[] credited)` | A hub sale: a currency sold for HYPE, each lane debited and credited in proportion. | | `Withdrawn(uint256 indexed lane, address indexed currency, address indexed to, uint256 amount) · Released(uint256 indexed lane, address indexed currency, uint256 amount, uint256 delivered)` | A withdrawing lane's destination is paid, or a converting lane's revenue goes to the converter and its own asset reaches the lane's destination. | | `LaneAdded(uint256 indexed lane, address indexed to, address want, bool converts) · LaneUpdated(uint256 indexed lane, address indexed to, address want, bool converts)` | A lane is added or repointed: where it pays, in what asset, and whether it converts. | | `SharesUpdated(uint16[] shares) · FloorRaised(uint256 indexed lane, uint16 floorBps) · DirectUpdated(uint256 indexed lane, address currency)` | The lanes' shares are retuned, a lane's least share is raised for good, or a lane names the one currency it sells straight for its own asset. | | `ConverterUpdated(address indexed oldConverter, address indexed newConverter)` | The vault names what converts its lanes' revenue; naming none stops every sale. | ### Fee market | Event | Emitted when | | --- | --- | | `MarketOpened(bytes32 indexed market, uint8 kind, uint256 lane, address sell, address want, uint256 anchor)` | A sale opens: a hub sale of a currency for HYPE, or a lane's sale for its own asset. | | `Take(bytes32 indexed market, address indexed taker, address recipient, uint256 amount, uint256 price, uint256 owe, uint256 left, uint256 level)` | A take from a sale: what was bought, at what price, what is left and where the price falls from next. | | `AnchorPosted(bytes32 indexed market, uint256 reading, uint256 anchor, bool halted, bool restarted) · AnchorReset(bytes32 indexed market, uint256 anchor)` | A sale's reference moves, or is reset. | | `PausedSet(bytes32 indexed market, bool paused) · ParamsSet(bytes32 indexed market, Params params) · DripSet(bytes32 indexed market, uint256 perDay) · TakerSet(bytes32 indexed market, address taker)` | A sale is paused or resumed, retuned, given a daily cap, or given an only taker. | | `ReporterSet(address indexed reporter, bool approved, uint256 epoch) · GuardianSet(address indexed guardian)` | A reporter is approved or removed (removing one that has posted starts a new epoch, and every reference posted before goes stale), or the guardian is named. | ### Fillers and the burner | Event | Emitted when | | --- | --- | | `Filled(bytes32 indexed market, uint256 amount, uint256 owe, uint256 out, address surplusTo)` | A sale filled through the route filler: what it took, what the sale was paid, what the route returned and where the surplus went. | | `Bought(bytes32 indexed market, uint256 amount, uint256 owe, uint256 out, address to) · Burned(uint256 amount, uint256 rewards)` | The buyback filler buys MONTRA, and the burner destroys it, paying what it earned as a holder to the vault. | ### Router, registries | Event | Emitted when | | --- | --- | | `Swapped(bytes32 indexed poolId, address indexed sender, address indexed recipient, bool zeroForOne, uint256 amountIn, uint256 amountOut)` | A trade through Montra's router, with what was actually spent. | | `AddressSet(bytes32 indexed key, address indexed previous, address indexed current) · HookAdded(address indexed hook)` | The registry names an address under a key, or lists a new hook. | | `QuoteRegistered(address indexed asset, uint8 decimals, int24 defaultTickSpacing, string symbol) · QuoteEnabled(address indexed asset, bool enabled)` | An asset is listed as a quote, or switched on or off for new launches. | --- # Public API URL: https://montramarkets.xyz/docs/api Montra's indexer reads the chain and serves what it finds as a public JSON API: tokens, prices, charts, trades, holders and wallets. It is read-only, needs no key, and is the same API this site runs on. - **Base URL:** `https://api.montramarkets.xyz` - **Auth:** None. Open to any origin for reads. - **Live trades:** SSE. Server-sent events, up to 12 streams open at once per client. - **Format:** JSON. Pages of tokens; trades and events paged with before. ## How answers are written Every route that reads is a `GET`; the two that store a launch's image and document are `POST`, under [Launch a token](https://montramarkets.xyz/docs/build#launch-a-token). Amounts are integer strings in the asset's smallest unit; prices are decimal strings in the quote asset per whole token, to 36 decimal places, and market caps decimal strings in the quote asset, never with an exponent; times are milliseconds since 1970, except the `day` of `/api/stats/daily`, a date written `YYYY-MM-DD`; addresses are lowercase, and an address not set is `null`. Requests are rate limited per client: a `429` means slow down. A bad parameter answers `400` and names the field. The API never signs anything and holds no key; anything that moves value is a transaction from your own wallet. Example: the five newest tokens (one shown, trimmed): ```sh curl -s "https://api.montramarkets.xyz/api/tokens?sort=new&limit=5" { "tokens": [{ "address": "0x…", "name": "…", "symbol": "…", "poolId": "0x…", "quote": { "address": "0x0000…0000", "symbol": "HYPE", "decimals": 18, "native": true, "usd": 40.5 }, "supply": "1000000000000000000000000000", "price": "0.000000003500000000000000000000000000", "marketCap": "3.5", "volume24h": "…", "totalFeePips": 20000, "recipientKind": "holders", "graduation": { "stage": "new", "progressBps": 1640, "graduatedAt": null }, "lpLine": { "progressBps": 410, "active": false, "activeAt": null } }], "total": 41, "page": 1, "pages": 9 } ``` ## Token cards `/api/tokens`, a wallet's token lists and the top of `/api/tokens/{token}` answer with the same card. Its amounts are in the token's smallest unit (18 decimals) or the quote's, as the table says. | Field | Type | What it is | | --- | --- | --- | | `address`, `name`, `symbol` | string | The token, as launched. | | `imageUrl` | string or null | A URL a browser can load, null for no image. | | `creator`, `poolId` | string | The wallet that launched it, and its pool. | | `quote` | object | `address`, `symbol`, `decimals` and `native`, with `usd` (dollars per whole unit, null where nothing prices it), `usdSource` (`dollar`, `hypercore` or `route`) and `usdAt` (when it was read, null for a dollar or no price). | | `supply`, `burned` | string | Tokens minted, and burned since. | | `price`, `priceUsd` | string or null, number or null | The pool's price after the last trade, in the quote per whole token, and in dollars at the quote's price now. | | `marketCap`, `marketCapUsd` | string or null, number or null | `price` times `supply` less `burned`. | | `volume24h`, `volume24hUsd` | string, number or null | The last 24 hours' volume, in the quote's smallest unit and in dollars. | | `volume1h`, `volume1hUsd`, `traders1h` | string, number or null, number | The last hour's volume, and how many wallets traded. | | `change24hPct` | number or null | Percent, from the close of the last hourly candle begun 24 hours ago or more; null before there is one. | | `holders`, `swaps` | number | Wallets holding it, the pool and other custody left out, and trades so far. | | `launchedAt`, `lastTradeAt` | number, number or null | Block times. | | `totalFeePips` | number | The pool's total fee now, in pips. | | `recipient`, `recipientKind` | string | Who the pool's creator fees pay, and what that is: `holders` (the token itself), `creator`, `splitter` or `wallet`. | | `liquiditySealed` | boolean | The pool refuses outside liquidity. | | `rewardsReceived` | string | What its holders' rewards have received, in the quote's smallest unit. | | `trend` | number or null | The score `sort=trending` ranks by: dollars moving now, from how many wallets, how much of it buying and how young the market is. Null for a quote with no dollar price, which is left unranked. | | `graduation` | object | `stage`, below; `progressBps`, how far from the opening to the line in basis points of the quote the seed collects on the way, 10,000 past it; `graduatedAt`, a time or null. | | `lpLine` | object | The same for the LP line: `progressBps`, `active` and `activeAt`. | `graduation.stage`, and the `stage` filter, are three columns of a board, not the chain's five stages. `graduated` is a pool the hook has graduated for good (`PoolConfig.stage` 2, 3 or 4) or one with no line. `graduating` is any other pool at 5,000 basis points of `progressBps` or more, at chain stage `Below` or `Crossed`. `new` is the rest. For the chain's own stage read `LaunchHook.poolConfig(poolId).stage`, under [Contract reference](https://montramarkets.xyz/docs/reference). There is no bonding curve and nothing migrates: [how a terminal tracks these](https://montramarkets.xyz/docs/build#no-bonding-curve-no-migration). `/api/tokens/{token}` adds what the launch named, `pool` (its terms), `stats`, `rewards`, `splitter`, `config`, `liquidity` and `live`, the pool read from the chain, null when that read fails. `pool.feeFixed` is true when nobody can lower the total any more: it is locked, at its floor, or paid to holders or a splitter. ## Trades and the live stream `/api/tokens/{token}/trades` answers `{ trades: [...] }`, and each event of a stream carries one trade with its `poolId` and `token` added. | Field | Type | What it is | | --- | --- | --- | | `id` | number | The trade's row here: what `before` pages by, and the stream's event id. | | `block`, `logIndex`, `txHash` | number, number, string | Where its `Swap` log is. | | `t` | number | Its block's time. | | `sender` | string | Who traded, as below: not always the transaction's `from`, nor the `Swap`'s `sender`, which is a router. | | `isBuy` | boolean | True when the pool gave tokens out. | | `quoteAmount` | string | What the trader paid on a buy, the hook's fee included, or received on a sale, after it: the size of the `Swap`'s quote amount plus `hookFee` on a buy, less it on a sale. | | `tokenAmount` | string | The tokens that moved. | | `hookFee` | string | The hook's fee, the `FeeAccrued` event's `fee`, an anti-snipe rate's included. The LP fee is apart. | | `price` | string | The pool's price after this swap, not what the trade paid on average. | `sender` is the trader as far as the chain says. Through Montra's router it is the router's caller (`Swapped`'s `sender`). On a launch's first buy it is the creator. Through any other router it is the address the token moved to on a buy, or from on a sale, in that swap's transfer, which names the wallet where the `Swap` names only the router. A router that holds the tokens on the way, or nets two swaps into one transfer, leaves only itself. A contract trading for someone is named in their place. A trade on /api/stream or /api/tokens/{token}/stream: ```text id: 1042 event: trade data: {"id":1042,"poolId":"0x…","token":"0x…","block":1234567,"logIndex":2,"txHash":"0x…","t":1793000000000,"sender":"0x…","isBuy":true,"quoteAmount":"250000000000000000","tokenAmount":"71428571428571428571428571","hookFee":"5000000000000000","price":"0.000000003500000000000000000000000000"} ``` A stream opens with a `retry:` line and sends a `: ping` comment every 15 seconds. Reconnecting with `Last-Event-ID` sends the latest 200 trades after it, then goes live; take anything older from the trades route. A client has ten such replays a minute, and past them a stream still opens, live alone. A client that falls a mebibyte behind is dropped. A client may hold 12 streams at once, a client being an IPv4 address or an IPv6 /64, and an IPv6 /48 may hold 150 in all: past either a new stream answers `429`. A stream gives its place back when it closes. Past 5,000 streams in all, a new one answers `503` with `retry-after`. ## Reading the figures - **Volume is the pool's side.** Every volume here (`volume24h`, `volume1h`, `stats.volume`, a candle's `volumeQuote`, and the platform's totals) sums the `Swap`'s quote amount, which leaves out the hook's fee. So in an anti-snipe window a pool's fees over its volume can pass 100%. To see what traders spent, sum `quoteAmount` over the token's trades: what each buyer paid, and on a sale what the seller received. - **Candles.** `t` is the start of the bucket, a whole multiple of the interval counted from 1970. Each figure is the pool's price after a trade, so `open` is the bucket's first trade's, not the last bucket's `close`, and a bucket of one trade is flat. A bucket with no trades is not in the answer. `from` and `to` bound `t`, both included. `volumeToken` is in the token and `trades` counts them. - **A token with no trades.** `price`, `priceUsd`, `marketCap` and `marketCapUsd` are null until its first trade, though the pool has an opening price. Read it from the chain: `live.sqrtPriceX96` in `/api/tokens/{token}`, which in the quote per whole token is 2^192 / sqrtPriceX96² × 10^(18 − the quote's decimals) with the quote as currency0, and sqrtPriceX96² / 2^192 × 10^(18 − the quote's decimals) with it as currency1 (`pool.quoteIsCurrency0`). - **Holders.** `circulating` in `/api/tokens/{token}/holders` is `supply` less `burned`, whoever holds it, the pool included: not what is in wallets. `pct`, `top10Pct`, `inPoolPct` and `creatorPct` are percent of it. The list and `count` leave out the token, its hook and the pool manager. - **Dollar totals.** `volumeUsd`, `volume24hUsd`, `feesUsd` and `fees24hUsd` in `/api/stats` add each quote at its dollar price now, not at each trade's, and skip a quote with no price: `quotes` shows which have none, and `byQuote` each quote's totals in its own units. If no quote has a price they read 0. - **The buyback's keys.** In `/api/buyback`, `kntq` and `kntqFlow` are the one currency the vault's buyback lane keeps out of the sale for HYPE and sells straight for its token, by those keys whatever that currency is: read its `symbol` and `address`. `status` and `token` are the vault's word, not the registry's: `live` says only that the lane pays out in a token through a burner. ## Every route ### Platform | Route | What it answers | | --- | --- | | `/api/config` | The chain id, the contracts' addresses with Uniswap's Universal Router, Permit2 and quoter (each null until the registry names it), the venue, the launch fee and the quote assets a launch can pair with. [GET /api/config](https://api.montramarkets.xyz/api/config) | | `/api/stats` | Totals across every token: launches, holders, volume, fees and swaps, overall and per quote asset. [GET /api/stats](https://api.montramarkets.xyz/api/stats) | | `/api/stats/daily` | The last 90 days, per day and quote asset: launches, swaps, volume, fees and the protocol's part. [GET /api/stats/daily](https://api.montramarkets.xyz/api/stats/daily) | | `/api/protocol/fees` | The protocol's share of fees: what waits in each hook, what has reached the vault and what it has paid out. [GET /api/protocol/fees](https://api.montramarkets.xyz/api/protocol/fees) | | `/api/buyback` | The vault's buyback lane: what it holds while no token is named, and once its burner is, what its sales spent and bought and the burner destroyed, by day, with the token's supply and the currency the lane keeps out of the HYPE sale. [GET /api/buyback](https://api.montramarkets.xyz/api/buyback) | ### Tokens | Route | What it answers | | --- | --- | | `/api/tokens ?sort, stage, quote, search, creator, route, page, limit` | A page of tokens as cards, 24 unless limit says, up to 100. sort: trending (the default), new, old, mcap, volume, holders, progress, graduated; stage: new, graduating, graduated; route: holders, creator, splitter, wallet. quote and creator are lowercase addresses: a checksummed address or a symbol answers 400. To find a token by name or symbol, use search. [GET /api/tokens?sort=new&limit=5](https://api.montramarkets.xyz/api/tokens?sort=new&limit=5) | | `/api/tokens/{token}` | One token in full: its card, its pool's terms and stats, its holder-reward stats and the pool read live. `GET /api/tokens/{token}` | | `/api/tokens/{token}/trades ?before, limit` | Its trades, newest first, 50 unless limit says, up to 200. Page back with before, the last id you have. `GET /api/tokens/{token}/trades?limit=50` | | `/api/tokens/{token}/candles ?interval, from, to, limit` | Price candles, the newest 500 unless limit says, up to 2,000, oldest first: interval 1m, 5m (the default), 15m, 1h, 4h or 1d; from and to in milliseconds. `GET /api/tokens/{token}/candles?interval=1h&limit=48` | | `/api/tokens/{token}/holders ?limit` | The largest holders with their share, the top ten's share, the pool's and the creator's; up to 200. `GET /api/tokens/{token}/holders?limit=20` | | `/api/tokens/{token}/liquidity ?from, to` | Hourly volume and liquidity in its pool, the positions outside the seed and its pools elsewhere in the same pool manager; a week by default. `GET /api/tokens/{token}/liquidity` | | `/api/tokens/{token}/events ?before, limit` | Its pool's events other than trades: claims, sweeps, fee changes, graduation, the LP fee switching on. `GET /api/tokens/{token}/events` | | `/api/tokens/{token}/rewards ?before, limit` | Its holder-reward events: distributions, claims and pushes. `GET /api/tokens/{token}/rewards` | | `/api/tokens/{token}/rewards/{wallet}` | A holder's rewards read live from the chain: earned now, and the stream it comes from. `GET /api/tokens/{token}/rewards/{wallet}` | | `/api/tokens/{token}/earned/{wallet}` | What holding the token has paid a wallet, automatically and by its own claims, and the block that stands at. Add the token's `earned(wallet)` for everything holding it has earned. `GET /api/tokens/{token}/earned/{wallet}` | | `/api/tokens/{token}/position/{wallet}` | A wallet's balance of the token and what it has bought and sold. `GET /api/tokens/{token}/position/{wallet}` | ### Wallets | Route | What it answers | | --- | --- | | `/api/wallets/{wallet}/holdings` | Every token the wallet holds, as cards with its balance and value. `GET /api/wallets/{wallet}/holdings` | | `/api/wallets/{wallet}/earned` | What holding has paid it, token by token, with the tokens it has sold out of that pay holders, and the block that stands at. `GET /api/wallets/{wallet}/earned` | | `/api/wallets/{wallet}/launches` | The tokens it launched. `GET /api/wallets/{wallet}/launches` | | `/api/wallets/{wallet}/lanes` | The pools whose creator fees it is paid, directly or through a split. `GET /api/wallets/{wallet}/lanes` | | `/api/wallets/{wallet}/trades` | Its last 100 trades across every token, newest first. `GET /api/wallets/{wallet}/trades` | ### Builders | Route | What it answers | | --- | --- | | `/api/builders/programme` | Montra Builders: the rules in force (the shares, the caps, the minimum), the terms' version and hash, the week under way and the programme's totals. [GET /api/builders/programme](https://api.montramarkets.xyz/api/builders/programme) | | `/api/builders` | The approved builders: each one's codes, name, kind, website and what has been credited and paid to it. [GET /api/builders](https://api.montramarkets.xyz/api/builders) | | `/api/builders/periods` | Every week drafted or final: the protocol's revenue, the caps, what builders earned and are owed, the rates, and a final week's hash. [GET /api/builders/periods](https://api.montramarkets.xyz/api/builders/periods) | | `/api/builders/accounts/{wallet}` | The builder a wallet is the account of, by its first code, and its standing. `GET /api/builders/accounts/{wallet}` | | `/api/builders/launch-check/{tx}` | A launch transaction as the programme read it: the codes it carried, and whom it credited. `GET /api/builders/launch-check/{tx}` | | `/api/builders/{code}` | A builder's record: its codes and contracts, what it is owed, holds and has been paid, and its week so far. `GET /api/builders/{code}` | | `/api/builders/{code}/stats` | What its public page prints: the tokens launched through it, what they have traded, its top tokens and what it has been paid. `GET /api/builders/{code}/stats` | | `/api/builders/{code}/next-payout` | What the next batch would pay it as things stand, and why it would pay less or nothing. `GET /api/builders/{code}/next-payout` | | `/api/builders/{code}/launches ?page, limit` | The launches credited to it, newest first, with the trades, volume and protocol fee in each one's window and what it came to on final statements; up to 100 a page. `GET /api/builders/{code}/launches?limit=25` | | `/api/builders/{code}/trades ?page, limit` | The trades through its routers, newest first, and whether each counts; up to 100 a page. `GET /api/builders/{code}/trades?limit=25` | | `/api/builders/{code}/statements` | Its final statements, one a week: what it is owed and held, its lines and the statement's hash. `GET /api/builders/{code}/statements` | | `/api/builders/{code}/statements/{period}` | One statement in full: the document its hash is of, every line with its pool, trades, protocol fee, share, rate and state. `GET /api/builders/{code}/statements/{period}` | | `/api/builders/{code}/payouts` | Its payouts: each batch's amount, its state and the treasury's transaction that paid it. `GET /api/builders/{code}/payouts` | ### Live | Route | What it answers | | --- | --- | | `/api/stream` | Every trade as it is indexed, as server-sent events named trade. Reconnect with Last-Event-ID to be sent what you missed. [GET /api/stream](https://api.montramarkets.xyz/api/stream) | | `/api/tokens/{token}/stream` | The same, for one token's pool. `GET /api/tokens/{token}/stream` | ### Metadata and health | Route | What it answers | | --- | --- | | `/metadata/{id}.json` | A metadata document stored here: name, symbol, description, image and links, by the id its upload was answered with, which no token route gives. A token launched through Montra names its document on IPFS (ipfs://…), pinned as this one: read it from metadata in /api/tokens/{token}, which metadataUri names. The id in a card's imageUrl is the image's, and answers 404 here. `GET /metadata/{id}.json` | | `/health` | The indexer's state: the block it has read to, the chain's head and how far behind it is. [GET /health](https://api.montramarkets.xyz/health) | --- # The Montra RPC URL: https://montramarkets.xyz/docs/rpc The Montra RPC is how a wallet reaches Elysium to use Montra. It reads from Elysium nodes, and it sends only Montra's own transactions: your wallet signs as on any chain, the RPC forwards the transaction into Elysium's inbox on HyperEVM, and Elysium runs it seconds later. Montra pays the HyperEVM gas; you pay Elysium's, as on any chain. - **RPC URL:** `https://rpc.montramarkets.xyz`. Elysium's RPC in a wallet or a script. - **Chain ID:** 99901. Currency HYPE, 18 decimals. - **Sends:** Through the inbox. Usually within 30 seconds; Montra pays the HyperEVM gas. > **It isn't a general RPC.** It only sends Montra's own transactions: launches, trades through Montra's pools, claims, plain HYPE transfers and withdrawals. Anything else is refused and nothing is sent, so other apps, your own contracts and general activity on Elysium won't work through it. Reads aren't limited this way. To use another app on Elysium, switch your wallet to that app's connection. ## How a send reaches Elysium 1. **Your wallet signs.** Exactly as on any chain. The RPC never sees a key and can't change what you signed. 2. **The RPC forwards it to the inbox.** It doesn't go straight to Elysium's sequencer, the service that normally takes a chain's transactions and puts them in order, where direct access is limited to a few parties. The RPC checks your signed transaction and posts it into Elysium's inbox, a contract on HyperEVM, the chain Elysium settles to. Montra pays that gas. 3. **Elysium runs it.** Elysium reads the inbox and runs your transaction from your own address, usually within 30 seconds. You pay Elysium's gas in HYPE, as on any chain. ## What it does **Reads** come from Elysium nodes, Montra's own and a partner's, each checked every couple of seconds and passed over when it falls behind. **Sends** start in your wallet, which signs the transaction as on any chain. The RPC reads it from its bytes and checks it against everything that would make Elysium drop it without a word: a nonce out of turn, a fee cap the base fee could pass, too little HYPE for its gas and value, an account with code. It runs it once, so a revert is told now rather than paid for, then posts it into Elysium's inbox on HyperEVM, alone or in one message with others' that arrive together, and answers with its hash, the hash Elysium gives it. Elysium reads its inbox in order and runs it, usually within 30 seconds. **What it carries** is what Montra's pages send, each transaction read for what it really does before anything is run: - **Montra's contracts.** Calls to the factory, a hook the registry names, Montra's tokens and splitters, and the quote assets they trade in. - **LaunchRouter.** `swapExactIn` alone, of a pool whose key names a Montra hook. - **Uniswap's Universal Router.** An `execute` that really trades a Montra pool: every swap goes through one and through no one else's hook, with no hook data, naming only HYPE, listed quotes and Montra's tokens, its permits for that router alone. It may swap, settle, take, wrap, unwrap, sweep and pay a portion, in the standard ABI encoding, at most 6 commands, 8 actions and 4 hops. - **Permit2.** Its `approve` alone, of a Montra token or a listed quote, for the Universal Router. - **ArbSys.** `withdrawEth(destination)` alone, a withdrawal to HyperEVM. - **Plain HYPE.** A transfer with no calldata goes to any address on at most 100,000 gas, enough for a Safe's receive. On more, only to an address with no code or to one of Montra's contracts. Anything else is refused with `scope` before it is run, and nothing is sent: a new contract, a launcher or router of your own, liquidity through Uniswap's position manager, the fee market's fills. Post those through the inbox yourself, under Scripts and agents. A builder's code at the end of the calldata (ERC-8021) changes none of this. It never asks for a key, cannot sign, cannot change what you signed, and holds nothing of yours. Montra pays the HyperEVM gas of each post from an account used for nothing else. The Montra RPC sees your signed transaction before it is public and posts it as it arrives, alone or in one message with others' that arrive together. Montra holds a key to Elysium's sequencer for its own operations; it is never used for your transactions. ## What a send through the inbox means - **It waits in public from its post until it runs: usually under 30 seconds, sometimes minutes.** In that time your signed transaction can be read by anyone watching HyperEVM, and the few parties with direct access to Elysium's sequencer could trade ahead of it. Set slippage as you would in a public mempool: the minimum you sign for is what protects you. - **One at a time.** Send each after the last has landed. Speed up and cancel do nothing: a transaction posted runs, or is dropped, as it was signed, and the RPC refuses a second one at a nonce already on its way. - **It is still yours.** Elysium runs the transaction you signed, from your address. Holders and holder rewards count it as yours, as they would one sent any other way: Montra's posting account exists only on HyperEVM and appears nowhere on Elysium. - **A dropped one costs nothing.** If Elysium reads it and does not run it (another transaction of yours took its nonce, say), nothing is spent. The site says so, and your wallet may show it pending until you clear its activity. One the RPC lost track of after its post may still land: the site says it hasn't reached Elysium yet, and checks whether it ran before your next send. ## Limits | Limit | What it is | | --- | --- | | Reads | 6,000 a minute per connection (an IPv4 address, an IPv6 /64) and 12,000 a minute across an IPv6 /48, counted by weight: a plain read 1, a call or an estimate 2, a block's receipts 5, a log query 10, and more for a call that keeps a node long or answers a lot. Once 240,000 a minute has reached the nodes across everyone, only callers under a quarter of their own minute are served. | | Batches | 50 calls at most, 5 of them `eth_getLogs` or `eth_getBlockReceipts` | | Logs | 10,000 blocks a query, or 5,000,000 when it names a contract and an indexed topic; 10,000 logs an answer | | Size | 8 KiB a transaction, which holds everything the site sends: the longest launch it can make is about 6 KiB. Past 1 KiB a transaction counts as one more send per KiB against these limits: a swap through LaunchRouter or an approval counts 1, a trade through the Universal Router or a launch 2, the longest launch 6. | | Per account | 10 a minute and 1,500 a day, 4 on their way at once | | Per connection | 20 a minute and 6,000 a day; and every attempt counted, refused or not: 60 a minute | | Per network | An IPv6 /48: 200 a minute and 30,000 a day, and 600 attempts a minute. An IPv4 /24 or an IPv6 /32: 60,000 a day. | | Beyond trades | Of each day's limit, anything but a trade, a launch or money leaving may take 600 per account, 2,000 per connection, 10,000 per IPv6 /48 and 20,000 per IPv4 /24 or IPv6 /32 | | Gas | Montra pays the HyperEVM gas from a budget a day, at most an eighth of it an hour, the last 5% of each kept for money leaving. Once it is spent, what it can't pay for is refused (`budget`) until 00:00 UTC, or until the hour has room. As it runs short, accounts with a landed trade behind them are served first, and others hear `busy`. | | Inbox wait | Sends pause once a message the RPC posted has waited three minutes for Elysium to read it (`paused`, reason `inbox-stalled`), and resume when Elysium reads it. Elysium reads its inbox in bursts, so a minute's wait is nothing wrong. | | Not served | Filters and subscriptions (poll `eth_getLogs` instead), websockets, `eth_sendTransaction` and signing, admin and debug methods | Past a read budget a request answers HTTP `429` with `retry-after` in seconds and a JSON-RPC error of code `-32005`, its `data` naming `montra: "limit"`, a `scope` of `client`, `site` or `all`, and `retryAfter`; there are no other rate-limit headers. A batch of more than 50 calls is refused whole, with code `-32600`. A well-formed request otherwise answers HTTP `200`, a batch with a reply per call, each its own `result` or `error`, so check every reply. A sixth log read in a batch, a node too busy for one call, or a batch's answer grown too large fails that call alone, with code `-32005`, and the calls around it are answered. Everyone's sends share five lanes a minute, none able to spend another's room, so a flood of cheap transfers refuses no trade and no one's money leaving. | Lane | Everyone, a minute | What goes in it | | --- | --- | --- | | Trades | 200 | Launches, and trades through a Montra pool. | | Money leaving | 60 | A withdrawal of 0.001 HYPE or more, and a claim of what is really owed. At most 3 a minute per account, 6 per connection and 20 per IPv4 /24 or IPv6 /32. Past those and attempts, no limit of a connection's or a network's applies, so a neighbour's trades on your address don't use it up. | | Approvals and cancels | 120 | An approval of a Montra token or a listed quote for the router, Permit2 or the factory, and a wallet's cancel. At most 5 a minute per account, 10 per connection and 40 per IPv4 /24 or IPv6 /32. | | Montra's other calls | 60 | The rest of what Montra's pages send: dividing fees, a claim of nothing, a hook's and a splitter's settings. | | Low-value | 40 | Plain HYPE transfers, smaller withdrawals, trades under 0.001 HYPE where the quote is HYPE, Permit2's own `approve`, a token's own moves, and a trade or a claim that would not run yet, behind one of yours still on its way. | ## When a send is refused A refused send is a JSON-RPC error whose `data.montra` says why, beside words for a wallet to show, which may change. One that reverts when the RPC runs it carries no code: its `data` is the revert's bytes. Each answers HTTP `200`, and but for `not-posted` nothing was sent. While sending is switched off, every send answers `-32003`. | `data.montra` | Code | What it means | | --- | --- | --- | | `limit` | `-32005` | A limit above: `scope` says whose, `retryAfter` when it allows again, in seconds. | | `budget` | `-32005` | Montra's gas for posting is spent: `scope` is `day` or `hour`, `reason` `budget-spent` or `budget-hour`, `retryAfter` in seconds. | | `paused` | `-32005` | Sends are paused for everyone: `reason` is `inbox-stalled`, `inbox-refused`, `poster-unfunded`, `hyperevm-gas` or `hyperevm-disagree`. | | `busy` | `-32005` | A moment the RPC can't take it: its queue or its checks are full, or only accounts it knows are being served. Nothing was sent. | | `nodes` | `-32005` | Elysium's nodes couldn't answer its checks. Nothing was sent. | | `not-posted` | `-32005` | It couldn't post the transaction, or couldn't confirm it did, so it may yet land: check before sending another. | | `barred` | `-32005` | It holds an account's sends for an hour after 3 drops of its own doing in an hour, a connection's after 9. | | `in-flight` | `-32005` | 4 of the account's are on their way; or an earlier one waits to be posted again, with `retryAfter`. | | `nonce-low`, `nonce-high` | `-32000` | The nonce is out of turn: `next` is the one to use. | | `nonce-blocked` | `-32000` | The send at `next` never ran (`dropped`, its hash), and must be cleared before anything after it runs. | | `nonce-pending` | `-32000` | That nonce is already on its way, and can't be replaced. | | `fee-low` | `-32000` | The fee cap is under the base fee and 10%: `need`, in wei. | | `funds` | `-32000` | Too little for gas and value, beyond what is on its way: `balance`, in wei. | | `gas-limit`, `gas-low` | `-32000` | Over the 32,000,000 a transaction may ask for, or too little to run. | | `contract-sender` | `-32000` | The account has code, and Elysium runs no transaction signed for it (an EIP-7702 delegation is fine). | | `guard` | `-32000` | A Safe's launch, or a call setting the registry's `montra`: these never go through the inbox. | | `invalid` | `-32000`, `-32602` | It can't run as it is: malformed, another chain, a blob or EIP-7702 transaction, over the size cap, a high-s signature, or a second send in one batch. | | `scope` | `-32003` | A transaction it doesn't carry, as under What it does. It won't be taken however often it is sent. | Whose limit a `limit` met, by its `scope`: | `scope` | Whose limit | | --- | --- | | `sender-minute`, `sender-day` | The account's | | `client-minute`, `client-day` | The connection's: an IPv4 address, an IPv6 /64 | | `site-minute`, `site-day` | An IPv6 /48's | | `network-day` | An IPv4 /24's or an IPv6 /32's | | `sender-nontrade-day`, `client-nontrade-day`, `site-nontrade-day`, `network-nontrade-day` | What of that day is left to anything but a trade, a launch or money leaving | | `sender-own-minute`, `client-own-minute`, `network-own-minute` | What one account, connection or network may hold of the money-leaving lane | | `sender-setup-minute`, `client-setup-minute`, `network-setup-minute` | The same of approvals and cancels | | `all-trade-minute`, `all-own-minute`, `all-setup-minute`, `all-montra-minute`, `all-low-minute` | A lane, everyone together: trades, money leaving, approvals and cancels, Montra's other calls, low-value | | `client-attempts`, `site-attempts` | Attempts, refused or not: a connection's, an IPv6 /48's | `GET /health` says what a trade would hear now: `sends.reason` is one of the `paused` reasons, `budget-spent`, `budget-hour`, `off` (sending switched off) or null, and `sends.retryAfter` the seconds until a budget has room, or null. Money leaving keeps a reserve past `budget-spent` and `budget-hour`, so don't hold a withdrawal or a claim back for those: send it, and read the answer. `montra_sendStatus`, given the hash, says where a send it took is: `queued`, `posted` (its post on HyperEVM as `l1Hash`, in the inbox once `messageIndex` is set), `landed`, `dropped` or `failed`, the last two with a `code` (`nonce-used`, `not-run`, `not-posted`, `lost`). ## Which RPC a wallet uses Any Elysium node serves reads. The site reads through the Montra RPC whatever a wallet uses, and asks the wallet which RPC it uses: the Montra RPC answers a call to an empty address (`0x…6d6f6e747261`, the word montra in hex) with its name, where any other RPC answers nothing. That is a hint, not proof. A wallet that already has Elysium keeps its own RPC when the network is added again: MetaMask adds the new one to the network's list and goes on using the old until you choose it (its menu, Networks, the line under Elysium), and other wallets need the network's RPC edited by hand. ## Scripts and agents A script reads through the Montra RPC as it would through any RPC, and sends what it carries the same way: `eth_sendRawTransaction` with a signed transaction, then its receipt. For anything else, or by choice, it posts through the inbox itself, paying its own HyperEVM gas: sign the Elysium transaction without sending it, then send `sendL2Message` on the inbox with `0x04` followed by the signed bytes. Its hash on Elysium is the hash of those bytes. ### Through the Montra RPC A wallet or a script points at it as Elysium's RPC. A fee cap of twice the base fee leaves room for the seconds in the inbox; only the base fee is charged. ```sh cast balance
--ether --rpc-url https://rpc.montramarkets.xyz cast send --value 0.001ether --rpc-url https://rpc.montramarkets.xyz --account mywallet ``` ### Through the inbox yourself Every field given, so `cast mktx` signs without asking any node. Check the transaction's receipt on Elysium: a dropped one leaves no trace there. ```sh INBOX=0xce59f8B65ad41C9DAE4c07A0571a39824bD92cff RAW=$(cast mktx --chain 99901 --nonce --gas-limit 500000 --gas-price 1gwei --priority-gas-price 0 \ --value 0.001ether --account mywallet) cast send $INBOX "sendL2Message(bytes)" 0x04${RAW#0x} --rpc-url https://rpc.hyperliquid.xyz/evm --account mywallet cast keccak $RAW # its hash on Elysium ``` ### Deposit HYPE From a plain wallet on HyperEVM; it lands at the same address on Elysium. ```sh cast send $INBOX "depositEth()" --value 0.05ether --rpc-url https://rpc.hyperliquid.xyz/evm --account mywallet ``` ## Withdrawals by hand 1. **Start it on Elysium.** `withdrawEth(destination)` on ArbSys (`0x…64`), paying the HYPE as value. (under a minute) 2. **Wait for a confirmed assertion.** Elysium's state is asserted on HyperEVM about every six hours; each assertion is final after 483,840 HyperEVM blocks, about 5.5 days. A withdrawal is claimable once a confirmed assertion's send count passes its position. (about 6 days) 3. **Claim on HyperEVM.** `executeTransaction` on the rollup's outbox, with a proof any Elysium node builds (`constructOutboxProof(size, leaf)` on NodeInterface, `0x…c8`), the size being the confirmed send count. Anyone may send it; it pays only the destination. (seconds, HyperEVM gas) --- # Contracts URL: https://montramarkets.xyz/docs/contracts Montra's addresses on Elysium, read live from its registry contract rather than typed onto this page. The interface is built with one address, the registry, and reads every other from it. ## The address book Every address is read from the registry at `0xF1E2FA75422F889e565F3142bdbf60B28dce2aa7` on Elysium: `get(name)` for each name below, `hooks()` for the launch hooks. Read them there rather than from a copy of this page. | Launch and trade | Read from | Role | | --- | --- | --- | | Registry | the registry itself | The address book: names every address here and lists every launch hook. The one address the interface is built with. | | Factory | `get("factory")` | Launches tokens: one transaction from token to seeded pool. Emits TokenLaunched. | | Launch hook | `hooks()` | Attached to every pool: takes the fee, holds the seed and keeps the creator side's balance. | | Router | `get("router")` | Exact-input swaps with a minimum and a deadline, and quotes for them. | | Lens | `get("lens")` | Read-only views of a pool, its fees, its milestones and its holder rewards. | | Quote registry | `get("quoteRegistry")` | The assets a launch can pair with. Native HYPE is listed from the start. | | Token master | the factory's `getModuleSet` | Every launch token is a clone of it: compare a token's code hash with a clone of this to verify it. | | Splitter master | the factory's `getModuleSet` | Every fee splitter is a clone of it. | | Protocol fees and MONTRA | Read from | Role | | --- | --- | --- | | Protocol fee vault | `get("protocolVault")` | Receives the protocol's part of each pool's fee, and launch fees. | | Fee market | `get("feeMarket")` | Sells fees the vault holds in one asset for another. | | Route filler | `get("routeFiller")` | Fills fee-market sales by trading through pools. | | Buyback filler | `get("buybackFiller")` | Fills the buyback's sales: buys MONTRA in its own pool, and the burner destroys it. | | MONTRA | `get("montra")` | The launchpad's own token, which the buyback buys and burns. The registry's name for it is the only proof: a token anywhere else under its name is a copy. | | Uniswap v4 | Read from | Role | | --- | --- | --- | | PoolManager | `get("poolManager")` | Uniswap v4's own contract, where every pool lives. | | Multisigs | Read from | Role | | --- | --- | --- | | Governor | the registry's `owner()` | The Safe multisig that owns Montra's contracts. | | Treasury | the vault's `laneAt(0)` | A Safe multisig the vault pays out to. | --- # Montra Partner URL: https://montramarkets.xyz/docs/partner Bespoke launches for established projects: a token born through Montra with the rules its design needs, and every difference from a standard launch fixed at launch and readable on chain. > **Coming soon.** Details may change before the first partner launch. - **For:** Established projects. Whose design needs more than a standard launch. - **Its principle:** Disclosure. What differs from a standard launch is shown, not forbidden. - **Its label:** Montra Partner. A category of its own, never merged with standard Montra tokens. ## What it is A standard Montra token is the same as every other: its whole supply minted once into a pool position that never comes out, no owner, no minting, and a fee from the menu ([Launching a token](https://montramarkets.xyz/docs/launching)). That sameness is why a trader can trust one without reading its code, and why a design that needs more can't sit among them. Some designs need more: a game whose contract mints its rewards, a token that starts with no supply, part of a supply set aside for the project, or a launch fee that falls by half-lives or in steps, or over hours. Montra Partner is a second family of tokens for them. Every partner token is created by Montra's partner factory from Montra's partner token template, trades on Montra's partner hook (a Uniswap v4 hook), and is recorded in one on-chain registry, the Montra umbrella. A standard pool may carry a falling fee on buys too, its anti-snipe fee: in a straight line, over at most half an hour, what it takes above the pool's fee going to the creator side. A partner's launch fee can take other shapes and run for longer, and most of what it takes above the long-run rate goes to Montra. Its principle is **disclosure over restriction**. A partner token may differ from a standard one where its design needs it; what it can do is fixed at launch, readable on chain and set out in Montra's dated reviews. Each launch is curated: Montra approves it in advance, bound to its exact terms, and the approval can be used once. ## What a partner can choose Within limits the partner hook and the token template enforce whatever is chosen: - **Supply.** What is minted at launch, including none at all. - **A minter.** One contract, such as its game's mining contract, with a cap and optionally a rate; or no minter. - **The fee schedule.** From Montra's catalogue: flat, different for buys and sells, or a launch fee on buys that falls in a straight line, by half-lives or in steps, with an optional hold first. The catalogue can grow with reviewed additions, without a new hook. - **Where fees go.** Any address it names, for example its own revenue or staking contract, which it can later hand on. - **How the market opens.** Liquidity locked for good through Montra's locker (tokens, the quote asset, or both: a token with no supply can open with bids that buy what its miners sell), its own liquidity added with the launch itself, or both. For a set time after launch it can close the pool to new liquidity from anyone, the project included, except Montra's locker, so a launch fee can't be avoided through range orders. Liquidity can always be withdrawn. - **An allocation.** Part of the supply to its own wallets, vesting or distribution contracts, each one public. ## Launch types - **Game emissions.** A game or rewards contract as the token's minter; the token can start with no supply; flat fees. - **Fair launch.** A fixed supply, no minter, nothing allocated; an anti-snipe launch fee on buys that falls away soon after launch. - **Project launch.** An optional minter, part of the supply (at most half) to the project, and an anti-snipe launch fee on buys that falls away soon after launch. ## What every partner token keeps Whatever its design, a partner token keeps these rules, and nothing a partner chooses can change them: - No owner, no pause, no blacklist, no transfer tax, no rebasing and no upgrade on the token. Its name, symbol and profile are written once. - Fees are fixed at launch and never rise. A launch fee, if there is one, is on buys and falls to the long-run rate within a set time; a sale's fee is capped at every moment, and the project can only lower its long-run fee. - Fees are charged in the pool's quote asset, never in the token, and the pool's liquidity providers earn its LP fee as in any Uniswap v4 pool. - Every trade fills the whole amount it asks for or does not happen. Nothing refuses a sale because it is a sale or because of who sends it, and nothing can stop a liquidity provider withdrawing, or charge them for it. - A minter, if there is one, is a contract, never a personal wallet, fixed at launch, and it can never mint past its cap. The cap counts either everything ever minted or what exists now (so burned tokens can be mined again); both, and how much is still mintable, are readable on chain. - Montra takes a fee on every trade, set at launch. During a launch fee, most of what it takes above the long-run rate goes to Montra, not the project, so a project buying its own launch pays the launch fee like anyone else. ## How terminals tell them apart Each family of token keeps its own label, because each keeps different promises. How a terminal shows them is its own choice. - **Montra:** Can't mint, has no owner, its liquidity locked from block one. - **Montra Partner:** May differ by design, every difference fixed at launch, readable on chain and reviewed by Montra. - **Montra Listings:** Its supply is its issuer's, and its liquidity the market maker's or the project's. The Montra umbrella is one registry that terminals and apps integrate once. It records every Montra Partner token, which only Montra's partner factory can add, beside [Montra Listings](https://montramarkets.xyz/docs/listings) under a family of its own. A partner token is never shown as a standard Montra token, and a standard one is never a partner token. Names, tickers and logos prove nothing; the registry does. The registry shows each partner token's standing: active, flagged (shown with a warning), or label withdrawn (no longer presented as a Montra Partner token). Each can carry dated reviews: plain-language labels, such as that its minter has an owner, that part of its supply went to the project, that the project controls most of its pool's liquidity, or that it has emissions, and a published report. The registry, its contracts and their addresses are published with the product. ## What partners get - **The category.** Their token identifiable as Montra Partner, through the registry terminals and apps can follow. - **A launch on Montra.** A page on the site, and the launch announced to Montra's community. - **Support.** Help designing the launch within the partner rules, and a review of how the project's contracts connect to Montra's. - **Market making.** An introduction to Acheron Trading, a professional market maker Montra works closely with, where the project wants one. ## What it is not - Not a way to bring an existing token from elsewhere: that is [Montra Listings](https://montramarkets.xyz/docs/listings). - Montra does not build or run a partner's own game, treasury or staking. Those stay the partner's contracts, and their powers are disclosed in the token's review. - No hook can tax trades in other pools or on other venues. ## How a partner launch goes 1. **A first conversation.** The project, its token and what its design needs beyond a standard launch. 2. **Review.** Montra goes through the project's contracts and the launch's design, and what it will disclose is agreed. 3. **Approval.** Montra approves that one launch in advance, bound to its exact terms; the approval can be used once. 4. **Launch.** The token is created by Montra's partner factory and recorded in the Montra umbrella. 5. **Trading.** It trades in its pool on the partner hook, labelled Montra Partner wherever the registry is followed. ## Where it stands Montra Partner is coming soon. Until then, standard launches are what Montra offers: [Launching a token](https://montramarkets.xyz/docs/launching). --- # Montra Listings URL: https://montramarkets.xyz/docs/listings Tokens not native to Elysium, landed liquid: a market made by a professional market maker, its liquidity in place when trading opens, so a project doesn't have to do it alone. > **Coming soon.** Details may change before the first listing. A listing's route, all in one: a token from any chain (the six are examples) crosses with a bridge partner onto Elysium, where its Uniswap v4 market is seeded with liquidity positions deepest at the price, opens, and goes live, each swap moving the price a step around home's. Illustrative: the prices, sizes and volume too. 1. **Bridge.** Your token crosses from its home chain, any chain, with a bridge partner, one for one. 2. **Seed.** Montra and the market maker place liquidity, deepest at the price, before trading opens. 3. **Trade.** Trading opens, and swaps move the price a little either way, in line with home. ## What it is Montra Listings gives tokens that aren't native to Elysium access to its ecosystem, professionally handled and landing liquid. With Montra Listings, the project has the inventory and strikes up a market-making deal with Acheron Trading, a professional market maker Montra works closely with. Montra does the rest: the listing, the market and its opening, the trading interface, discovery and routes. The market opens with the market maker's liquidity already in place. ## Why Montra Listings - **One chain, one community.** Tokens and their communities each live on one chain, and people rarely move between chains. A token wants to be found everywhere. - **A relaunch isn't the token.** Launching again on a new chain loses the holders, the market cap and the history. It's a reskin, not the token. - **A bridge brings a copy, not a market.** Bridging gives an official copy but no market. A market needs tokens to sell (inventory), liquidity at the prices around it (depth) and a price kept close to home: if it drifts, traders buy cheap on one chain and sell dear on the other (arbitrage), at the market's expense. Done well, that's a professional's job. - **On your own.** A team going it alone is left with a Uniswap pool on its own: nobody finds it, it's thin, and its price drifts. Montra Listings is the one-stop shop. It gives the token **distribution on Elysium** (the Montra Listings category, discovery on Montra and routing that brings traders to it), **a professionally managed market** (the market maker places the liquidity before trading opens and keeps the price in line with home), and **bridging done properly**: the token arrives as its official, one-for-one version over an existing bridge, with bridge partners, and Montra checks the route without building the bridge itself. The project brings the inventory; Montra does the hard part, and the project earns a share of the trading fee. ## On your own, or with Montra Listings The same five moments, on your own and with Montra Listings: the token at home, the bridge, arriving on Elysium, the market and who finds it, and the price. Illustrative: the prices are examples. - **On your own.** Your token on its home chain; which bridge? Several options, no guidance, no expertise to judge them; on Elysium, nothing to trade; a pool on its own, thin and lost among many, with no distribution; the price drifts from home (for example home $1.00, Elysium $0.71: illiquid, a wide spread). - **With Montra Listings.** From any chain; a bridge partner carries it across one for one; Montra and the market maker build a deep market before trading opens, concentrated liquidity deepest at the price; listed in the Montra Listings category; the price tracks home (home $1.00, Elysium $1.00: liquid, trading normally). | What matters | On your own | With Montra Listings | | --- | --- | --- | | Distribution | A pool nobody finds | The Montra Listings category, discovery and routes on Elysium | | The market | Thin, and the project's to build and run | Liquidity a professional market maker places before trading opens, deepest at the price | | Price alignment | Drifts from home, and bleeds to arbitrage | Kept in line with the home market by the market maker | | Effort | Inventory, market making, bridging and promotion, all the project's | The project brings the inventory and agrees terms with the market maker; its token comes over an existing bridge, and Montra does the rest | ## Two kinds, one shape - **Arrival.** A token from another chain, as its official representation on Elysium. - **Native listing.** An Elysium project's existing token, given a Montra market. Both are listed the same way, under one label of their own: **Montra Listings**, apart from standard Montra tokens and from [Montra Partner](https://montramarkets.xyz/docs/partner), because a listed token makes different promises. ## Is it the same token? **Yes.** A token arriving from another chain isn't a relaunch: it's the project's own token. It already exists on its home chain (Ethereum, Base, Solana, HyperEVM or another) and reaches Elysium as its official representation. It has its own address on Elysium, but each one on Elysium matches one taken out of circulation on the chain it left, and holders can bridge it back. Its supply is kept whole one of two ways: - **Lock and mint:** the originals are locked on the chain it leaves, and the same amount is minted on Elysium. This is Elysium's own official bridge, its canonical bridge, from HyperEVM. - **Burn and mint:** burned on one chain as the same amount is minted on the other, as an issuer's own multichain token does (a LayerZero OFT or a Wormhole NTT, for example). Elysium's door today is its canonical bridge from HyperEVM, so a token on another chain needs an official route to HyperEVM first; more bridges are added as they come to Elysium. Montra doesn't build bridges: each bridge is a route Montra checks and switches on, with bridge partners. A token already on Elysium isn't bridged at all. ## Who does what - **The project:** Brings the token and the inventory, agrees terms with the market maker, and accepts the listing itself. - **The market maker:** Runs the liquidity: places it, moves it, and keeps the price in line with the home market. - **Montra:** Provides the venue: the listing, the market and its opening, the trading interface, discovery and routing. It never builds the bridge, and never holds the liquidity, which belongs to the market maker or the project. ## How a listing goes 1. **Approval.** Montra approves the listing. 2. **Acceptance.** The project accepts it itself. That creates the token's Uniswap v4 market, closed: nobody can trade it while the liquidity goes in. 3. **The liquidity.** While the market is closed, the market maker places its liquidity: offers above the price, bids below it. 4. **The opening.** Trading opens at the set time, at least ten minutes and at most seven days after acceptance, and not before, with the liquidity already in place. ## What traders can rely on The listing contracts enforce these: - Every listed market is approved by Montra and accepted by the project itself. - A market opens at its set time and not before. - Nobody can block a deposit or a withdrawal of liquidity, Montra included. The market maker can always take its liquidity out: before the opening, while trading, or after the market is retired. Depth is never guaranteed. - Fees are charged in the quote asset (HYPE first), each way. They never rise; the project can only lower them. - Every trade fills in full or not at all: none fills part of its size and leaves the price somewhere nobody paid for. - A listing can be retired if its token stops being what was listed, for example if a bridge fails. Trading then stops for good, but liquidity and earned fees still come out. ## What a listed token is - **Montra didn't mint it.** Its supply lives on its home chain, and more can arrive through the bridge. - **Its price follows its home market,** kept in line by the market maker and by arbitrage. - **Its page shows** its home chain, the route it came by, who can mint it on Elysium, its fees each way, and when its market opens. Every listed token is recorded in the Montra umbrella, the registry [Montra Partner](https://montramarkets.xyz/docs/partner) shares, under its own family, Montra Listings: a terminal that reads the umbrella once sees every listing. The registry, the listing contracts and their addresses are published with the product. ## For projects - **You bring.** Your token, inventory for the market maker, and a deal with the market maker. - **Montra does.** The listing, the market, the opening, the interface, discovery and routes. - **You get.** A liquid Elysium market from its opening, a share of the trading fee in that market, and Montra's distribution to Elysium's traders. ## Where it stands Montra Listings is coming soon.