DEV Community

Cover image for Before the Swaps: A Map of Uniswap V2
Adi
Adi

Posted on

Before the Swaps: A Map of Uniswap V2

Recently I did a blog post where we did a quick breakdown of what AMMs are, Uniswap specifically, and built a basic intuition of how things work under the hood. It was initially just supposed to be a quick intro, but it eventually turned out more technical. We covered the Constant Product formula, how it came to be, and how it's been the cornerstone of almost the entire DeFi space (the actual decentralized space, not the protocols that keep showing up with a multisig and claiming to be DeFi, until somehow they get compromised and begin crying that the protocol itself is decentralized and we aren't at fault — it was the keys. Just shut up, if something can put that big of a dent in your core logic then stop calling yourself a DeFi protocol. Yeah, it's DeFi until it isn't. It's unbelievable how there have been so many such exploits in the past few years, but that doesn't mean it prevents new protocols, following similar architecture, from not coming up, not getting funded and somehow not growing to a quintillion dollars in TVL. Sorry I got sidetracked, but it is what it is).

So this is the second post in the series. But before we get into the actual flows — how swaps work, how liquidity gets added, all of that — it's worth taking five minutes to understand how Uniswap V2 is laid out. Not the contracts in detail, we'll get to those one by one in the posts that follow. Just the structure. Think of this as the blueprint of a building you've never been in. You don't need to know where every room is, just where the front door is and which staircase goes where.

If some of the terms or concepts here feel unfamiliar, don't worry about it. By the time we're done with this series, everything here will have clicked into place naturally. This post is just context — a map you can come back to if you ever want to orient yourself while following along.


Two repos, one protocol

The first thing that trips people up when they open the Uniswap V2 GitHub is that there are two repositories: v2-core and v2-periphery. It's tempting to assume this is just an organisational choice (at least thats what I initially thought it was) i.e. someone decided to split the code into two folders and gave them dramatic names. It's actually a deliberate architectural decision, and understanding why it's split that way makes the whole thing easier to reason about.

Core holds the contracts that actually hold your money. That's why its the core. Nothing fancy, nothing extraneous. Because these contracts custody real funds, they are intentionally minimal and completely immutable after deployment. There's no admin key, no upgrade mechanism, no way to patch them after the fact. If a bug were found in Core, the only option would be to deploy a new version entirely. Sounds scary at first, until you flip it around: it also means nobody can change these contracts to do something unexpected with your funds. Ever.

Periphery, well, as the name suggests, lies on the periphery of the core logic. It holds everything that makes Core usable. The routing logic, the slippage checks, the optimal amount calculations, the multi-hop path resolution, all of that lives here. And we will get to it. And crucially, Periphery can be replaced. If a better router gets deployed tomorrow, users can switch to it without the underlying pools changing at all. The pools don't care who calls them.

The split is a trust boundary. Core is what you have to trust. Periphery is just convenience.


What's actually in each repo

Core has three contracts that matter: UniswapV2Factory, UniswapV2Pair, and UniswapV2ERC20.

The Factory is the registry. It deploys new pool contracts and keeps a record of every pool that exists - a mapping of token pair to pool address. When you want to find or create a pool for two tokens, the factory is where you go. One interesting detail: it uses a deterministic deployment method (CREATE2) which means any pool's address can be computed off-chain just from the factory address and the two token addresses, without ever querying the chain. Internally, CREATE2 is just a hash of multiple parameters, one of them being the salt. That means the same parameters generate the same result (the contract address, in this case) every time. If you want to deploy again, just change the salt and you'll get a new address. I'll try to write a separate post on deterministic deployment: the types, the when and why, and when to avoid it altogether.

The Pair is the pool itself. Each deployed pair contract holds exactly two tokens and handles everything: minting LP tokens when liquidity is added, burning them when it's removed, executing swaps, flash loans, all of it. It also stores the price accumulators that make Uniswap's built-in TWAP oracle possible. One thing worth noting: UniswapV2Pair is itself an ERC-20. The LP tokens are the pair contract's own token. Holding LP tokens for a pool means literally holding shares of that contract.

UniswapV2ERC20 is just the base ERC-20 that Pair inherits from, standard token functionality plus EIP-2612 permit support, which lets you approve and act in a single transaction rather than two.

Core also has a couple of utility libraries — Math.sol for the integer square root used in initial LP token calculations, and UQ112x112.sol for the fixed-point arithmetic used in price accumulation. Nothing you need to think about much right now.

Periphery is anchored by UniswapV2Router02 — the contract users actually interact with. Every function you call when using a DEX frontend, or any other integrator, sits here. It handles all four operations, wraps and unwraps ETH for ETH-involving swaps, enforces deadlines, and calculates the optimal amounts before anything touches Core. There's also a Router01 in the repo, but it's effectively deprecated. Router02 replaced it with better support for a broader range of token types.

Supporting the Router is UniswapV2Library, a stateless utility library that handles all the computational work: looking up pair addresses, fetching reserves, calculating swap outputs, walking multi-hop paths. None of this needs to touch state, so it's compiled inline rather than deployed as a standalone contract. And there's UniswapV2OracleLibrary, which provides helpers for building TWAP oracles on top of the pair's price accumulators — if you've ever wondered how protocols get manipulation-resistant on-chain price feeds, this is part of that story.


The helpers that don't live in either repo

Two external utility packages get pulled into V2 that are worth knowing about, mostly because you'll see them imported in the contracts as you follow along.

The first is TransferHelper from @uniswap/solidity-lib. The problem it solves is a real one: ERC-20's transfer() is supposed to return a boolean indicating success, but a surprising number of tokens, USDT being the most notorious, don't return anything at all. A naive require(token.transfer(...)) would just revert on these tokens even when the transfer worked fine. TransferHelper wraps transfers in a low-level call that handles both cases gracefully. It's a small thing, but without it the Router would silently fail on a large class of tokens that people actually use.

The second is FixedPoint from @uniswap/lib, which provides fixed-point number types for the oracle library. Solidity has no native floating-point, so cumulative price values need a way to accumulate fractional ratios over time without losing precision. That's what this handles.


The call flow in one line

For every operation, the path is:

User → Router → Pair → User

The Factory sits to the side, consulted by the Router to find or deploy pair addresses when needed, but not in the critical path of every transaction. The libraries are silent infrastructure, compiled into the Router and never called directly.

That's the map. Keep it somewhere in the back of your head as we get into the flows. Whenever you see the Router calling something on the Pair, or the Pair checking something via the Library, you'll know exactly where you are.


Here are all the contracts and repos referenced in this post, if you want to poke around yourself, although you definitely don't need to do that just yet.

Next up: adding liquidity to a pool. This is where the constant product formula stops being theory and starts getting its hands dirty.
In case you missed the intro: x * y = k, and Other Things I Should've Learned Sooner

Top comments (0)