tollbooth Back to the site

Tollbooth docs

Tollbooth is a token exchange on Solana. Every pool auctions the right to set its swap fee. The winner, called the operator, picks a fee between a floor and a cap that never change. The operator keeps whatever it charges above the floor and pays rent, every slot, to the pool's liquidity providers.

The design follows the am-AMM paper by Adams, Moallemi, Reynolds and Robinson. The point is that whoever can price a pool best, usually an arbitrageur, competes for the booth in rent, and that rent goes to LPs instead of being lost to them.

WhoWhat they doWhat they get
TradersSwap like on any constant-product poolA fee that can't go above the pool's cap
OperatorsBid rent in SOL to run a pool's booth, then set its feeThe toll: the fee above the floor on every trade
Liquidity providersDeposit both tokens of a poolThe floor fee on every trade, plus 90% of the rent
$TOLL holdersHold the protocol coin10% of all rent buys $TOLL and burns it

One trade, step by step

Say you swap 10 SOL for $ROAD in a pool holding 800 SOL, the floor is 0.30% and the operator has set the fee to 1.20%.

  1. The booth reads the fee. It is 1.20%, the fee the operator set in an earlier slot. A fee change never lands in the same slot as the trade it applies to, so nobody can raise it in front of you.
  2. The floor stays with LPs. 0.30% of the input, 0.03 SOL, stays in the pool like any AMM fee. Everyone pays it, the operator included.
  3. The toll goes to the operator. The other 0.90%, 0.09 SOL, moves to the operator's toll account for this pool.
  4. The curve moves the price. The remaining 9.88 SOL goes through x ยท y = k.
  5. Tokens move once. You get your $ROAD out, or, if your minimum isn't met, the whole transaction fails and nothing moves.

When nobody runs the booth, the pool is quiet: it charges only the floor and all of it stays with LPs.

Floor and cap

Each pool fixes a fee floor and a fee cap when it is created. The cap can be at most 10%. The operator can set any fee in between, and a change applies from the next slot. Nothing about a pool's tokens, floor, cap or bid delay can change after creation, not even by the creator.

A fee outside the band is refused by the program. A trader can always read the cap on the pool account and knows the worst case before trading.

The booth auction

Liquidity providers

LPs earn two things: the floor fee on every trade, which stays in the pool, and 90% of the booth's rent, which streams to them in SOL by share every slot. Rent is claimable at any time and is paid out automatically when you withdraw.

On a quiet day a fixed-fee pool earns almost nothing. A Tollbooth pool still earns rent, because the operator pays for the booth whether or not volume shows up.

A position can be locked until a date. Locks can only be extended; nobody can shorten one, the owner included. Coin creators use this to show their liquidity can't be pulled.

$TOLL buyback and burn

The other 10% of rent goes to the treasury, which can do exactly one thing: buy $TOLL through the TOLL / SOL Tollbooth pool and burn it in the same instruction. Anyone can trigger a buyback. Each one:

Launching a coin

The launch form creates a coin and its Tollbooth pool in one transaction: it creates a Token-2022 mint with the name and ticker on the mint itself, mints the whole supply, puts the supply and your SOL into a new pool, and drops the mint authority so nobody can ever mint more.

For the first 150 slots the fee sits at the pool's cap and none of it is a toll: it all goes to LPs, so launch snipers pay the pool. After that the booth opens for bids.

Pools refuse Token-2022 mints with extensions that could take tokens from the pool or freeze it, such as transfer fees, transfer hooks, permanent delegates, default-frozen accounts, non-transferable tokens and confidential transfers.

Program and SDK

The program is written with Anchor. On devnet its address is CUVu4X8eEZ1GHQCvw8ZNQcUCeHfRYiBY5hKDP9bZ3JTb (Explorer). Every pool uses the same account layout, so a router integrates once.

Accounts

AccountSeeds
Pool["pool", mint_a, mint_b], mints sorted by bytes
Vault["vault", pool, mint]
LP position["position", pool, owner]
Operator tolls["toll", pool, owner, mint]
Config and treasury["config"]

Instructions

create_pool, swap, deposit, withdraw, claim_rent, extend_lock, bid, top_up, set_fee, settle, collect_tolls, sweep, buyback.

JavaScript SDK

The SDK is a single ES module that depends only on @solana/web3.js. It builds every instruction and mirrors the program's math in BigInt, so a quote matches the program to the lamport, pending rent included.

import { Connection } from "@solana/web3.js";
import * as tb from "./sdk/tollbooth.js";

const conn = new Connection("https://api.devnet.solana.com");
const pools = await tb.listPools(conn);
const pool = await tb.loadPool(conn, pools[0].address);
const slot = BigInt(await conn.getSlot());

// what a 1 SOL swap pays and gets, exactly as the program computes it
const q = tb.predictSwap(pool.state, slot, 1_000_000_000n, true);
// q.fee, q.lpFee, q.toll, q.out, q.phase ("launch" | "quiet" | "booth")

// bid for the booth: rent per slot, deposit, and the fee you'll charge
const ix = tb.bidIx(pool, wallet, { rent: 1_000_000n, deposit: 2_000_000_000n, feeBps: 120 });

Status and risks

Tollbooth runs on Solana devnet only, with test SOL that has no value. The program has not been audited. As on any AMM, LPs carry price risk, and an operator can lose money if its tolls don't cover its rent. Nothing here is financial advice.