- Before graduation — trade against the token’s
BagsBondingCurve(buy with native ETH, sell for native ETH). - After graduation — trade against the token/WETH Uniswap v4 pool through the Robinhood-modified UniversalRouter.
Prerequisites
Before starting, make sure you have:- Completed the Environment Setup.
- Read the Overview (fee model, lifecycle) and know the token address you want to trade.
- ETH in your wallet for buys and gas.
1. Route by Migration Status
Read the token’s state and branch onmigrated. The simplest source is BagsLens (see Read State):
2. Slippage Helper
Every trade below derivesminOut from a fresh on-chain quote taken immediately before signing, with slippage applied. Use pure integer math:
slippage.ts
Take the quote right before the swap — not from a stale polled value. Approvals and block time can move reserves enough to trip slippage otherwise.
3. Trade on the Bonding Curve
Before graduation, trade directly againststate.curve.
Buy (ETH to token)
Quote withquoteBuy, then call buy with native ETH as value:
curve-buy.ts
buyFor(recipient, minTokensOut) is also available if you want the tokens delivered to a different address.Sell (token to ETH)
Curve sells pull tokens via a plain ERC-20transferFrom, so you must approve the curve first (a one-time maxUint256 approval is convenient). Then quote with quoteSell and call sell:
curve-sell.ts
sellFor(recipient, tokensIn, minQuoteOut) delivers proceeds to another address.
4. Trade After Graduation (Uniswap v4)
After migration, the token trades in a Uniswap v4 pool paired with WETH. Swaps go through the Robinhood-modified UniversalRouter. Key facts for the pool phase:- The pool pairs the token with WETH, not native ETH. To buy you need a sufficient WETH balance — wrap only the shortfall (the amount your wallet is missing), not the full input, since it may already hold some WETH. To receive native ETH after a sell you unwrap WETH.
- The router spends your ERC-20 input through Permit2, so you must set up a two-leg Permit2 route once.
- Swaps are exact-in only — the Bags hook reverts exact-out WETH by design.
- Quote amounts from
V4Quoteralready include the 2% hook fee; apply slippage only, never subtract the fee again.
4a. Periphery ABIs
These infrastructure contracts aren’t part of the Bags ABI export. Save the minimal fragments asabi/periphery.ts:
abi/periphery.ts
4b. Pool key and swap direction
The pool is identified by a v4PoolKey. It must match the factory’s derivation exactly. Save as poolKey.ts:
poolKey.ts
4c. Quote a pool swap
V4Quoter.quoteExactInputSingle is not a view function — call it through simulateContract. The returned amount already accounts for the 2% hook fee.
pool-quote.ts
4d. Wrap ETH and set up the Permit2 route
To buy, wrap the ETH you’re about to spend; to swap any ERC-20 through the router, ensure the two-leg Permit2 route (ERC-20 -> Permit2, then Permit2 -> router).
permit2.ts
4e. Execute the swap
Encode the modified router calldata manually: commandV4_SWAP (0x10), actions [SWAP_EXACT_IN_SINGLE (0x06), SETTLE_ALL (0x0c), TAKE_ALL (0x0f)], with the extra minHopPriceX36 set to 0.
pool-swap.ts
4f. Full pool buy and sell
Compose the helpers. A buy wraps ETH, ensures the WETH route, quotes, then swaps. A sell ensures the token route, quotes, swaps, then unwraps the WETH proceeds back to native ETH.pool-trade.ts
The unwrap is a secondary convenience transaction — the sale itself is already final once the swap confirms. If unwrapping fails, your proceeds are simply held as WETH and can be unwrapped or reused later.
5. Understanding Fees and Quotes
- The 2% fee applies in both phases. On the curve,
quoteBuy/quoteSellreturn fee-adjusted amounts (tokensOut/quoteToSellerare what you actually receive). In the pool,V4Quoteroutput already includes the hook fee. quoteBuyreturnsrefundQuote > 0when a buy would cross graduation — onlygrossUsedis spent and the rest is refunded. Surface this to users.- Distinguish a failed quote (RPC error / revert) from a zero quote. Treat a failure as “no quote” and disable trading; never coerce it to
0(that would send an unprotectedminOut = 0).
Troubleshooting
BagsBondingCurve_SlippageExceeded(minExpected, actualOut)— reserves moved between quote and execution. Re-quote immediately before signing and/or raise slippage.BagsBondingCurve_AlreadyMigrated/EnforcedPause— the token graduated; switch to the pool path.- Pool swap reverts with stock SDK calldata — you must include the
minHopPriceX36field (set0) and encode manually, as in section 4e. The correct router is0x8876...0904. - Router pulls nothing / transfer fails — the Permit2 route isn’t set up. Run
ensurePermit2Routefor the input token first (WETH for buys, the token for sells) and wait for the approvals to confirm. - Exact-out WETH reverts — unsupported by design. Always swap exact-in.
