Uniswap V2
The Uniswap V2 adapter reports an account's V2 LP across any pair as the dilution-adjusted pro-rata share of both reserves, emitted as one Supplied leg per token.
Last updated
The Uniswap V2 adapter reports an account's V2 LP across any pair as the dilution-adjusted pro-rata share of both reserves, emitted as one Supplied leg per token.
The Uniswap V2 adapter reports an account's V2 LP across any pair as the dilution-adjusted pro-rata share of both reserves, emitted as one Supplied leg per token. A single immutable meta-adapter serves every pair; the active pair set lives on NAVCalculator.
Type: Meta-adapter
protocolSubId: keccak256("uniswap-v2")
Instance key: the Uniswap V2 pair address, widened to bytes32.
Per pair instance, up to two legs:
token0 share
Supplied
false
Account's pro-rata share of reserve0
token1 share
Supplied
false
Account's pro-rata share of reserve1
Legs with zero amount, unregistered assets, or assets excluded by assetFilter are dropped.
Delegated to UniswapV2PairLib.pairAmounts(pair, account). The account's LP balance is converted to underlying reserves against an effective supply that includes the protocol fee LP the pair would mint on its next mint/burn — so the reported amounts match what an LP would actually receive on burn:
The dilution term mirrors V2's internal _mintFee, the LP the pair would mint to feeTo on the next burn:
The three coefficients (numFactor, rootKCoeff, rootKLastCoeff) come from a virtual _mintFeeConstants() hook, so byte-compatible forks can override them. Uniswap V2 (and SushiSwap, which shares them) use (1, 5, 1) — i.e. (√k − √kLast) / (5·√k + √kLast), capturing 1/6 of fee growth.
It is 0 when feeTo is unset, kLast == 0, or √k <= √kLast. feeTo() and kLast() are read defensively, so forks omitting them are treated as having no protocol fee.
An absent feeTo() and a malformed one are treated differently, because this is the one defensive read whose failure direction is upward: dropping the dilution term shrinks effectiveSupply and so raises the reported share. A read that fails means a fork without protocol fees, and reporting undiluted is correct — that is why the defensive read exists. A read that succeeds but returns a word that is not a valid address means a malformed factory: the dilution is unknowable, so the pair is dropped rather than valued on a guess. Treating the second case as "no protocol fee" over-reports the position.
positionId: abi.encode(address pair) — one positionId per pair instance.
positionKind: Supplied for both legs.
There is no labels field on the position. The adapter implements positionLabels(positionId), which returns ["AMM Liquidity Pool", "sym0/sym1"] from the pair's on-chain token symbols — surfaced only by the verbose reads (getAccountPositionsVerbose / getAccountNavVerbose); the full breadcrumb is [protocolName, ...labels].
navCalculator_
NAVCalculator contract for asset-registry access
No protocol addresses are pinned — every pair is self-describing (token0, token1, factory, reserves are read from the pair itself).
Register the adapter once with addMetaBalanceAdapter, then enable each pair with addMetaInstances. The instance coordinate is the Uniswap V2 pair address widened to bytes32. The adapter holds no instance state — it reads its active set from NAV_CALCULATOR.getMetaInstances(address(this)).
Last updated
balance = pair.balanceOf(account)
totalSupply = pair.totalSupply()
(r0, r1) = pair.getReserves()
effectiveSupply = totalSupply + pendingProtocolMint // V2 _mintFee dilution
amount0 = balance × r0 / effectiveSupply
amount1 = balance × r1 / effectiveSupplypendingProtocolMint = totalSupply × (√k − √kLast) × numFactor / (rootKCoeff·√k + rootKLastCoeff·√kLast)constructor(
address navCalculator_
)