So you want to tokenize a fund
and trade it on Uniswap.
Say you’re a bank. Clients want a tokenized money-market fund they can actually trade. Compliance needs today’s rules to keep working on-chain. Usually you’re told to pick one.
We built it. Two prompts for the contracts. ~200 lines of policy. 13 tests that try to break it. Every number below is a live read from the chain, not a screenshot.
The architecture, live
pick a scenario — the pulse traces the real call flowThe investor places an order. The custodian signs from the segregated custody account — the client never holds a key.
One prompt → the security token
OpenZeppelinOne sentence in. A complete permissioned token out.
Generate a permissioned RWA token called APACMoneyMarket, symbol APMM. Transfers have to be restricted to an allowlist. I need to freeze an individual holder's balance, mint, burn, and pause the whole asset. Use role-based access control rather than a single owner, include ERC-2612 permit, and pre-mint 1,000,000 to the deployer. MIT licence, security contact security@openzeppelin.com.
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.7.0 and Community Contracts commit b0ddd27
pragma solidity ^0.8.27;
import {ERC20Freezable} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Freezable.sol";
import {ERC20Restricted} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Restricted.sol";One prompt → the market's gatekeeper
OpenZeppelinThe hook is the contract the pool asks before every trade — and which can refuse. Generated on OpenZeppelin's audited BaseHook.
Scaffold a Uniswap v4 hook called CompliantSwapHook on top of BaseHook. It has to gate pool initialisation, gate liquidity adds, gate swaps, and observe swaps after they settle — so beforeInitialize, beforeAddLiquidity, beforeSwap and afterSwap, and nothing else.
The one part you must own: the rule
OpenZeppelinNo generator writes your policy. ~200 lines, reading the issuer's ERC-3643 registry — the standard behind ~$28bn of tokenized assets.
// The whole decision, cheapest gate first — asked before every trade.
if (_isPaused(tokenAddress)) return NONE; // issuer paused the asset
if (_isFrozen(tokenAddress, account)) return NONE; // agent froze this wallet
if (!_isVerified(policy.identityRegistry, account)) return NONE; // passport not verified
if (policy.enforceCountry && !_countryAllowed[token][country])
return NONE; // venue blocked the jurisdiction
granted = SWAP_ALLOWED;
if (_holdsClaim(account, policy.liquidityClaimTopic))| CLIENT-A · Sakura (JP, professional) | swapLP | KYC + professional claim |
| CLIENT-C · Harbour (HK, retail) | swapLP | KYC only — no professional claim |
| CLIENT-D · Prospect (KYC pending) | swapLP | No identity registered |
One function, three answers — all from the issuer’s attestations.
Prove it refuses
OpenZeppelinA control nobody tried to break is a claim. These tests attack every gate.
- ✓An unverified wallet can't reach the asset by any route.
- ✓KYC-only investors can trade — but can't provide liquidity.
- ✓Freeze one client; the others keep trading.
Now watch it trade
Same deployment, live. Press Present demo there — a trade settles, a wallet freezes, the pool refuses on its own.
Open the live console →