draft · current risk score is a first attempt, yet to be refined.

buy many

4 min readmetropolx
  • contracts
  • monad
  • nad.fun
share

buy a whole list of nad.fun tokens in one transaction. you send MON, every token lands in your wallet. no basket, no custody, no partial fills.

it runs on NadMultiBuy, a small contract we deployed on monad mainnet.

use it from the dashboard

  1. open established cults, every row has a checkbox
  2. tick the tokens you want, up to 20
  3. set MON per token and your slippage (0.5%, 1%, 2% or custom)
  4. connect your wallet, review, confirm

the review screen shows, before you sign:

  • each token, its route (nad.fun DEX or nad.fun V2) and the least amount you will receive
  • the buy fee and the total MON sent
  • the network fee, at most
  • your slippage

quotes refresh every few seconds while the review is open. the transaction is valid for 5 minutes, after that it reverts and nothing is spent.

the gas badge compares one buyMany against buying each token one by one. batching is cheaper at every size, see gas.

what can be bought

  • legacy nad.fun tokens, on the bonding curve or graduated
  • nad.fun V2 tokens quoted in WMON, on the curve or graduated

V2 tokens quoted in anything else (LVMON, BNCB, USDC…) cannot be bought here, the dashboard greys them out with the reason. tokens that did not come from nad.fun are rejected by the contract.

how a buy works

one call, buyMany(legs, to, deadline, maxFeeBps). a leg is one purchase:

struct Leg {
    address token;   // what to buy
    uint256 monIn;   // MON to spend on it
    uint256 minOut;  // least amount of token `to` must receive, non-zero
}

for each leg, in order, the contract:

  1. picks the nad.fun router that fits the token:
    • legacy, graduated → DEX_ROUTER
    • legacy, still on the curve → BONDING_CURVE_ROUTER
    • V2 quoted in WMON → NadFunRouter.buyWithNative, which picks curve or pair itself
  2. buys with monIn, the router sends the token straight to to
  3. checks to's balance went up by at least minOut

then it pays the fee, refunds every unspent MON to you, and emits BoughtMany.

all or nothing. if any leg fails, the whole transaction reverts. you never end up with half a list.

fee

  • charged on the MON spent buying, sum(monIn), not on what you send on top
  • capped at 1% (MAX_FEE_BPS = 100) in the contract, it can never go higher
  • currently 0%
  • you pass maxFeeBps, the highest fee you accept. if the fee changed above it before your transaction lands, it reverts with FeeAboveMax

msg.value must cover sum(monIn) + fee. anything above comes back in the refund.

safety

  • no custody. the contract holds nothing between calls: tokens go router → your wallet, MON is refunded in the same transaction. it only accepts MON while a buy is running
  • your minimums, checked twice. each minOut goes to the router, and the contract checks the actual balance change of to itself. a router that ignores its minimum is caught, and tokens you already held never count
  • deadline. past deadline, it reverts with Expired
  • reentrancy. every entry point is guarded, MON is sent last
  • owner powers. the owner (Ownable2Step) can set the fee within the 1% cap, set the fee recipient, and pause new buys. the owner cannot touch your funds or tokens
  • reviewed internally, not externally audited yet

fresh tokens. nad.fun applies anti-sniping penalties right after launch: legacy tokens fill about 1% under quote for 5 blocks, V2 up to 80% for 7. your minOut bounds the loss, but buying a token younger than ~10 blocks will likely revert.

gas

monad bills the gas limit, not the gas used. the dashboard sends the estimate plus 10%.

full transaction cost, every account cold, 0.5 MON per token:

routetokensone by onebuyManysaved
legacy curve3954k707k26%
103.18M1.90M40%
legacy DEX3841k720k14%
103.05M2.28M25%
V2 pair31.71M1.12M35%
105.72M2.81M51%
2011.43M5.22M54%
mixed31.13M1.09M4%
104.25M2.61M39%

one by one pays cold access to the router, curve, WMON and fee collectors on every transaction, a batch pays it once.

errors

errorwhy
Expiredthe deadline passed before the transaction landed
NoLegs, TooManyLegs0 legs, or more than 20
ZeroMinOuta leg's minOut is 0
FeeAboveMaxthe current fee is above your maxFeeBps
MonBudgetExceededmsg.value does not cover sum(monIn) + fee
UnsupportedTokennot a nad.fun token, or a V2 token not quoted in WMON
ComponentLockeda legacy token is mid-graduation, or nad.fun V2 is halted. retry in a bit
LegFailed(i, token, reason)the router reverted on leg i, reason is its revert data. usually slippage
InsufficientOut(i, got, min)leg i delivered less than its minOut

contract

networkmonad mainnet (chain id 143)
NadMultiBuy0xe9546c686A146a398Ce58FB67f5077bE8Ab36fa0
RecipeQuoter0xB4212eB16F8Aa1550943D24e00D49a112d3d8f80

building your own frontend? RecipeQuoter.multiBuyLegs(tokens, monIn, slippageBps) returns ready-made legs from live quotes. read feeBps() and pass it as maxFeeBps.