Integrate Twofold pools
For aggregators, routers, solvers and anyone building on top. Twofold pools are ordinary Uniswap v4 pools whose hook is Uniswap Labs' DualPoolHook, deployed unmodified. Swaps go through the standard PoolManager and Universal Router with the hook in the pool key. The one thing that differs from a plain v4 pool is pricing: the pool's tick liquidity is empty between trades, so reading slot0 and liquidity tells you nothing. The hook prices the swap itself, and it gives you two ways to ask.
Pool discovery
Two hook deployments are live and both stay live. Read the pool list from the Registry of each, or from the API, which merges them.
| Stack | Hook | Registry | Fee / spacing |
|---|---|---|---|
| Current (2026-09) | 0xd1BcbCCa41f3bdb6b4812652959c6dF725ea2Ac0 | 0xdF1a23B1A7507Cc3B270DfA78FDD9ddA7bC36325 | 500 / 10 |
| First (2026-08) | 0x127B3f3b7769f659C5eDBfF8b4005443f19FAAc0 | 0x1b66DD14C9281A18E696dbdb40cFB5070842c0C2 | 3000 / 60, TWO/USDG 10000 / 200 |
On chain: Registry.allPoolIds() returns every listed pool id and Registry.getPool(bytes32) returns the listing, which holds the hook, both currencies, the fee, the vault on each leg and an active flag. The tick spacing is not in the listing; it is fixed per stack as above. Off chain: GET https://twofold.fi/api/pools returns every pool with its full key, symbols, decimals, current reserves and 24h volume, refreshed from an indexer that follows the hook's own events.
{ "pools": [ {
"poolId": "0x085e…a06b",
"key": { "currency0": "0xd060…9EEC", "currency1": "0x5fc5…d168",
"fee": 500, "tickSpacing": 10, "hooks": "0xd1Bc…2Ac0" },
"sym0": "NVDA", "sym1": "USDG", "dec0": 18, "dec1": 6,
"reserves": ["2006099115124073394", "463342245"],
"active": true, "vol24h": { "amount0": "…", "amount1": "…", "swaps": 0 }
} ] }
Quoting
Reserves live in the hook, not in the pool: hook.getReserves(PoolKey) returns both legs in token units, vault interest included. To price a swap:
- 01Ask the hook.
hook.getIndicativeQuote(key, zeroForOne, amountSpecified, "")is a view call. Pass a negativeamountSpecifiedfor exact input and it returns the output; pass a positive one for exact output and it returns the input, or 0 when the pool cannot deliver that much. It runs the same allocation math the swap will run, so it is the cheapest accurate price. It is indicative: put your own slippage bound on the execution. - 02Or ask the V4 Quoter.
V4Quoter.quoteExactInputSingleat 0x8Dc178eFB8111BB0973Dd9d722ebeFF267c98F94 with the hook in the key simulates the full swap, hook and all, and is exact to the wei. Use it when your router already speaks Quoter. - 03Or port the math. The hook places the reserves as concentrated positions in weighted tick buckets around the current price and swaps against them.
hook.getDistribution(poolId)returns the buckets. A reference implementation in Go, wei-exact against the Quoter on live pools, is in the KyberSwap adapter.
Two properties matter for a router. The price stays at the pool's own reserve ratio, which the range runner recenters onto the chain's reference price, so there is no standing arbitrage to harvest. And depth is what getReserves says it is, all of it deployable in one swap; a fill cannot exceed the output-side reserve.
Executing a swap
Standard v4. Call PoolManager.swap inside your own unlock callback, or use the Universal Router at 0x8876789976dEcBfCbBbe364623C63652db8C0904 with commands V4_SWAP and actions SWAP_EXACT_IN_SINGLE, SETTLE, TAKE_ALL. Pass empty hookData; the hook takes no input. Two chain-specific traps:
- 01The Universal Router on Robinhood Chain uses a six-field
ExactInputSingleParams:(poolKey, zeroForOne, amountIn, amountOutMinimum, minHopPriceX36, hookData). The canonical five-field layout reverts insideunlockCallback. SetminHopPriceX36to 0 unless you use it. - 02Pay through Permit2 (0x000000000022D473030F116dDEE9F6B43aC78BA3) for ERC-20 input with
payerIsUser = true; for native ETH,WRAP_ETHfirst and settle withpayerIsUser = false.
Depositing from a contract
Deposits mint shares proportional to reserves, so both tokens are needed at the pool's ratio. hook.previewDeposit(key, shares) tells you the amounts for a share count, hook.addLiquidity(key, shares, maxAmount0, maxAmount1, deadline) takes them, and hook.removeLiquidity(key, shares, minAmount0, minAmount1, deadline) pays both legs back. Shares are held per address in the hook (sharesOf(key, address)) and cannot be withdrawn in the block they were minted. For single-asset entry, the PoolZapper does the swap and the two-sided deposit in one transaction and keeps a per-wallet ledger; see One-click deposit.
Listings and contact
TWO is on CoinGecko and Twofold is on DefiLlama. Sources for every contract are verified on Blockscout and listed under Contracts. For an integration question, a quoter test vector, or a pool request, write to contact at twofold.fi.
This is one section of the Twofold documentation. Read all of it.