5. Spoon-Fed Uniswap V2: createPair — Code level walkthrough
createPair
5. Spoon-Fed Uniswap V2: createPair — Code level walkthrough

createPair

Alright, quick recap.
A Pair contract is deployed by the Factory. And like we briefly tasted during the swap deep-dive: the pair address isn’t some “random gift from the blockchain gods.”
Uniswap V2 makes the Pair address deterministic using the two token addresses.
Meaning:
If you know
tokenAandtokenB, you can predict the Pair contract address without asking anyone.
Let’s open up the hood and see exactly how.
0) Warm-up: the interface
v2-core/IUniswapV2Factory.sol
function createPair(address tokenA, address tokenB) external returns
(address pair);
The vibe is obvious:
“Here are two token addresses. Make me a pool.”
Factory:
“Got you. Here’s the pair address.”

…Except the factory is also doing something sneaky and elegant: it guarantees the address ahead of time.
1) The real code: UniswapV2Factory.createPair
v2-core/UniswapV2Factory.sol
function createPair(address tokenA, address tokenB) external returns (address pair) {
// 1.
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS');
// 2.
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
// 3.
getPair[token0][token1] = pair;
getPair[token1][token0] = pair;
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
Let’s slice it into 3 phases.
Phase 1 — Sanity checks (a.k.a. “don’t be weird”)
- Tokens must be different
tokenA != tokenB - Sort them so ordering is consistent
token0 < token1always - No zero address
- Pair must not already exist
That sorting part is not “style.” It’s a critical invariant.
If you don’t sort:
WETH/USDTandUSDT/WETHwould be treated as different pairs- which would be… chaos with extra steps.
Also: yes, they store the pair address in a mapping, which feels expensive — but it makes lookup cheap and standard.
Phase 2 — Deploy the Pair with CREATE2
Here’s the core idea:
CREATE vs CREATE2
- CREATE: address depends on deployer + nonce (good luck guessing a factory’s nonce at some historical moment)
- CREATE2: address depends on (deployer address, salt, init code hash)
So Uniswap uses CREATE2 so that:
knowing
token0andtoken1is enough to compute the pair address.
Step 2.1 — grab init code (creation code)
bytes memory bytecode = type(UniswapV2Pair).creationCode;
That returns the init code used at deployment time.
Important mental model:
- creation code / init code runs only once, during deployment
- it returns the runtime code
- runtime code is what ends up stored on-chain as “the contract”
So CREATE2 executes init code, then stores whatever it returns.
Step 2.2 — compute the salt
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
Salt rule is simple: pack the two sorted token addresses and hash them.
So again:
token addresses → salt → predictable address
Step 2.3 — raw create2 call (assembly)
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
create2(value, ptr, size, salt):
value: ETH to send on deployment (0)ptr: memory pointer to init codesize: init code lengthsalt: our deterministic ingredient
Now the “ptr/size” part looks scary because Solidity memory is a bit… “why are you like this?”
Why add(bytecode, 32)?
Dynamic bytes in memory is stored like:
- first 32 bytes: length
- then: actual bytes data
So:
bytecodepoints to the length slotadd(bytecode, 32)points to the real code bytes
Why mload(bytecode)?
That loads the first 32 bytes = the length.
So the assembly is basically:
“Take this init code from memory, run it, and deploy the returned runtime code — but pick the address deterministically using
salt.”
That’s it.
All the mysticism is just memory layout.
Phase 3 — Initialize the Pair
After deploying, Factory calls:
IUniswapV2Pair(pair).initialize(token0, token1);
Inside the Pair:
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN');
token0 = _token0;
token1 = _token1;
}
It just stores which tokens this pair is for.
“Why not constructor?”
Because Uniswap wants the init code to be identical for all pairs.
If token addresses were constructor args:
- each pair’s init code would differ (args get baked into creation)
- which means
init code hashdiffers - which changes the CREATE2 address formula inputs
- and your “predictable address from token addresses” property becomes messier / less clean.
So they keep init code constant and pass token addresses via an initialize() call instead.
Clean, deterministic, reusable.
Phase 4 — Save and emit (done)
getPair[token0][token1] = pair;
getPair[token1][token0] = pair;
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
They store it both ways for convenience, push into array, and emit event.
The real takeaway
Uniswap V2 Pair addresses are deterministic because:
- tokens are sorted
- salt is hash(token0, token1)
- init code is constant
- deployer (factory) is fixed
- deployed via CREATE2
So:
tokenA + tokenB → (sort) → salt → predictable CREATE2 address
And yeah: this “predict the pool address before it exists” trick is one of those patterns you’ll keep seeing all the way through v4 (just with different flavors).
If you want, next I can write the “compute pair address off-chain” section in the same vibe (the famous keccak256(0xff ++ factory ++ salt ++ init_code_hash) formula) — that’s the final piece that makes the determinism feel real.
메타데이터
- post_id
- 6bf0a09ef5c6
- slug
- 5-spoon-fed-uniswap-v2-createpair-code-level-walkthrough-6bf0a09ef5c6
- url
- https://medium.com/@dreamboys0107/5-spoon-fed-uniswap-v2-createpair-code-level-walkthrough-6bf0a09ef5c6
- canonical_url
- https://medium.com/@dreamboys0107/5-spoon-fed-uniswap-v2-createpair-code-level-walkthrough-6bf0a09ef5c6
- author_url
- https://medium.com/@dreamboys0107
- status
- ok
- fetched_at
- 2026-08-20 18:19:56