Skip to main content
In this guide, you’ll trade Bags tokens on Robinhood Chain. Which venue you use depends on whether the token has graduated:
  • 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.
Always determine the phase first, then route accordingly.

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 on migrated. The simplest source is BagsLens (see Read State):
A buy that completes the bonding curve graduates the token in the same transaction. After any curve buy, re-read migrated (or check for the Migrated event in the receipt) before the next trade so you don’t send a curve call to a paused curve.

2. Slippage Helper

Every trade below derives minOut 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 against state.curve.

Buy (ETH to token)

Quote with quoteBuy, 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-20 transferFrom, so you must approve the curve first (a one-time maxUint256 approval is convenient). Then quote with quoteSell and call sell:
curve-sell.ts
Proceeds are paid as native ETH. sellFor(recipient, tokensIn, minQuoteOut) delivers proceeds to another address.
BagsToken supports EIP-2612 permit, so instead of a separate approve transaction you can have the seller sign a permit off-chain and submit the allowance gaslessly (or bundle permit + sell via multicall infrastructure) for a one-transaction sell UX.

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.
This router is a fork. Its v4 swap struct has an extra uint256 minHopPriceX36 field (set it to 0), so stock Uniswap SDK calldata reverts — you must encode the calldata manually as shown below. Only the router at 0x8876...0904 is correct.
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 V4Quoter already 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 as abi/periphery.ts:
abi/periphery.ts

4b. Pool key and swap direction

The pool is identified by a v4 PoolKey. 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: command V4_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/quoteSell return fee-adjusted amounts (tokensOut / quoteToSeller are what you actually receive). In the pool, V4Quoter output already includes the hook fee.
  • quoteBuy returns refundQuote > 0 when a buy would cross graduation — only grossUsed is 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 unprotected minOut = 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 minHopPriceX36 field (set 0) and encode manually, as in section 4e. The correct router is 0x8876...0904.
  • Router pulls nothing / transfer fails — the Permit2 route isn’t set up. Run ensurePermit2Route for the input token first (WETH for buys, the token for sells) and wait for the approvals to confirm.
  • Exact-out WETH reverts (BagsV4Hook_ExactOutputWETHSpecifiedUnsupported) — unsupported by design. Always swap exact-in.
For the full function, event, and error catalog, see the Contracts Reference.