Uniswap v4 把 AMM 从固定池子合约推进到可扩展的池子执行框架。v4 通过单例 PoolManager 管理所有池子,并允许每个池子挂载一个 hook,在初始化、swap、增减流动性等生命周期节点插入自定义逻辑。
Permissioned Pools 正是基于 hook 能力构建的。Uniswap Labs 在 7 月 23 日联合Securitize和Superstate发布了 Permissioned Pools,旨在让受监管资产、RWA(如股票代币、基金代币)等资产可以在 AMM 上交易,同时把白名单和权限检查放到链上执行,而不是只依赖前端或链下合规判断。
一、Permissioned Pools 机制解析
传统 AMM Pool 默认是 permissionless 的,任何地址只要持有代币、授权代币,就可以进行交易或提供流动性。这一设计难以满足RWA资产的合规要求,而如果直接把 permissioned token 放入Uniswap v4 AMM Pool,未获得授权的地址可能通过内部 claim 或组合路径绕过原 token 白名单的流动性入口。为此,Uniswap 的 Permissioned Pools 采用了 adapter + hook 的设计:池子里交易的不是原始 permissioned token,而是由 PermissionsAdapter 管理的虚拟表示。
1.1 Permissioned Pools 架构
Permissioned Pools 主要由以下4个核心组件:
(1) PermissionsAdapter
核心包装合约。它持有底层 permissioned token,并铸造池子内部使用的 virtual token。用户通常不直接管理 virtual token,路由器和 position manager 会在交互中自动处理包装与解包(unwrap)。
(2) IAllowlistChecker
发行方自行实现的白名单检查合约。它返回某个地址对某个 token 的权限,例如是否允许 swap、是否允许提供流动性。
(3) PermissionedHooks
挂在 v4 pool 上的 hook。它在 beforeInitialize、beforeSwap、beforeAddLiquidity 等阶段执行检查:池子是否使用已验证 adapter、swapper 是否有 SWAP_ALLOWED、LP 是否有 LIQUIDITY_ALLOWED。
(4) PermissionedPositionManager / Universal Router
负责用户实际交互。permissioned swap routing 需要通过 Universal Router,而 LP position NFT 也不可转让,以避免已授权地址把 LP 仓位转给未授权地址。这一限制能够对获得做市收益或间接持有敞口的场景进行监管。
1.2 交易流程如何被限制
以 swap 为例,路径基本为:
(1) 用户持有底层 permissioned token;
(2) 用户通过 Universal Router 发起 swap;
(3) Router 检查并把底层 token 转入 PermissionsAdapter;
(4) Adapter 创建池子内部使用的 virtual token;
(5) v4 PoolManager 执行 swap;
(6) PermissionedHooks.beforeSwap 检查用户是否有 SWAP_ALLOWED;
(7) swap 完成后,virtual token 被转换回底层 token。
二、代码案例分析
2.1 Allowlist Checker
下面是一个简化版 allowlist checker,实际生产环境应使用多签、更细致的角色权限管理、可审计的 KYC provider 更新流程。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {
IAllowlistChecker
} from "@uniswap/v4-periphery/src/hooks/permissionedPools/interfaces/IAllowlistChecker.sol";
import {
PermissionFlag,
PermissionFlags
} from "@uniswap/v4-periphery/src/hooks/permissionedPools/libraries/PermissionFlags.sol";
contract IssuerAllowlistChecker is IAllowlistChecker, ERC165, Ownable {
mapping(address account => PermissionFlag permission) private _permissions;
constructor(address initialOwner) Ownable(initialOwner) {}
function checkAllowlist(
address account,
address /* tokenAddress */
) external view returns (PermissionFlag) {
return _permissions[account];
}
function setSwapAllowed(address account, bool allowed) external onlyOwner {
PermissionFlag current = _permissions[account];
if (allowed) {
_permissions[account] = current | PermissionFlags.SWAP_ALLOWED;
} else {
_permissions[account] = current & ~PermissionFlags.SWAP_ALLOWED;
}
}
function setLiquidityAllowed(address account, bool allowed) external onlyOwner {
PermissionFlag current = _permissions[account];
if (allowed) {
_permissions[account] = current | PermissionFlags.LIQUIDITY_ALLOWED;
} else {
_permissions[account] = current & ~PermissionFlags.LIQUIDITY_ALLOWED;
}
}
function setFullPermission(address account) external onlyOwner {
_permissions[account] =
PermissionFlags.SWAP_ALLOWED |
PermissionFlags.LIQUIDITY_ALLOWED;
}
function clearPermission(address account) external onlyOwner {
delete _permissions[account];
}
function supportsInterface(
bytes4 interfaceId
) public view override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IAllowlistChecker).interfaceId ||
super.supportsInterface(interfaceId);
}
}
该合约只负责检查某地址对某类资产有什么权限,真正的执行操作发生在adapter 和 hook 中。
2.2 部署 adapter 并创建 pool
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {
IAllowlistChecker
} from "@uniswap/v4-periphery/src/hooks/permissionedPools/interfaces/IAllowlistChecker.sol";
interface IPermissionsAdapterFactory {
function createPermissionsAdapter(
IERC20 token,
address owner,
IAllowlistChecker checker
) external returns (address adapter);
function verifyPermissionsAdapter(address adapter) external;
}
interface IPermissionsAdapter {
function depositForVerification(uint256 amount) external;
function updateAllowedWrapper(address wrapper, bool allowed) external;
function updateSwappingEnabled(bool enabled) external;
}
interface IPermissionedPositionManager {
function setAllowedHook(
Currency currency,
IHooks hook,
bool allowed
) external;
}
contract PermissionedPoolOnboardingExample {
function onboard(
address permissionedToken,
address issuerAdmin,
address checker,
address factory,
address permissionedPositionManager,
address universalRouter,
address v4Quoter,
address mixedRouteQuoterV2,
address permissionedHooks,
address pairedAsset
) external returns (PoolKey memory key) {
address adapter = IPermissionsAdapterFactory(factory)
.createPermissionsAdapter(
IERC20(permissionedToken),
issuerAdmin,
IAllowlistChecker(checker)
);
IPermissionsAdapter(adapter).depositForVerification(1);
IPermissionsAdapterFactory(factory).verifyPermissionsAdapter(adapter);
IPermissionsAdapter(adapter).updateAllowedWrapper(permissionedPositionManager, true);
IPermissionsAdapter(adapter).updateAllowedWrapper(universalRouter, true);
IPermissionsAdapter(adapter).updateAllowedWrapper(v4Quoter, true);
IPermissionsAdapter(adapter).updateAllowedWrapper(mixedRouteQuoterV2, true);
IPermissionedPositionManager(permissionedPositionManager).setAllowedHook(
Currency.wrap(adapter),
IHooks(permissionedHooks),
true
);
key = PoolKey({
currency0: Currency.wrap(adapter),
currency1: Currency.wrap(pairedAsset),
fee: 3000,
tickSpacing: 60,
hooks: IHooks(permissionedHooks)
});
IPermissionsAdapter(adapter).updateSwappingEnabled(true);
}
}
Permissioned Pools 将合规控制嵌入链上执行路径,但需要注意其中的潜在安全点:PermissionsAdapter的owner地址可以更换 allowlist checker、可以暂停或开启swap、批准或撤销wrapper,allowlist checker的逻辑实现,资产发行方管理的用户资格变化。
结语
Uniswap v4 Permissioned Pools 的意义在于它让 permissioned assets 可以进入 AMM流动性池。v4 的 hook 架构提供了插入点,PermissionsAdapter 解决了底层 token 不能直接进入共享 PoolManager 的问题,PermissionedHooks 则把 swap 和 LP 的资格检查变成链上约束。这一设计将为 RWA 项目提供更标准的链上流动性基础设施,获得更多流动性,但项目团队需满足更复杂的权限治理和更高的合约审计要求。



