UNI404: an ERC-404 with Uniswap-style pool NFTs
01Abstract
UNI404 is the first ERC-404 token to launch directly on Uniswap V4 with a custom Hook attached to its pool. ERC-404 is a non-standard pattern that pairs an ERC-20 fungible balance with an ERC-721 NFT layer on the same contract. Each whole token (1.0 UNI404, 18 decimals) corresponds to exactly one Pool NFT in the same wallet. Whole-unit boundaries are the trigger: cross one upward, an NFT is minted; cross one downward, an NFT is burned.
The pool sits on Uniswap V4 paired with WETH. A companion UNI404Hook contract is attached at pool initialization. The protocol charges 0% on transfers, 0% on swaps. Instead, the hook captures pool yield from V4 LP fees and redistributes it to UNI404 holders, weighted by NFT count and rarity. Hold a Legendary, earn 50x what a Common holder earns per share. UNI404's NFTs are deterministic on-chain art - the SVG, traits and color all derived from the tokenId via keccak256 and rendered at view time. 10,000 supply, hard-capped, no mint after deploy.
02Design
The contract in three pillars
The whole design fits in three sentences before we get to the gory bits.
- Fungible layer. 10,000 UNI404 with 18 decimals. Trades through a Uniswap V4 pool with our custom Hook attached. 0% protocol fee on transfers and swaps. The hook is a yield redistributor: it captures V4 LP fees and pays them out to UNI404 holders weighted by their NFT count and rarity.
- NFT layer. One NFT per whole token. Hold 3.7 UNI404, you also hold 3 Pool NFTs in the same wallet. Each NFT is a unique on-chain SVG of an AMM-style pool: token pair, fee tier, rarity, color. Drawn from the contract bytecode, never off-chain.
- Boundary math. Cross a whole number, the NFT moves. Spend down past a whole-unit boundary, the lowest-id NFT in your wallet burns. Cross upward, a fresh NFT mints to you (or a previously-burned id is recycled). LP and vault contracts can be whitelisted so the LP doesn't accumulate worthless NFTs.
Two surfaces, one balance
Internally the contract has standard ERC-20 storage (balance per address, allowance per pair) and standard ERC-721 storage (ownerOf per tokenId, owned-tokens list per address). The two stay synced through a single rule applied on every transfer:
burn = max(0, floor(balanceBefore / 1e18) - floor(balanceAfter / 1e18)) mint = max(0, floor(balanceAfter / 1e18) - floor(balanceBefore / 1e18))
burn pops the youngest NFT in the sender's wallet and pushes its tokenId onto a recycling stack. mint pops a tokenId from the recycling stack first (so id-space stays compact), or increments a counter for fresh ones, then assigns it to the receiver.
Whitelist for LPs
Liquidity pools, vaults and bridges hold ERC-20 balances they'd never use the NFT side of. The owner can mark such addresses as whitelisted: they hold the ERC-20 balance, but boundary crossings don't mint or burn NFTs for them. Without this, every Uniswap LP pool would slowly accumulate dead Pool NFTs that nobody wanted.
transferFrom is overloaded
One quirk worth knowing: the third argument of transferFrom(from, to, amountOrId) is interpreted as a tokenId (ERC-721 path) when it falls in [1, TOTAL_NFTS] AND that id is owned by from. Otherwise it's treated as an ERC-20 amount. This is the standard ERC-404 trick, kept for compatibility with bridge and aggregator code. approve(spender, amount) follows the same rule.
03Contract surface
ERC-20 reads
name(),symbol(),decimals()- standard.totalSupply()- 10,000 * 10^18, immutable.balanceOf(addr)- ERC-20 balance, in wei.allowance(owner, spender)- ERC-20 allowance.
ERC-721 reads
ownerOf(tokenId)- reverts if the id was never minted (or has been burned).balanceOfNFTs(addr)- count of NFTs owned. Note this is a separate function from the ERC-20 balanceOf to avoid the standards collision.tokensOfOwner(addr)- full list of tokenIds.tokensOfOwnerPaged(addr, offset, limit)- paginated read for large holders.tokenURI(tokenId)- base64-encoded JSON with attributes and an inline SVG image.
User actions
transfer(to, amount)- standard ERC-20 transfer with auto-mint / auto-burn at whole-unit boundaries.transferFrom(from, to, amountOrId)- ERC-721 path if amountOrId is a valid tokenId offrom, ERC-20 path otherwise.safeTransferFrom(from, to, tokenId)- ERC-721 only, with onERC721Received check.approve(spender, amountOrId)- same overload trick.setApprovalForAll(operator, bool)- standard ERC-721.
Owner actions
setWhitelist(addr, bool)- flag an address as ERC-20-only (skip NFT side effects). Used to mark the V4 Position Manager and any approved bridges/vaults.transferOwnership(addr)/renounceOwnership()- the usual.
That's the whole owner surface. There is no pause, no mint, no fee setter. Yield distribution lives in the separate UNI404Hook contract and is also non-tunable - the rarity weights are hard-coded.
Yield distribution (the V4 hook)
The V4 Hook is what makes UNI404 different from a vanilla ERC-404. Pool fees on Uniswap V4 normally go to liquidity providers; our hook intercepts a portion and accumulates it for UNI404 NFT holders. The math is straightforward:
- Each NFT carries a weight based on its rarity: Common 1, Uncommon 3, Rare 8, Epic 20, Legendary 50.
- A user's
shares= sum of weights across every UNI404 they hold. - The hook tracks a global
yieldIndexthat increments every time pool fees flow in, scaled by the total weighted shares in circulation. - A user's claimable =
shares * (yieldIndex - userLastIndex). - Calling
claim()sends the user their accrued yield in WETH and updates theirlastIndex.
When NFTs move (transfers crossing whole-unit boundaries mint/burn NFTs), the hook updates both wallets' shares atomically. Selling all your UNI404 zeroes your shares; you keep whatever you accrued before the sell, claimable forever.
04On-chain art
Every Pool NFT's image is generated by the contract at view time. There's no pinned IPFS hash, no off-chain metadata server, no team key signing the image. The recipe:
- Hash the tokenId:
h = keccak256("UNI404", tokenId). - Pick a token symbol from the 16-token shipping list using
h[0]. The pair is alwaysTOKEN/WETH. - Pick a fee tier (0.01% / 0.05% / 0.10% / 0.30% / 1.00%) from
h[2], weighted to mirror Uniswap's real V3+V4 tier distribution. - Pick a rarity (Common / Uncommon / Rare / Epic / Legendary) from
h[3], weighted 50 / 25 / 15 / 8 / 2. - Pick a pool type (V2 / V3 / V4 / Stable / Exotic) from
h[4], weighted 25 / 30 / 20 / 15 / 10. - Derive an HSL hue from
h[5]for the non-WETH token blob; WETH always renders pink-violet. - Derive a TVL flavor number ($50k - $500M) from
h[6:8]for display only, plus its 5-bucket categorical bracket (Bag / Steady / Deep / Whale / Beast). - Compose an SVG with the pool-type header pill, the pair label, two colored token blobs, an AMM curve squiggle, three bottom pills (fee / TVL bracket / rarity), and the tokenId footer.
- Wrap the SVG in JSON with attributes, base64-encode the lot, return as
data:application/json;base64,....
The recipe is deterministic. The same tokenId always produces the same NFT. Re-mints (burned-id recycling) preserve the id, so the art also stays the same.
05Risks
// honest risk disclosure
UNI404 is experimental. ERC-404 is a non-standard pattern that breaks edge cases of both ERC-20 and ERC-721 wallets if they don't know about it. Read this list before buying.
- Wallet display can lie. A wallet that only knows ERC-20 will show your fungible balance. A wallet that only knows ERC-721 will show your NFTs. Neither will show both. Etherscan does, kind of.
- Bridges may silently drop the NFT side. Most cross-chain bridges only forward the ERC-20 layer. The NFT layer doesn't survive the trip and you arrive on the other side with the fungible balance and no NFTs.
- Burned tokenIds get recycled. If you're attached to a specific id, transferring out and back may give you a different one. The contract prefers recycled ids over fresh ones.
- LPs need to be whitelisted. Until the deployer marks the LP address as whitelisted, the LP will accumulate Pool NFTs on every buy and burn them on every sell. This wastes some gas but doesn't break the math.
- Smart contract bugs. ERC-404 is new and not deeply audited. The contract is short, but read it before depositing more than you can afford to lose.
06Why Uniswap?
Of all the things you could build a 10,000-supply ERC-404 around, why picture Uniswap-style pools? Three reasons.
- Pools have natural variation. Pair, fee tier, depth, type. That's a 4-trait NFT before you make anything up. Add rarity for the cherry on top and you have a five-trait card without forcing it.
- The brand fits the math. Uniswap shipped the AMM that defined the genre and the V4 hook that made it programmable. ERC-404 is, in spirit, a hook on standard ERC-20 transfers - mint or burn an NFT every time the fungible balance crosses a whole-unit line.
- The art writes itself. Two tokens, an AMM curve, a fee, a frame color for rarity. The SVG is short. The result is recognizable from a thumbnail. No need to commission art or run an AI through 10,000 prompts.
07FAQ
Why ERC-404?
Because tying NFTs to whole-unit balances is a real-feeling mechanic. You buy 1.5 tokens, you get 1 NFT. Buy half a token more, the second NFT shows up. It makes the fungible balance feel meaningful.
Why pools as the NFT subject?
Pools have natural variation (pair, fee, depth, age) and the brand fits the Uniswap-themed contract. No need to invent traits or commission art.
Can I move just one NFT without moving any tokens?
No. Transferring a tokenId always moves 1 whole UNI404 of fungible balance with it. The two layers are tied at the boundary; you can't desync them.
What happens to the NFT layer when I list on Uniswap?
Selling tokens to the LP transfers the fungible balance to the pool address (whitelisted), which doesn't trigger an NFT mint there. On your side, your fungible balance drops below a whole-unit boundary, your NFT burns. The opposite happens when you buy back.
Is the LP a single-sided pool?
No. UNI404 trades against WETH on a single Uniswap V4 pool with our companion UNI404Hook attached. The Position Manager (the V4 contract holding LP positions) is whitelisted so it doesn't accumulate NFTs. The pool can be created via standard V4 tools - Universal Router 2 routes through it automatically.
Is the art random?
Deterministic from tokenId. Not random. You can predict any NFT's traits just from its id by running the recipe in section 4.
Will there be a v2?
No plans. UNI404 is one contract, fully on-chain, ownership renounceable. v2 would be a different contract.


