LLMs.txt: agent-readable Markdown index of this site at /llms.txt

Deploy a Permissioned Pool

Deploy a permissioned pool on Uniswap v4 by creating an allowlist checker and a Permissions Adapter.

This guide walks a permissioned asset issuer through onboarding a permissioned token so it can trade in a Uniswap v4 permissioned pool. Rather than building custom infrastructure, you use the shared PermissionsAdapterFactory and PermissionedHooks that Uniswap deploys and maintains, while keeping direct administrative control over your compliance rules.

At a high level, onboarding is seven steps:

  1. Implement an allowlist checker. Define the compliance logic that decides who is authorized to swap or provide liquidity.
  2. Create the Permissions Adapter. Use the factory to deploy the wrapper contract that acts as your token's pool-facing proxy.
  3. Allowlist and fund the adapter. Add the adapter to your token's allowlist and seed it with at least 1 wei so it can be verified.
  4. Verify the adapter. Register it with the factory so the protocol recognizes it as a legitimate wrapper.
  5. Approve wrappers. Authorize the position manager, router, and quoters that are allowed to wrap your token, and allow the permissioned hook on the position manager.
  6. Create the pool. Initialize the pool with the adapter as a currency and the permissioned hook, then enable swapping on the adapter.
  7. Allowlist your pool for routing. Register with Uniswap Labs so the pool is eligible for routing in the Uniswap interface and API.

The steps below walk through each in detail.

A permissioned pool does not by itself restrict who can hold the token. The token's allowlist does that, and the pool enforces those rules onchain for swaps and liquidity. Make sure your allowlist reflects any requirements that apply to your asset before deploying.

Prerequisites

  • A deployed ERC-20 permissioned token. The wrapped token is always named Uniswap v4 <name> with symbol v4<symbol>, read from your token's name(), symbol(), and decimals(). When those are missing, it falls back to Uniswap v4 Permissioned Token, v4PT, and 18 decimals, so exposing them is recommended.
  • A way to determine which addresses are allowed to swap or provide liquidity.
  • The address of the PermissionsAdapterFactory and PermissionedHooks for your target network. See Deployment Addresses.

Step 1: Implement an Allowlist Checker

The adapter delegates permission checks to a contract you implement. It must satisfy the IAllowlistChecker interface, which is an IERC165. The checkAllowlist function returns a PermissionFlag for a given account and token. A single checker can therefore serve multiple assets.

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";
import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";

contract MyAllowlistChecker is IAllowlistChecker, ERC165 {
    // Map each approved account to its granted permissions.
    mapping(address account => PermissionFlag) public permissions;

    // Return the permissions granted to `account` for `tokenAddress`.
    // PermissionFlags.SWAP_ALLOWED and PermissionFlags.LIQUIDITY_ALLOWED can be combined.
    function checkAllowlist(address account, address tokenAddress)
        external
        view
        returns (PermissionFlag)
    {
        return permissions[account];
    }

    // Owner-controlled setter to manage the allowlist (access control omitted for brevity).
    function setPermissions(address account, PermissionFlag permission) external {
        permissions[account] = permission;
    }

    // ERC-165 lets the adapter confirm this is a valid allowlist checker.
    // This is separate from factory verification of the adapter itself.
    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC165, IERC165)
        returns (bool)
    {
        return interfaceId == type(IAllowlistChecker).interfaceId || super.supportsInterface(interfaceId);
    }
}

Step 2: Create a Permissions Adapter

Call createPermissionsAdapter on the factory with your permissioned token, the admin address that will own the adapter, and your allowlist checker. The factory deploys an adapter scoped to your token.

address adapter = factory.createPermissionsAdapter(
    IERC20(permissionedToken),
    issuerAdmin,            // adapter owner: holds admin control over the pool
    IAllowlistChecker(myChecker)
);

The admin address becomes the adapter owner and holds admin control over the pool. It can change the allowlist checker, approve or revoke wrappers, pause swapping, and unwind positions. This is a high-leverage key, so secure it accordingly, for example with a multisig.

Step 3: Allowlist and Fund the Adapter

The adapter must be able to hold your permissioned token. Add the adapter address to your token's allowlist, then transfer a small balance so the factory can verify it. The amount can be as little as 1 wei.

// 1. On your permissioned token, allow the adapter to hold the token.
// 2. Seed the adapter so it holds a non-zero balance.
PermissionsAdapter(adapter).depositForVerification(1);

Step 4: Verify the Adapter

Verification confirms the adapter is allowed to hold your token. It succeeds only when the adapter holds a non-zero balance of the permissioned token.

factory.verifyPermissionsAdapter(adapter);

Step 5: Approve Wrappers

Approve the contracts that are allowed to wrap and unwrap your token, called allowed wrappers: the Permissioned Position Manager, the Universal Router, and both quoters (V4Quoter and MixedRouteQuoterV2) used for pricing. All four addresses are listed in Deployment Addresses below.

PermissionsAdapter(adapter).updateAllowedWrapper(permissionedPositionManager, true);
PermissionsAdapter(adapter).updateAllowedWrapper(universalRouter, true);
PermissionsAdapter(adapter).updateAllowedWrapper(v4Quoter, true);
PermissionsAdapter(adapter).updateAllowedWrapper(mixedRouteQuoterV2, true);

Then, allow PermissionedHooks for your adapter on the Permissioned Position Manager. Only the adapter owner can call this, and without it every attempt to mint a liquidity position reverts with InvalidHook.

PermissionedPositionManager(permissionedPositionManager).setAllowedHook(
    Currency.wrap(adapter),
    IHooks(permissionedHooks),
    true
);

Step 6: Create the Pool

Create the pool with the adapter address as the currency and PermissionedHooks as the hook. The hook reverts on initialization unless at least one currency is a verified adapter.

PoolKey memory key = PoolKey({
    currency0: Currency.wrap(adapter),   // the adapter, not the underlying token
    currency1: Currency.wrap(weth),
    fee: 3000,
    tickSpacing: 60,
    hooks: IHooks(permissionedHooks)
});

Finally, enable swapping on the adapter. Swapping is disabled by default on a new adapter, so swaps revert with SwappingDisabled until the owner turns it on.

PermissionsAdapter(adapter).updateSwappingEnabled(true);

Step 7: Allowlist Your Pool for Routing

To be eligible for routing in the Uniswap interface and API, your permissioned pool must be allowlisted with Uniswap Labs. Once your adapter is verified, request allowlisting with the details below, and the team follows up to complete onboarding. Allowlisting is required on every network, including mainnet and Sepolia.

This form is for issuers with a deployed, verified permissioned token. If you are still exploring and want the team to reach out, use the interest form instead.

During onboarding, the following values are added to the backend configuration:

  • Permissioned token address (the token wallets hold).
  • Verified adapter address (the Permissions Adapter created in Step 2 and verified in Step 4).
  • KYC URL (where unapproved users complete verification).

The configuration entry looks like this:

// Keyed by the permissioned token address, lowercased.
'0x...permissionedtokenaddress': {
  adapterTokenAddress: '0x...verifiedadapteraddress',
  kycUrl: 'https://your-kyc-provider.example/verify',
  issuer: 'Your Issuer Name',
},

Addresses must be lowercase. When the adapter and the permissioned token share an address, the token already behaves as its own adapter.

Deployment Addresses

Ethereum mainnet

Sepolia testnet

Swaps involving a permissioned token route through the Universal Router rather than a standalone router.

Where to Go Next