diff --git a/foundry.toml b/foundry.toml
index 30dff3ef..a77b97d5 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -1,15 +1,27 @@
 [profile.default]
 out = 'foundry-out'
 solc_version = '0.8.26'
-optimizer_runs = 1
+optimizer_runs = 44444444
 via_ir = true
 ffi = true
-fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}]
+fs_permissions = [{ access = "read-write", path = ".forge-snapshots/" }, { access = "read", path = "foundry-out/" }]
 evm_version = "cancun"
 gas_limit = "3000000000"
 fuzz_runs = 10_000
 bytecode_hash = "none"
 
+additional_compiler_profiles = [
+  { name = "posm", via_ir = true, optimizer_runs = 30000 },
+  { name = "descriptor", via_ir = true, optimizer_runs = 1 },
+  { name = "test", via_ir = false }
+]
+
+compilation_restrictions = [
+  { paths = "src/PositionManager.sol", optimizer_runs = 30000 },
+  { paths = "src/PositionDescriptor.sol", optimizer_runs = 1 },
+  { paths = "test/**", via_ir = false }
+]
+
 [profile.debug]
 via_ir = false
 optimizer_runs = 200
diff --git a/script/DeployPosm.s.sol b/script/DeployPosm.s.sol
index b87fbea0..c51c2ca4 100644
--- a/script/DeployPosm.s.sol
+++ b/script/DeployPosm.s.sol
@@ -5,11 +5,8 @@ import "forge-std/console2.sol";
 import "forge-std/Script.sol";
 
 import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
-import {StateView} from "../src/lens/StateView.sol";
-import {PositionManager} from "../src/PositionManager.sol";
 import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
-import {IPositionDescriptor} from "../src/interfaces/IPositionDescriptor.sol";
-import {PositionDescriptor} from "../src/PositionDescriptor.sol";
+import {Deploy, IPositionDescriptor, IPositionManager} from "../test/shared/Deploy.sol";
 import {IWETH9} from "../src/interfaces/external/IWETH9.sol";
 
 contract DeployPosmTest is Script {
@@ -21,18 +18,14 @@ contract DeployPosmTest is Script {
         uint256 unsubscribeGasLimit,
         address wrappedNative,
         string memory nativeCurrencyLabel
-    ) public returns (PositionDescriptor positionDescriptor, PositionManager posm) {
+    ) public returns (IPositionDescriptor positionDescriptor, IPositionManager posm) {
         vm.startBroadcast();
 
-        positionDescriptor = new PositionDescriptor(IPoolManager(poolManager), wrappedNative, nativeCurrencyLabel);
+        positionDescriptor = Deploy.positionDescriptor(poolManager, wrappedNative, nativeCurrencyLabel, hex"00");
         console2.log("PositionDescriptor", address(positionDescriptor));
 
-        posm = new PositionManager{salt: hex"03"}(
-            IPoolManager(poolManager),
-            IAllowanceTransfer(permit2),
-            unsubscribeGasLimit,
-            IPositionDescriptor(address(positionDescriptor)),
-            IWETH9(wrappedNative)
+        posm = Deploy.positionManager(
+            poolManager, permit2, unsubscribeGasLimit, address(positionDescriptor), wrappedNative, hex"03"
         );
         console2.log("PositionManager", address(posm));
 
diff --git a/script/DeployStateView.s.sol b/script/DeployStateView.s.sol
index 9584099d..230bd819 100644
--- a/script/DeployStateView.s.sol
+++ b/script/DeployStateView.s.sol
@@ -4,17 +4,16 @@ pragma solidity ^0.8.20;
 import "forge-std/console2.sol";
 import "forge-std/Script.sol";
 
-import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
-import {StateView} from "../src/lens/StateView.sol";
+import {Deploy, IStateView} from "../test/shared/Deploy.sol";
 
 contract DeployStateView is Script {
     function setUp() public {}
 
-    function run(address poolManager) public returns (StateView state) {
+    function run(address poolManager) public returns (IStateView state) {
         vm.startBroadcast();
 
         // forge script --broadcast --sig 'run(address)' --rpc-url <RPC_URL> --private-key <PRIV_KEY> --verify script/DeployStateView.s.sol:DeployStateView <POOL_MANAGER_ADDR>
-        state = new StateView(IPoolManager(poolManager));
+        state = Deploy.stateView(poolManager, hex"00");
         console2.log("StateView", address(state));
         console2.log("PoolManager", address(state.poolManager()));
 
diff --git a/script/DeployV4Quoter.s.sol b/script/DeployV4Quoter.s.sol
index 7cc61d5f..72361df6 100644
--- a/script/DeployV4Quoter.s.sol
+++ b/script/DeployV4Quoter.s.sol
@@ -4,17 +4,16 @@ pragma solidity ^0.8.20;
 import "forge-std/console2.sol";
 import "forge-std/Script.sol";
 
-import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
-import {V4Quoter} from "../src/lens/V4Quoter.sol";
+import {Deploy, IV4Quoter} from "../test/shared/Deploy.sol";
 
 contract DeployV4Quoter is Script {
     function setUp() public {}
 
-    function run(address poolManager) public returns (V4Quoter state) {
+    function run(address poolManager) public returns (IV4Quoter state) {
         vm.startBroadcast();
 
         // forge script --broadcast --sig 'run(address)' --rpc-url <RPC_URL> --private-key <PRIV_KEY> --verify script/DeployV4Quoter.s.sol:DeployV4Quoter <POOL_MANAGER_ADDR>
-        state = new V4Quoter(IPoolManager(poolManager));
+        state = Deploy.v4Quoter(poolManager, hex"00");
         console2.log("V4Quoter", address(state));
         console2.log("PoolManager", address(state.poolManager()));
 
diff --git a/snapshots/BaseActionsRouterTest.json b/snapshots/BaseActionsRouterTest.json
index 7290bb57..d7241dbe 100644
--- a/snapshots/BaseActionsRouterTest.json
+++ b/snapshots/BaseActionsRouterTest.json
@@ -1,3 +1,3 @@
 {
-  "BaseActionsRouter_mock10commands": "61332"
+  "BaseActionsRouter_mock10commands": "63076"
 }
\ No newline at end of file
diff --git a/snapshots/PaymentsTests.json b/snapshots/PaymentsTests.json
index f2523f09..c3e810d1 100644
--- a/snapshots/PaymentsTests.json
+++ b/snapshots/PaymentsTests.json
@@ -1,6 +1,6 @@
 {
-  "Payments_swap_settleFromCaller_takeAllToMsgSender": "132997",
-  "Payments_swap_settleFromCaller_takeAllToSpecifiedAddress": "134973",
-  "Payments_swap_settleWithBalance_takeAllToMsgSender": "127102",
-  "Payments_swap_settleWithBalance_takeAllToSpecifiedAddress": "127212"
+  "Payments_swap_settleFromCaller_takeAllToMsgSender": "133006",
+  "Payments_swap_settleFromCaller_takeAllToSpecifiedAddress": "134475",
+  "Payments_swap_settleWithBalance_takeAllToMsgSender": "126957",
+  "Payments_swap_settleWithBalance_takeAllToSpecifiedAddress": "127095"
 }
\ No newline at end of file
diff --git a/snapshots/PosMGasTest.json b/snapshots/PosMGasTest.json
index 10bfc9b2..195b2104 100644
--- a/snapshots/PosMGasTest.json
+++ b/snapshots/PosMGasTest.json
@@ -1,42 +1,42 @@
 {
-  "PositionManager_burn_empty": "51576",
-  "PositionManager_burn_empty_native": "51576",
-  "PositionManager_burn_nonEmpty_native_withClose": "128316",
-  "PositionManager_burn_nonEmpty_native_withTakePair": "127696",
-  "PositionManager_burn_nonEmpty_withClose": "135236",
-  "PositionManager_burn_nonEmpty_withTakePair": "134615",
-  "PositionManager_collect_native": "148571",
-  "PositionManager_collect_sameRange": "157220",
-  "PositionManager_collect_withClose": "157220",
-  "PositionManager_collect_withTakePair": "156456",
-  "PositionManager_decreaseLiquidity_native": "114165",
-  "PositionManager_decreaseLiquidity_withClose": "122555",
-  "PositionManager_decreaseLiquidity_withTakePair": "121791",
-  "PositionManager_decrease_burnEmpty": "138177",
-  "PositionManager_decrease_burnEmpty_native": "131258",
-  "PositionManager_decrease_sameRange_allLiquidity": "135218",
-  "PositionManager_decrease_take_take": "123169",
-  "PositionManager_increaseLiquidity_erc20_withClose": "162419",
-  "PositionManager_increaseLiquidity_erc20_withSettlePair": "161283",
-  "PositionManager_increaseLiquidity_native": "145296",
-  "PositionManager_increase_autocompoundExactUnclaimedFees": "138010",
-  "PositionManager_increase_autocompoundExcessFeesCredit": "180194",
-  "PositionManager_increase_autocompound_clearExcess": "150782",
-  "PositionManager_mint_native": "369623",
-  "PositionManager_mint_nativeWithSweep_withClose": "378280",
-  "PositionManager_mint_nativeWithSweep_withSettlePair": "377364",
-  "PositionManager_mint_onSameTickLower": "321205",
-  "PositionManager_mint_onSameTickUpper": "321875",
-  "PositionManager_mint_sameRange": "247444",
-  "PositionManager_mint_settleWithBalance_sweep": "423280",
-  "PositionManager_mint_warmedPool_differentRange": "327236",
-  "PositionManager_mint_withClose": "423986",
-  "PositionManager_mint_withSettlePair": "422936",
-  "PositionManager_multicall_initialize_mint": "460558",
-  "PositionManager_permit": "79259",
-  "PositionManager_permit_secondPosition": "62159",
-  "PositionManager_permit_twice": "45035",
-  "PositionManager_subscribe": "88475",
-  "PositionManager_unsubscribe": "63253",
-  "positionManager bytecode size": "19060"
+  "PositionManager_burn_empty": "51102",
+  "PositionManager_burn_empty_native": "51102",
+  "PositionManager_burn_nonEmpty_native_withClose": "127695",
+  "PositionManager_burn_nonEmpty_native_withTakePair": "127137",
+  "PositionManager_burn_nonEmpty_withClose": "134600",
+  "PositionManager_burn_nonEmpty_withTakePair": "134043",
+  "PositionManager_collect_native": "147732",
+  "PositionManager_collect_sameRange": "156364",
+  "PositionManager_collect_withClose": "156364",
+  "PositionManager_collect_withTakePair": "155679",
+  "PositionManager_decreaseLiquidity_native": "113681",
+  "PositionManager_decreaseLiquidity_withClose": "121933",
+  "PositionManager_decreaseLiquidity_withTakePair": "121248",
+  "PositionManager_decrease_burnEmpty": "137500",
+  "PositionManager_decrease_burnEmpty_native": "130595",
+  "PositionManager_decrease_sameRange_allLiquidity": "134649",
+  "PositionManager_decrease_take_take": "122490",
+  "PositionManager_increaseLiquidity_erc20_withClose": "160652",
+  "PositionManager_increaseLiquidity_erc20_withSettlePair": "159563",
+  "PositionManager_increaseLiquidity_native": "143532",
+  "PositionManager_increase_autocompoundExactUnclaimedFees": "138231",
+  "PositionManager_increase_autocompoundExcessFeesCredit": "179593",
+  "PositionManager_increase_autocompound_clearExcess": "149511",
+  "PositionManager_mint_native": "367550",
+  "PositionManager_mint_nativeWithSweep_withClose": "376153",
+  "PositionManager_mint_nativeWithSweep_withSettlePair": "375348",
+  "PositionManager_mint_onSameTickLower": "319298",
+  "PositionManager_mint_onSameTickUpper": "319940",
+  "PositionManager_mint_sameRange": "245522",
+  "PositionManager_mint_settleWithBalance_sweep": "420760",
+  "PositionManager_mint_warmedPool_differentRange": "325316",
+  "PositionManager_mint_withClose": "421910",
+  "PositionManager_mint_withSettlePair": "420939",
+  "PositionManager_multicall_initialize_mint": "458237",
+  "PositionManager_permit": "79175",
+  "PositionManager_permit_secondPosition": "62063",
+  "PositionManager_permit_twice": "44975",
+  "PositionManager_subscribe": "87968",
+  "PositionManager_unsubscribe": "62697",
+  "positionManager bytecode size": "23877"
 }
\ No newline at end of file
diff --git a/snapshots/PositionDescriptorTest.json b/snapshots/PositionDescriptorTest.json
index 32cd6922..5be19f6a 100644
--- a/snapshots/PositionDescriptorTest.json
+++ b/snapshots/PositionDescriptorTest.json
@@ -1,3 +1,3 @@
 {
-  "positionDescriptor bytecode size": "24179"
+  "positionDescriptor bytecode size": "24110"
 }
\ No newline at end of file
diff --git a/snapshots/QuoterTest.json b/snapshots/QuoterTest.json
index 34049732..a3c80a4f 100644
--- a/snapshots/QuoterTest.json
+++ b/snapshots/QuoterTest.json
@@ -1,15 +1,15 @@
 {
-  "Quoter_exactInputSingle_oneForZero_multiplePositions": "146317",
-  "Quoter_exactInputSingle_zeroForOne_multiplePositions": "151973",
-  "Quoter_exactOutputSingle_oneForZero": "80048",
-  "Quoter_exactOutputSingle_zeroForOne": "84626",
-  "Quoter_quoteExactInput_oneHop_1TickLoaded": "122994",
-  "Quoter_quoteExactInput_oneHop_initializedAfter": "147949",
-  "Quoter_quoteExactInput_oneHop_startingInitialized": "81420",
-  "Quoter_quoteExactInput_twoHops": "205421",
-  "Quoter_quoteExactOutput_oneHop_1TickLoaded": "122296",
-  "Quoter_quoteExactOutput_oneHop_2TicksLoaded": "152648",
-  "Quoter_quoteExactOutput_oneHop_initializedAfter": "122364",
-  "Quoter_quoteExactOutput_oneHop_startingInitialized": "98875",
-  "Quoter_quoteExactOutput_twoHops": "204897"
+  "Quoter_exactInputSingle_oneForZero_multiplePositions": "145902",
+  "Quoter_exactInputSingle_zeroForOne_multiplePositions": "152117",
+  "Quoter_exactOutputSingle_oneForZero": "79267",
+  "Quoter_exactOutputSingle_zeroForOne": "84512",
+  "Quoter_quoteExactInput_oneHop_1TickLoaded": "122728",
+  "Quoter_quoteExactInput_oneHop_initializedAfter": "147363",
+  "Quoter_quoteExactInput_oneHop_startingInitialized": "80638",
+  "Quoter_quoteExactInput_twoHops": "204942",
+  "Quoter_quoteExactOutput_oneHop_1TickLoaded": "122224",
+  "Quoter_quoteExactOutput_oneHop_2TicksLoaded": "152879",
+  "Quoter_quoteExactOutput_oneHop_initializedAfter": "122251",
+  "Quoter_quoteExactOutput_oneHop_startingInitialized": "98545",
+  "Quoter_quoteExactOutput_twoHops": "204670"
 }
\ No newline at end of file
diff --git a/snapshots/StateViewTest.json b/snapshots/StateViewTest.json
index 8da85637..dc141e39 100644
--- a/snapshots/StateViewTest.json
+++ b/snapshots/StateViewTest.json
@@ -1,12 +1,12 @@
 {
-  "StateView_extsload_getFeeGrowthGlobals": "2367",
-  "StateView_extsload_getFeeGrowthInside": "8444",
-  "StateView_extsload_getLiquidity": "1480",
-  "StateView_extsload_getPositionInfo": "2973",
-  "StateView_extsload_getPositionLiquidity": "1750",
-  "StateView_extsload_getSlot0": "1548",
-  "StateView_extsload_getTickBitmap": "1476",
-  "StateView_extsload_getTickFeeGrowthOutside": "2672",
-  "StateView_extsload_getTickInfo": "2896",
-  "StateView_extsload_getTickLiquidity": "1748"
+  "StateView_extsload_getFeeGrowthGlobals": "2203",
+  "StateView_extsload_getFeeGrowthInside": "7875",
+  "StateView_extsload_getLiquidity": "1439",
+  "StateView_extsload_getPositionInfo": "2761",
+  "StateView_extsload_getPositionLiquidity": "1691",
+  "StateView_extsload_getSlot0": "1486",
+  "StateView_extsload_getTickBitmap": "1432",
+  "StateView_extsload_getTickFeeGrowthOutside": "2490",
+  "StateView_extsload_getTickInfo": "2693",
+  "StateView_extsload_getTickLiquidity": "1686"
 }
\ No newline at end of file
diff --git a/snapshots/V4RouterTest.json b/snapshots/V4RouterTest.json
index 0b39471b..ff5612e8 100644
--- a/snapshots/V4RouterTest.json
+++ b/snapshots/V4RouterTest.json
@@ -1,26 +1,26 @@
 {
-  "V4Router_Bytecode": "5137",
-  "V4Router_ExactIn1Hop_nativeIn": "121819",
-  "V4Router_ExactIn1Hop_nativeOut": "120826",
-  "V4Router_ExactIn1Hop_oneForZero": "129715",
-  "V4Router_ExactIn1Hop_zeroForOne": "135623",
-  "V4Router_ExactIn2Hops": "191979",
-  "V4Router_ExactIn2Hops_nativeIn": "178175",
-  "V4Router_ExactIn3Hops": "248385",
-  "V4Router_ExactIn3Hops_nativeIn": "234581",
-  "V4Router_ExactInputSingle": "134546",
-  "V4Router_ExactInputSingle_nativeIn": "120742",
-  "V4Router_ExactInputSingle_nativeOut": "119714",
-  "V4Router_ExactOut1Hop_nativeIn_sweepETH": "128078",
-  "V4Router_ExactOut1Hop_nativeOut": "121895",
-  "V4Router_ExactOut1Hop_oneForZero": "130784",
-  "V4Router_ExactOut1Hop_zeroForOne": "134905",
-  "V4Router_ExactOut2Hops": "190319",
-  "V4Router_ExactOut2Hops_nativeIn": "183492",
-  "V4Router_ExactOut3Hops": "245811",
-  "V4Router_ExactOut3Hops_nativeIn": "238984",
-  "V4Router_ExactOut3Hops_nativeOut": "224567",
-  "V4Router_ExactOutputSingle": "133809",
-  "V4Router_ExactOutputSingle_nativeIn_sweepETH": "126982",
-  "V4Router_ExactOutputSingle_nativeOut": "120876"
+  "V4Router_Bytecode": "8975",
+  "V4Router_ExactIn1Hop_nativeIn": "121653",
+  "V4Router_ExactIn1Hop_nativeOut": "120119",
+  "V4Router_ExactIn1Hop_oneForZero": "128991",
+  "V4Router_ExactIn1Hop_zeroForOne": "135517",
+  "V4Router_ExactIn2Hops": "192843",
+  "V4Router_ExactIn2Hops_nativeIn": "178979",
+  "V4Router_ExactIn3Hops": "250218",
+  "V4Router_ExactIn3Hops_nativeIn": "236354",
+  "V4Router_ExactInputSingle": "134001",
+  "V4Router_ExactInputSingle_nativeIn": "120137",
+  "V4Router_ExactInputSingle_nativeOut": "118581",
+  "V4Router_ExactOut1Hop_nativeIn_sweepETH": "127940",
+  "V4Router_ExactOut1Hop_nativeOut": "121329",
+  "V4Router_ExactOut1Hop_oneForZero": "130201",
+  "V4Router_ExactOut1Hop_zeroForOne": "134858",
+  "V4Router_ExactOut2Hops": "191291",
+  "V4Router_ExactOut2Hops_nativeIn": "184373",
+  "V4Router_ExactOut3Hops": "247788",
+  "V4Router_ExactOut3Hops_nativeIn": "240870",
+  "V4Router_ExactOut3Hops_nativeOut": "224943",
+  "V4Router_ExactOutputSingle": "133337",
+  "V4Router_ExactOutputSingle_nativeIn_sweepETH": "126419",
+  "V4Router_ExactOutputSingle_nativeOut": "119821"
 }
\ No newline at end of file
diff --git a/src/PositionDescriptor.sol b/src/PositionDescriptor.sol
index 021cbd5e..5dbbe151 100644
--- a/src/PositionDescriptor.sol
+++ b/src/PositionDescriptor.sol
@@ -21,8 +21,6 @@ contract PositionDescriptor is IPositionDescriptor {
     using CurrencyLibrary for Currency;
     using PositionInfoLibrary for PositionInfo;
 
-    error InvalidTokenId(uint256 tokenId);
-
     // mainnet addresses
     address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
     address private constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
@@ -87,18 +85,12 @@ contract PositionDescriptor is IPositionDescriptor {
         );
     }
 
-    /// @notice Returns true if currency0 has higher priority than currency1
-    /// @param currency0 The first currency address
-    /// @param currency1 The second currency address
-    /// @return flipRatio True if currency0 has higher priority than currency1
+    /// @inheritdoc IPositionDescriptor
     function flipRatio(address currency0, address currency1) public view returns (bool) {
         return currencyRatioPriority(currency0) > currencyRatioPriority(currency1);
     }
 
-    /// @notice Returns the priority of a currency.
-    /// For certain currencies on mainnet, the smaller the currency, the higher the priority
-    /// @param currency The currency address
-    /// @return priority The priority of the currency
+    /// @inheritdoc IPositionDescriptor
     function currencyRatioPriority(address currency) public view returns (int256) {
         // Currencies in order of priority on mainnet: USDC, USDT, DAI, (ETH, WETH), TBTC, WBTC
         // wrapped native is different address on different chains. passed in constructor
diff --git a/src/base/BaseV4Quoter.sol b/src/base/BaseV4Quoter.sol
index 52bacdb3..55e57742 100644
--- a/src/base/BaseV4Quoter.sol
+++ b/src/base/BaseV4Quoter.sol
@@ -1,4 +1,5 @@
 // SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
 
 import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
 import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
diff --git a/src/base/Permit2Forwarder.sol b/src/base/Permit2Forwarder.sol
index d5a749a8..43cdc1ce 100644
--- a/src/base/Permit2Forwarder.sol
+++ b/src/base/Permit2Forwarder.sol
@@ -1,11 +1,11 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.0;
 
-import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
+import {IPermit2Forwarder, IAllowanceTransfer} from "../interfaces/IPermit2Forwarder.sol";
 
-/// @notice PermitForwarder allows permitting this contract as a spender on permit2
+/// @notice Permit2Forwarder allows permitting this contract as a spender on permit2
 /// @dev This contract does not enforce the spender to be this contract, but that is the intended use case
-contract Permit2Forwarder {
+contract Permit2Forwarder is IPermit2Forwarder {
     /// @notice the Permit2 contract to forward approvals
     IAllowanceTransfer public immutable permit2;
 
@@ -13,11 +13,7 @@ contract Permit2Forwarder {
         permit2 = _permit2;
     }
 
-    /// @notice allows forwarding a single permit to permit2
-    /// @dev this function is payable to allow multicall with NATIVE based actions
-    /// @param owner the owner of the tokens
-    /// @param permitSingle the permit data
-    /// @param signature the signature of the permit; abi.encodePacked(r, s, v)
+    /// @inheritdoc IPermit2Forwarder
     function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature)
         external
         payable
@@ -30,11 +26,7 @@ contract Permit2Forwarder {
         }
     }
 
-    /// @notice allows forwarding batch permits to permit2
-    /// @dev this function is payable to allow multicall with NATIVE based actions
-    /// @param owner the owner of the tokens
-    /// @param _permitBatch a batch of approvals
-    /// @param signature the signature of the permit; abi.encodePacked(r, s, v)
+    /// @inheritdoc IPermit2Forwarder
     function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature)
         external
         payable
diff --git a/src/base/PoolInitializer.sol b/src/base/PoolInitializer.sol
index fea60340..5212afc6 100644
--- a/src/base/PoolInitializer.sol
+++ b/src/base/PoolInitializer.sol
@@ -2,18 +2,14 @@
 pragma solidity ^0.8.0;
 
 import {ImmutableState} from "./ImmutableState.sol";
-
 import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
+import {IPoolInitializer} from "../interfaces/IPoolInitializer.sol";
 
 /// @title Pool Initializer
 /// @notice Initializes a Uniswap v4 Pool
 /// @dev Enables create pool + mint liquidity in a single transaction with multicall
-abstract contract PoolInitializer is ImmutableState {
-    /// @notice Initialize a Uniswap v4 Pool
-    /// @dev If the pool is already initialized, this function will not revert and just return type(int24).max
-    /// @param key the PoolKey of the pool to initialize
-    /// @param sqrtPriceX96 the initial sqrtPriceX96 of the pool
-    /// @return tick The current tick of the pool
+abstract contract PoolInitializer is ImmutableState, IPoolInitializer {
+    /// @inheritdoc IPoolInitializer
     function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24) {
         try poolManager.initialize(key, sqrtPriceX96) returns (int24 tick) {
             return tick;
diff --git a/src/base/UnorderedNonce.sol b/src/base/UnorderedNonce.sol
index b08b5d92..53b93b96 100644
--- a/src/base/UnorderedNonce.sol
+++ b/src/base/UnorderedNonce.sol
@@ -1,13 +1,12 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.0;
 
+import {IUnorderedNonce} from "../interfaces/IUnorderedNonce.sol";
+
 /// @title Unordered Nonce
 /// @notice Contract state and methods for using unordered nonces in signatures
-contract UnorderedNonce {
-    error NonceAlreadyUsed();
-
-    /// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap
-    /// @dev word is at most type(uint248).max
+contract UnorderedNonce is IUnorderedNonce {
+    /// @inheritdoc IUnorderedNonce
     mapping(address owner => mapping(uint256 word => uint256 bitmap)) public nonces;
 
     /// @notice Consume a nonce, reverting if it has already been used
@@ -22,9 +21,7 @@ contract UnorderedNonce {
         if (flipped & bit == 0) revert NonceAlreadyUsed();
     }
 
-    /// @notice Revoke a nonce by spending it, preventing it from being used again
-    /// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce
-    /// @dev payable so it can be multicalled with native-token related actions
+    /// @inheritdoc IUnorderedNonce
     function revokeNonce(uint256 nonce) external payable {
         _useUnorderedNonce(msg.sender, nonce);
     }
diff --git a/src/interfaces/IPermit2Forwarder.sol b/src/interfaces/IPermit2Forwarder.sol
new file mode 100644
index 00000000..98403ac8
--- /dev/null
+++ b/src/interfaces/IPermit2Forwarder.sol
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
+
+/// @title IPermit2Forwarder
+/// @notice Interface for the Permit2Forwarder contract
+interface IPermit2Forwarder {
+    /// @notice allows forwarding a single permit to permit2
+    /// @dev this function is payable to allow multicall with NATIVE based actions
+    /// @param owner the owner of the tokens
+    /// @param permitSingle the permit data
+    /// @param signature the signature of the permit; abi.encodePacked(r, s, v)
+    /// @return err the error returned by a reverting permit call, empty if successful
+    function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature)
+        external
+        payable
+        returns (bytes memory err);
+
+    /// @notice allows forwarding batch permits to permit2
+    /// @dev this function is payable to allow multicall with NATIVE based actions
+    /// @param owner the owner of the tokens
+    /// @param _permitBatch a batch of approvals
+    /// @param signature the signature of the permit; abi.encodePacked(r, s, v)
+    /// @return err the error returned by a reverting permit call, empty if successful
+    function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature)
+        external
+        payable
+        returns (bytes memory err);
+}
diff --git a/src/interfaces/IPoolInitializer.sol b/src/interfaces/IPoolInitializer.sol
new file mode 100644
index 00000000..b6168d4b
--- /dev/null
+++ b/src/interfaces/IPoolInitializer.sol
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
+
+interface IPoolInitializer {
+    /// @notice Initialize a Uniswap v4 Pool
+    /// @dev If the pool is already initialized, this function will not revert and just return type(int24).max
+    /// @param key The PoolKey of the pool to initialize
+    /// @param sqrtPriceX96 The initial starting price of the pool, expressed as a sqrtPriceX96
+    /// @return The current tick of the pool, or type(int24).max if the pool creation failed, or the pool already existed
+    function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24);
+}
diff --git a/src/interfaces/IPositionDescriptor.sol b/src/interfaces/IPositionDescriptor.sol
index a1736cea..70f8d3eb 100644
--- a/src/interfaces/IPositionDescriptor.sol
+++ b/src/interfaces/IPositionDescriptor.sol
@@ -2,13 +2,38 @@
 pragma solidity ^0.8.24;
 
 import "./IPositionManager.sol";
+import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
 
 /// @title Describes position NFT tokens via URI
 interface IPositionDescriptor {
+    error InvalidTokenId(uint256 tokenId);
+
     /// @notice Produces the URI describing a particular token ID
     /// @dev Note this URI may be a data: URI with the JSON contents directly inlined
     /// @param positionManager The position manager for which to describe the token
     /// @param tokenId The ID of the token for which to produce a description, which may not be valid
     /// @return The URI of the ERC721-compliant metadata
     function tokenURI(IPositionManager positionManager, uint256 tokenId) external view returns (string memory);
+
+    /// @notice Returns true if currency0 has higher priority than currency1
+    /// @param currency0 The first currency address
+    /// @param currency1 The second currency address
+    /// @return True if currency0 has higher priority than currency1
+    function flipRatio(address currency0, address currency1) external view returns (bool);
+
+    /// @notice Returns the priority of a currency.
+    /// For certain currencies on mainnet, the smaller the currency, the higher the priority
+    /// And those with the higher priority values (more positive values) will be in the numerator of the price ratio
+    /// @param currency The currency address
+    /// @return The priority of the currency
+    function currencyRatioPriority(address currency) external view returns (int256);
+
+    /// @return The wrapped native token for this descriptor
+    function wrappedNative() external view returns (address);
+
+    /// @return The native currency label for this descriptor
+    function nativeCurrencyLabel() external view returns (string memory);
+
+    /// @return The pool manager for this descriptor
+    function poolManager() external view returns (IPoolManager);
 }
diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol
index 86df7bbf..e9746fbf 100644
--- a/src/interfaces/IPositionManager.sol
+++ b/src/interfaces/IPositionManager.sol
@@ -6,10 +6,25 @@ import {PositionInfo} from "../libraries/PositionInfoLibrary.sol";
 
 import {INotifier} from "./INotifier.sol";
 import {IImmutableState} from "./IImmutableState.sol";
+import {IERC721Permit_v4} from "./IERC721Permit_v4.sol";
+import {IEIP712_v4} from "./IEIP712_v4.sol";
+import {IMulticall_v4} from "./IMulticall_v4.sol";
+import {IPoolInitializer} from "./IPoolInitializer.sol";
+import {IUnorderedNonce} from "./IUnorderedNonce.sol";
+import {IPermit2Forwarder} from "./IPermit2Forwarder.sol";
 
 /// @title IPositionManager
 /// @notice Interface for the PositionManager contract
-interface IPositionManager is INotifier, IImmutableState {
+interface IPositionManager is
+    INotifier,
+    IImmutableState,
+    IERC721Permit_v4,
+    IEIP712_v4,
+    IMulticall_v4,
+    IPoolInitializer,
+    IUnorderedNonce,
+    IPermit2Forwarder
+{
     /// @notice Thrown when the caller is not approved to modify a position
     error NotApproved(address caller);
     /// @notice Thrown when the block.timestamp exceeds the user-provided deadline
@@ -34,13 +49,20 @@ interface IPositionManager is INotifier, IImmutableState {
     /// @return uint256 The next token ID
     function nextTokenId() external view returns (uint256);
 
+    /// @notice Returns the liquidity of a position
     /// @param tokenId the ERC721 tokenId
     /// @return liquidity the position's liquidity, as a liquidityAmount
     /// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library
     function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity);
 
+    /// @notice Returns the pool key and position info of a position
     /// @param tokenId the ERC721 tokenId
-    /// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
     /// @return poolKey the pool key of the position
+    /// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
     function getPoolAndPositionInfo(uint256 tokenId) external view returns (PoolKey memory, PositionInfo);
+
+    /// @notice Returns the position info of a position
+    /// @param tokenId the ERC721 tokenId
+    /// @return a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
+    function positionInfo(uint256 tokenId) external view returns (PositionInfo);
 }
diff --git a/src/interfaces/IStateView.sol b/src/interfaces/IStateView.sol
new file mode 100644
index 00000000..31f62b3a
--- /dev/null
+++ b/src/interfaces/IStateView.sol
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
+import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
+import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
+import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
+import {IImmutableState} from "../interfaces/IImmutableState.sol";
+
+/// @title StateView Interface
+/// @notice A view only contract wrapping the StateLibrary.sol library for reading storage in v4-core.
+/// @dev The contract is intended for offchain clients. Use StateLibrary.sol directly if reading state onchain.
+interface IStateView is IImmutableState {
+    /// @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee
+    /// @dev Corresponds to pools[poolId].slot0
+    /// @param poolId The ID of the pool.
+    /// @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision.
+    /// @return tick The current tick of the pool.
+    /// @return protocolFee The protocol fee of the pool.
+    /// @return lpFee The swap fee of the pool.
+    function getSlot0(PoolId poolId)
+        external
+        view
+        returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee);
+
+    /// @notice Retrieves the tick information of a pool at a specific tick.
+    /// @dev Corresponds to pools[poolId].ticks[tick]
+    /// @param poolId The ID of the pool.
+    /// @param tick The tick to retrieve information for.
+    /// @return liquidityGross The total position liquidity that references this tick
+    /// @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
+    /// @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
+    /// @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
+    function getTickInfo(PoolId poolId, int24 tick)
+        external
+        view
+        returns (
+            uint128 liquidityGross,
+            int128 liquidityNet,
+            uint256 feeGrowthOutside0X128,
+            uint256 feeGrowthOutside1X128
+        );
+
+    /// @notice Retrieves the liquidity information of a pool at a specific tick.
+    /// @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo
+    /// @param poolId The ID of the pool.
+    /// @param tick The tick to retrieve liquidity for.
+    /// @return liquidityGross The total position liquidity that references this tick
+    /// @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
+    function getTickLiquidity(PoolId poolId, int24 tick)
+        external
+        view
+        returns (uint128 liquidityGross, int128 liquidityNet);
+
+    /// @notice Retrieves the fee growth outside a tick range of a pool
+    /// @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo
+    /// @param poolId The ID of the pool.
+    /// @param tick The tick to retrieve fee growth for.
+    /// @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
+    /// @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
+    function getTickFeeGrowthOutside(PoolId poolId, int24 tick)
+        external
+        view
+        returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128);
+
+    /// @notice Retrieves the global fee growth of a pool.
+    /// @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128
+    /// @param poolId The ID of the pool.
+    /// @return feeGrowthGlobal0 The global fee growth for token0.
+    /// @return feeGrowthGlobal1 The global fee growth for token1.
+    function getFeeGrowthGlobals(PoolId poolId)
+        external
+        view
+        returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1);
+
+    /// @notice Retrieves the total liquidity of a pool.
+    /// @dev Corresponds to pools[poolId].liquidity
+    /// @param poolId The ID of the pool.
+    /// @return liquidity The liquidity of the pool.
+    function getLiquidity(PoolId poolId) external view returns (uint128 liquidity);
+
+    /// @notice Retrieves the tick bitmap of a pool at a specific tick.
+    /// @dev Corresponds to pools[poolId].tickBitmap[tick]
+    /// @param poolId The ID of the pool.
+    /// @param tick The tick to retrieve the bitmap for.
+    /// @return tickBitmap The bitmap of the tick.
+    function getTickBitmap(PoolId poolId, int16 tick) external view returns (uint256 tickBitmap);
+
+    /// @notice Retrieves the position info without needing to calculate the `positionId`.
+    /// @dev Corresponds to pools[poolId].positions[positionId]
+    /// @param poolId The ID of the pool.
+    /// @param owner The owner of the liquidity position.
+    /// @param tickLower The lower tick of the liquidity range.
+    /// @param tickUpper The upper tick of the liquidity range.
+    /// @param salt The bytes32 randomness to further distinguish position state.
+    /// @return liquidity The liquidity of the position.
+    /// @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
+    /// @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
+    function getPositionInfo(PoolId poolId, address owner, int24 tickLower, int24 tickUpper, bytes32 salt)
+        external
+        view
+        returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128);
+
+    /// @notice Retrieves the position information of a pool at a specific position ID.
+    /// @dev Corresponds to pools[poolId].positions[positionId]
+    /// @param poolId The ID of the pool.
+    /// @param positionId The ID of the position.
+    /// @return liquidity The liquidity of the position.
+    /// @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
+    /// @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
+    function getPositionInfo(PoolId poolId, bytes32 positionId)
+        external
+        view
+        returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128);
+
+    /// @notice Retrieves the liquidity of a position.
+    /// @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieving liquidity as compared to getPositionInfo
+    /// @param poolId The ID of the pool.
+    /// @param positionId The ID of the position.
+    /// @return liquidity The liquidity of the position.
+    function getPositionLiquidity(PoolId poolId, bytes32 positionId) external view returns (uint128 liquidity);
+
+    /// @notice Calculate the fee growth inside a tick range of a pool
+    /// @dev pools[poolId].feeGrowthInside0LastX128 in Position.Info is cached and can become stale. This function will calculate the up to date feeGrowthInside
+    /// @param poolId The ID of the pool.
+    /// @param tickLower The lower tick of the range.
+    /// @param tickUpper The upper tick of the range.
+    /// @return feeGrowthInside0X128 The fee growth inside the tick range for token0.
+    /// @return feeGrowthInside1X128 The fee growth inside the tick range for token1.
+    function getFeeGrowthInside(PoolId poolId, int24 tickLower, int24 tickUpper)
+        external
+        view
+        returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128);
+}
diff --git a/src/interfaces/IUnorderedNonce.sol b/src/interfaces/IUnorderedNonce.sol
new file mode 100644
index 00000000..4e1866a6
--- /dev/null
+++ b/src/interfaces/IUnorderedNonce.sol
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/// @title UnorderedNonce Interface
+/// @notice Interface for the UnorderedNonce contract
+interface IUnorderedNonce {
+    error NonceAlreadyUsed();
+
+    /// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap
+    /// @dev word is at most type(uint248).max
+    function nonces(address owner, uint256 word) external view returns (uint256);
+
+    /// @notice Revoke a nonce by spending it, preventing it from being used again
+    /// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce
+    /// @dev payable so it can be multicalled with native-token related actions
+    function revokeNonce(uint256 nonce) external payable;
+}
diff --git a/src/interfaces/IV4Quoter.sol b/src/interfaces/IV4Quoter.sol
index f502a4f4..d51ddb3c 100644
--- a/src/interfaces/IV4Quoter.sol
+++ b/src/interfaces/IV4Quoter.sol
@@ -4,13 +4,14 @@ pragma solidity ^0.8.0;
 import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
 import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
 import {PathKey} from "../libraries/PathKey.sol";
+import {IImmutableState} from "./IImmutableState.sol";
 
 /// @title V4 Quoter Interface
 /// @notice Supports quoting the delta amounts for exact input or exact output swaps.
 /// @notice For each pool also tells you the sqrt price of the pool after the swap.
 /// @dev These functions are not marked view because they rely on calling non-view functions and reverting
 /// to compute the result. They are also not gas efficient and should not be called on-chain.
-interface IV4Quoter {
+interface IV4Quoter is IImmutableState {
     struct QuoteExactSingleParams {
         PoolKey poolKey;
         bool zeroForOne;
diff --git a/src/lens/StateView.sol b/src/lens/StateView.sol
index 6527d2f7..e3894c19 100644
--- a/src/lens/StateView.sol
+++ b/src/lens/StateView.sol
@@ -5,24 +5,16 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
 import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
 import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
 import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
-
 import {ImmutableState} from "../base/ImmutableState.sol";
+import {IStateView} from "../interfaces/IStateView.sol";
 
 /// @notice A view only contract wrapping the StateLibrary.sol library for reading storage in v4-core.
-contract StateView is ImmutableState {
+contract StateView is ImmutableState, IStateView {
     using StateLibrary for IPoolManager;
 
     constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {}
 
-    /**
-     * @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee
-     * @dev Corresponds to pools[poolId].slot0
-     * @param poolId The ID of the pool.
-     * @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision.
-     * @return tick The current tick of the pool.
-     * @return protocolFee The protocol fee of the pool.
-     * @return lpFee The swap fee of the pool.
-     */
+    /// @inheritdoc IStateView
     function getSlot0(PoolId poolId)
         external
         view
@@ -31,16 +23,7 @@ contract StateView is ImmutableState {
         return poolManager.getSlot0(poolId);
     }
 
-    /**
-     * @notice Retrieves the tick information of a pool at a specific tick.
-     * @dev Corresponds to pools[poolId].ticks[tick]
-     * @param poolId The ID of the pool.
-     * @param tick The tick to retrieve information for.
-     * @return liquidityGross The total position liquidity that references this tick
-     * @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
-     * @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
-     * @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
-     */
+    /// @inheritdoc IStateView
     function getTickInfo(PoolId poolId, int24 tick)
         external
         view
@@ -54,14 +37,7 @@ contract StateView is ImmutableState {
         return poolManager.getTickInfo(poolId, tick);
     }
 
-    /**
-     * @notice Retrieves the liquidity information of a pool at a specific tick.
-     * @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo
-     * @param poolId The ID of the pool.
-     * @param tick The tick to retrieve liquidity for.
-     * @return liquidityGross The total position liquidity that references this tick
-     * @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
-     */
+    /// @inheritdoc IStateView
     function getTickLiquidity(PoolId poolId, int24 tick)
         external
         view
@@ -70,14 +46,7 @@ contract StateView is ImmutableState {
         return poolManager.getTickLiquidity(poolId, tick);
     }
 
-    /**
-     * @notice Retrieves the fee growth outside a tick range of a pool
-     * @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo
-     * @param poolId The ID of the pool.
-     * @param tick The tick to retrieve fee growth for.
-     * @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
-     * @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
-     */
+    /// @inheritdoc IStateView
     function getTickFeeGrowthOutside(PoolId poolId, int24 tick)
         external
         view
@@ -86,13 +55,7 @@ contract StateView is ImmutableState {
         return poolManager.getTickFeeGrowthOutside(poolId, tick);
     }
 
-    /**
-     * @notice Retrieves the global fee growth of a pool.
-     * @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128
-     * @param poolId The ID of the pool.
-     * @return feeGrowthGlobal0 The global fee growth for token0.
-     * @return feeGrowthGlobal1 The global fee growth for token1.
-     */
+    /// @inheritdoc IStateView
     function getFeeGrowthGlobals(PoolId poolId)
         external
         view
@@ -101,39 +64,17 @@ contract StateView is ImmutableState {
         return poolManager.getFeeGrowthGlobals(poolId);
     }
 
-    /**
-     * @notice Retrieves the total liquidity of a pool.
-     * @dev Corresponds to pools[poolId].liquidity
-     * @param poolId The ID of the pool.
-     * @return liquidity The liquidity of the pool.
-     */
+    /// @inheritdoc IStateView
     function getLiquidity(PoolId poolId) external view returns (uint128 liquidity) {
         return poolManager.getLiquidity(poolId);
     }
 
-    /**
-     * @notice Retrieves the tick bitmap of a pool at a specific tick.
-     * @dev Corresponds to pools[poolId].tickBitmap[tick]
-     * @param poolId The ID of the pool.
-     * @param tick The tick to retrieve the bitmap for.
-     * @return tickBitmap The bitmap of the tick.
-     */
+    /// @inheritdoc IStateView
     function getTickBitmap(PoolId poolId, int16 tick) external view returns (uint256 tickBitmap) {
         return poolManager.getTickBitmap(poolId, tick);
     }
 
-    /**
-     * @notice Retrieves the position info without needing to calculate the `positionId`.
-     * @dev Corresponds to pools[poolId].positions[positionId]
-     * @param poolId The ID of the pool.
-     * @param owner The owner of the liquidity position.
-     * @param tickLower The lower tick of the liquidity range.
-     * @param tickUpper The upper tick of the liquidity range.
-     * @param salt The bytes32 randomness to further distinguish position state.
-     * @return liquidity The liquidity of the position.
-     * @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
-     * @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
-     */
+    /// @inheritdoc IStateView
     function getPositionInfo(PoolId poolId, address owner, int24 tickLower, int24 tickUpper, bytes32 salt)
         external
         view
@@ -142,15 +83,7 @@ contract StateView is ImmutableState {
         return poolManager.getPositionInfo(poolId, owner, tickLower, tickUpper, salt);
     }
 
-    /**
-     * @notice Retrieves the position information of a pool at a specific position ID.
-     * @dev Corresponds to pools[poolId].positions[positionId]
-     * @param poolId The ID of the pool.
-     * @param positionId The ID of the position.
-     * @return liquidity The liquidity of the position.
-     * @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
-     * @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
-     */
+    /// @inheritdoc IStateView
     function getPositionInfo(PoolId poolId, bytes32 positionId)
         external
         view
@@ -159,26 +92,12 @@ contract StateView is ImmutableState {
         return poolManager.getPositionInfo(poolId, positionId);
     }
 
-    /**
-     * @notice Retrieves the liquidity of a position.
-     * @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieving liquidity as compared to getPositionInfo
-     * @param poolId The ID of the pool.
-     * @param positionId The ID of the position.
-     * @return liquidity The liquidity of the position.
-     */
+    /// @inheritdoc IStateView
     function getPositionLiquidity(PoolId poolId, bytes32 positionId) external view returns (uint128 liquidity) {
         return poolManager.getPositionLiquidity(poolId, positionId);
     }
 
-    /**
-     * @notice Calculate the fee growth inside a tick range of a pool
-     * @dev pools[poolId].feeGrowthInside0LastX128 in Position.Info is cached and can become stale. This function will calculate the up to date feeGrowthInside
-     * @param poolId The ID of the pool.
-     * @param tickLower The lower tick of the range.
-     * @param tickUpper The upper tick of the range.
-     * @return feeGrowthInside0X128 The fee growth inside the tick range for token0.
-     * @return feeGrowthInside1X128 The fee growth inside the tick range for token1.
-     */
+    /// @inheritdoc IStateView
     function getFeeGrowthInside(PoolId poolId, int24 tickLower, int24 tickUpper)
         external
         view
diff --git a/test/PositionDescriptor.t.sol b/test/PositionDescriptor.t.sol
index c20465f0..57b1a641 100644
--- a/test/PositionDescriptor.t.sol
+++ b/test/PositionDescriptor.t.sol
@@ -2,7 +2,7 @@
 pragma solidity ^0.8.24;
 
 import "forge-std/Test.sol";
-import {PositionDescriptor} from "../src/PositionDescriptor.sol";
+import {IPositionDescriptor} from "../src/interfaces/IPositionDescriptor.sol";
 import {CurrencyRatioSortOrder} from "../src/libraries/CurrencyRatioSortOrder.sol";
 import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
 import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol";
@@ -87,149 +87,164 @@ contract PositionDescriptorTest is Test, PosmTestSetup {
     function test_tokenURI_succeeds() public {
         int24 tickLower = int24(key.tickSpacing);
         int24 tickUpper = int24(key.tickSpacing * 2);
-        uint256 amount0Desired = 100e18;
-        uint256 amount1Desired = 100e18;
-        uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts(
-            SQRT_PRICE_1_1,
-            TickMath.getSqrtPriceAtTick(tickLower),
-            TickMath.getSqrtPriceAtTick(tickUpper),
-            amount0Desired,
-            amount1Desired
-        );
-
-        PositionConfig memory config = PositionConfig({poolKey: key, tickLower: tickLower, tickUpper: tickUpper});
         uint256 tokenId = lpm.nextTokenId();
-        mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES);
-
-        // The prefix length is calculated by converting the string to bytes and finding its length
-        uint256 prefixLength = bytes("data:application/json;base64,").length;
-
-        string memory uri = positionDescriptor.tokenURI(lpm, tokenId);
-        // Convert the uri to bytes
-        bytes memory uriBytes = bytes(uri);
-
-        // Slice the uri to get only the base64-encoded part
-        bytes memory base64Part = new bytes(uriBytes.length - prefixLength);
-
-        for (uint256 i = 0; i < base64Part.length; i++) {
-            base64Part[i] = uriBytes[i + prefixLength];
+        Token memory token;
+        {
+            uint256 amount0Desired = 100e18;
+            uint256 amount1Desired = 100e18;
+            uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts(
+                SQRT_PRICE_1_1,
+                TickMath.getSqrtPriceAtTick(tickLower),
+                TickMath.getSqrtPriceAtTick(tickUpper),
+                amount0Desired,
+                amount1Desired
+            );
+
+            PositionConfig memory config = PositionConfig({poolKey: key, tickLower: tickLower, tickUpper: tickUpper});
+            mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES);
+
+            // The prefix length is calculated by converting the string to bytes and finding its length
+            uint256 prefixLength = bytes("data:application/json;base64,").length;
+
+            string memory uri = positionDescriptor.tokenURI(lpm, tokenId);
+            // Convert the uri to bytes
+            bytes memory uriBytes = bytes(uri);
+
+            // Slice the uri to get only the base64-encoded part
+            bytes memory base64Part = new bytes(uriBytes.length - prefixLength);
+
+            for (uint256 i = 0; i < base64Part.length; i++) {
+                base64Part[i] = uriBytes[i + prefixLength];
+            }
+
+            // Decode the base64-encoded part
+            bytes memory decoded = Base64.decode(string(base64Part));
+            string memory json = string(decoded);
+
+            // decode json
+            bytes memory data = vm.parseJson(json);
+            token = abi.decode(data, (Token));
         }
 
-        // Decode the base64-encoded part
-        bytes memory decoded = Base64.decode(string(base64Part));
-        string memory json = string(decoded);
-
-        // decode json
-        bytes memory data = vm.parseJson(json);
-        Token memory token = abi.decode(data, (Token));
-
         // quote is currency1, base is currency0
         assertFalse(positionDescriptor.flipRatio(Currency.unwrap(key.currency0), Currency.unwrap(key.currency1)));
 
         string memory symbol0 = SafeCurrencyMetadata.currencySymbol(Currency.unwrap(currency0), nativeCurrencyLabel);
         string memory symbol1 = SafeCurrencyMetadata.currencySymbol(Currency.unwrap(currency1), nativeCurrencyLabel);
-        string memory managerAddress = toHexString(address(manager));
-        string memory currency0Address = toHexString(Currency.unwrap(currency0));
-        string memory currency1Address = toHexString(Currency.unwrap(currency1));
-        string memory id = uintToString(tokenId);
-        string memory hookAddress = address(key.hooks) == address(0)
-            ? "No Hook"
-            : string(abi.encodePacked("0x", toHexString(address(key.hooks))));
         string memory fee = Descriptor.feeToPercentString(key.fee);
-        string memory tickToDecimal0 = Descriptor.tickToDecimalString(
-            tickLower,
-            key.tickSpacing,
-            SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)),
-            SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)),
-            false
-        );
-        string memory tickToDecimal1 = Descriptor.tickToDecimalString(
-            tickUpper,
-            key.tickSpacing,
-            SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)),
-            SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)),
-            false
-        );
-
-        assertEq(
-            token.name,
-            string(
-                abi.encodePacked(
-                    "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1
+        {
+            string memory tickToDecimal0 = Descriptor.tickToDecimalString(
+                tickLower,
+                key.tickSpacing,
+                SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)),
+                SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)),
+                false
+            );
+            string memory tickToDecimal1 = Descriptor.tickToDecimalString(
+                tickUpper,
+                key.tickSpacing,
+                SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)),
+                SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)),
+                false
+            );
+
+            assertEq(
+                token.name,
+                string(
+                    abi.encodePacked(
+                        "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1
+                    )
                 )
-            )
-        );
-        assertEq(
-            token.description,
-            string(
-                abi.encodePacked(
-                    unicode"This NFT represents a liquidity position in a Uniswap v4 ",
-                    symbol1,
-                    "-",
-                    symbol0,
-                    " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ",
-                    managerAddress,
-                    "\n",
-                    symbol1,
-                    " Address: ",
-                    currency1Address,
-                    "\n",
-                    symbol0,
-                    " Address: ",
-                    currency0Address,
-                    "\nHook Address: ",
-                    hookAddress,
-                    "\nFee Tier: ",
-                    fee,
-                    "\nToken ID: ",
-                    id,
-                    "\n\n",
-                    unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated."
+            );
+        }
+        {
+            string memory managerAddress = toHexString(address(manager));
+            string memory currency0Address = toHexString(Currency.unwrap(currency0));
+            string memory currency1Address = toHexString(Currency.unwrap(currency1));
+            string memory id = uintToString(tokenId);
+            string memory hookAddress = address(key.hooks) == address(0)
+                ? "No Hook"
+                : string(abi.encodePacked("0x", toHexString(address(key.hooks))));
+
+            assertEq(
+                token.description,
+                string(
+                    abi.encodePacked(
+                        abi.encodePacked(
+                            unicode"This NFT represents a liquidity position in a Uniswap v4 ",
+                            symbol1,
+                            "-",
+                            symbol0,
+                            " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ",
+                            managerAddress,
+                            "\n",
+                            symbol1,
+                            " Address: ",
+                            currency1Address
+                        ),
+                        abi.encodePacked(
+                            "\n",
+                            symbol0,
+                            " Address: ",
+                            currency0Address,
+                            "\nHook Address: ",
+                            hookAddress,
+                            "\nFee Tier: ",
+                            fee,
+                            "\nToken ID: ",
+                            id,
+                            "\n\n",
+                            unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated."
+                        )
+                    )
                 )
-            )
-        );
+            );
+        }
     }
 
     function test_native_tokenURI_succeeds() public {
         (nativeKey,) = initPool(CurrencyLibrary.ADDRESS_ZERO, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1);
         int24 tickLower = int24(nativeKey.tickSpacing);
         int24 tickUpper = int24(nativeKey.tickSpacing * 2);
-        uint256 amount0Desired = 100e18;
-        uint256 amount1Desired = 100e18;
-        uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts(
-            SQRT_PRICE_1_1,
-            TickMath.getSqrtPriceAtTick(tickLower),
-            TickMath.getSqrtPriceAtTick(tickUpper),
-            amount0Desired,
-            amount1Desired
-        );
-
-        PositionConfig memory config = PositionConfig({poolKey: nativeKey, tickLower: tickLower, tickUpper: tickUpper});
+        Token memory token;
         uint256 tokenId = lpm.nextTokenId();
-        mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES);
-
-        // The prefix length is calculated by converting the string to bytes and finding its length
-        uint256 prefixLength = bytes("data:application/json;base64,").length;
-
-        string memory uri = positionDescriptor.tokenURI(lpm, tokenId);
-        // Convert the uri to bytes
-        bytes memory uriBytes = bytes(uri);
-
-        // Slice the uri to get only the base64-encoded part
-        bytes memory base64Part = new bytes(uriBytes.length - prefixLength);
-
-        for (uint256 i = 0; i < base64Part.length; i++) {
-            base64Part[i] = uriBytes[i + prefixLength];
+        {
+            uint256 amount0Desired = 100e18;
+            uint256 amount1Desired = 100e18;
+            uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts(
+                SQRT_PRICE_1_1,
+                TickMath.getSqrtPriceAtTick(tickLower),
+                TickMath.getSqrtPriceAtTick(tickUpper),
+                amount0Desired,
+                amount1Desired
+            );
+
+            PositionConfig memory config =
+                PositionConfig({poolKey: nativeKey, tickLower: tickLower, tickUpper: tickUpper});
+            mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES);
+            // The prefix length is calculated by converting the string to bytes and finding its length
+            uint256 prefixLength = bytes("data:application/json;base64,").length;
+
+            string memory uri = positionDescriptor.tokenURI(lpm, tokenId);
+            // Convert the uri to bytes
+            bytes memory uriBytes = bytes(uri);
+
+            // Slice the uri to get only the base64-encoded part
+            bytes memory base64Part = new bytes(uriBytes.length - prefixLength);
+
+            for (uint256 i = 0; i < base64Part.length; i++) {
+                base64Part[i] = uriBytes[i + prefixLength];
+            }
+
+            // Decode the base64-encoded part
+            bytes memory decoded = Base64.decode(string(base64Part));
+            string memory json = string(decoded);
+
+            // decode json
+            bytes memory data = vm.parseJson(json);
+            token = abi.decode(data, (Token));
         }
 
-        // Decode the base64-encoded part
-        bytes memory decoded = Base64.decode(string(base64Part));
-        string memory json = string(decoded);
-
-        // decode json
-        bytes memory data = vm.parseJson(json);
-        Token memory token = abi.decode(data, (Token));
-
         // quote is currency1, base is currency0
         assertFalse(
             positionDescriptor.flipRatio(Currency.unwrap(nativeKey.currency0), Currency.unwrap(nativeKey.currency1))
@@ -239,70 +254,79 @@ contract PositionDescriptorTest is Test, PosmTestSetup {
             SafeCurrencyMetadata.currencySymbol(Currency.unwrap(nativeKey.currency0), nativeCurrencyLabel);
         string memory symbol1 =
             SafeCurrencyMetadata.currencySymbol(Currency.unwrap(nativeKey.currency1), nativeCurrencyLabel);
-        string memory managerAddress = toHexString(address(manager));
-        string memory currency0Address = Currency.unwrap(nativeKey.currency0) == address(0)
-            ? "Native"
-            : toHexString(Currency.unwrap(nativeKey.currency0));
-        string memory currency1Address = Currency.unwrap(nativeKey.currency1) == address(0)
-            ? "Native"
-            : toHexString(Currency.unwrap(nativeKey.currency1));
-        string memory id = uintToString(tokenId);
-        string memory hookAddress = address(nativeKey.hooks) == address(0)
-            ? "No Hook"
-            : string(abi.encodePacked("0x", toHexString(address(nativeKey.hooks))));
         string memory fee = Descriptor.feeToPercentString(nativeKey.fee);
-        string memory tickToDecimal0 = Descriptor.tickToDecimalString(
-            tickLower,
-            nativeKey.tickSpacing,
-            SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)),
-            SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)),
-            false
-        );
-        string memory tickToDecimal1 = Descriptor.tickToDecimalString(
-            tickUpper,
-            nativeKey.tickSpacing,
-            SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)),
-            SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)),
-            false
-        );
-
-        assertEq(
-            token.name,
-            string(
-                abi.encodePacked(
-                    "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1
+        {
+            string memory tickToDecimal0 = Descriptor.tickToDecimalString(
+                tickLower,
+                nativeKey.tickSpacing,
+                SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)),
+                SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)),
+                false
+            );
+            string memory tickToDecimal1 = Descriptor.tickToDecimalString(
+                tickUpper,
+                nativeKey.tickSpacing,
+                SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)),
+                SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)),
+                false
+            );
+
+            assertEq(
+                token.name,
+                string(
+                    abi.encodePacked(
+                        "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1
+                    )
                 )
-            )
-        );
-        assertEq(
-            token.description,
-            string(
-                abi.encodePacked(
-                    unicode"This NFT represents a liquidity position in a Uniswap v4 ",
-                    symbol1,
-                    "-",
-                    symbol0,
-                    " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ",
-                    managerAddress,
-                    "\n",
-                    symbol1,
-                    " Address: ",
-                    currency1Address,
-                    "\n",
-                    symbol0,
-                    " Address: ",
-                    currency0Address,
-                    "\nHook Address: ",
-                    hookAddress,
-                    "\nFee Tier: ",
-                    fee,
-                    "\nToken ID: ",
-                    id,
-                    "\n\n",
-                    unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated."
+            );
+        }
+        {
+            string memory managerAddress = toHexString(address(manager));
+            string memory currency0Address = Currency.unwrap(nativeKey.currency0) == address(0)
+                ? "Native"
+                : toHexString(Currency.unwrap(nativeKey.currency0));
+            string memory currency1Address = Currency.unwrap(nativeKey.currency1) == address(0)
+                ? "Native"
+                : toHexString(Currency.unwrap(nativeKey.currency1));
+            string memory id = uintToString(tokenId);
+            string memory hookAddress = address(nativeKey.hooks) == address(0)
+                ? "No Hook"
+                : string(abi.encodePacked("0x", toHexString(address(nativeKey.hooks))));
+
+            assertEq(
+                token.description,
+                string(
+                    abi.encodePacked(
+                        abi.encodePacked(
+                            unicode"This NFT represents a liquidity position in a Uniswap v4 ",
+                            symbol1,
+                            "-",
+                            symbol0,
+                            " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ",
+                            managerAddress,
+                            "\n",
+                            symbol1,
+                            " Address: ",
+                            currency1Address,
+                            "\n",
+                            symbol0
+                        ),
+                        abi.encodePacked(
+                            " Address: ",
+                            currency0Address,
+                            "\nHook Address: ",
+                            hookAddress,
+                            "\nFee Tier: ",
+                            fee,
+                            "\nToken ID: ",
+                            id,
+                            "\n\n",
+                            unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated."
+                        )
+                    )
                 )
-            )
-        );
+            );
+        }
     }
 
     function test_tokenURI_revertsWithInvalidTokenId() public {
@@ -322,7 +346,7 @@ contract PositionDescriptorTest is Test, PosmTestSetup {
         uint256 tokenId = lpm.nextTokenId();
         mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES);
 
-        vm.expectRevert(abi.encodeWithSelector(PositionDescriptor.InvalidTokenId.selector, tokenId + 1));
+        vm.expectRevert(abi.encodeWithSelector(IPositionDescriptor.InvalidTokenId.selector, tokenId + 1));
 
         positionDescriptor.tokenURI(lpm, tokenId + 1);
     }
diff --git a/test/StateViewTest.t.sol b/test/StateViewTest.t.sol
index 64a6e750..41f5e79f 100644
--- a/test/StateViewTest.t.sol
+++ b/test/StateViewTest.t.sol
@@ -18,8 +18,7 @@ import {FixedPoint128} from "@uniswap/v4-core/src/libraries/FixedPoint128.sol";
 import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol";
 import {Fuzzers} from "@uniswap/v4-core/src/test/Fuzzers.sol";
 import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
-
-import {StateView} from "../src/lens/StateView.sol";
+import {Deploy, IStateView} from "./shared/Deploy.sol";
 
 /// This test was taken from StateLibrary.t.sol in v4-core and adapted to use the StateView contract instead.
 contract StateViewTest is Test, Deployers, Fuzzers {
@@ -28,7 +27,7 @@ contract StateViewTest is Test, Deployers, Fuzzers {
 
     PoolId poolId;
 
-    StateView state;
+    IStateView state;
 
     function setUp() public {
         deployFreshManagerAndRouters();
@@ -39,7 +38,7 @@ contract StateViewTest is Test, Deployers, Fuzzers {
         poolId = key.toId();
         manager.initialize(key, SQRT_PRICE_1_1);
 
-        state = new StateView(manager);
+        state = Deploy.stateView(address(manager), hex"00");
     }
 
     function test_getSlot0() public {
diff --git a/test/UnorderedNonce.t.sol b/test/UnorderedNonce.t.sol
index f7b46b2d..0983876d 100644
--- a/test/UnorderedNonce.t.sol
+++ b/test/UnorderedNonce.t.sol
@@ -2,7 +2,7 @@
 pragma solidity ^0.8.20;
 
 import "forge-std/Test.sol";
-import {UnorderedNonce} from "../src/base/UnorderedNonce.sol";
+import {UnorderedNonce, IUnorderedNonce} from "../src/base/UnorderedNonce.sol";
 import {MockUnorderedNonce} from "./mocks/MockUnorderedNonce.sol";
 
 contract UnorderedNonceTest is Test {
@@ -17,11 +17,11 @@ contract UnorderedNonceTest is Test {
         unorderedNonce.spendNonce(address(this), 0);
         unorderedNonce.spendNonce(address(this), 1);
 
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 1);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 5);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 0);
         unorderedNonce.spendNonce(address(this), 4);
     }
@@ -30,9 +30,9 @@ contract UnorderedNonceTest is Test {
         unorderedNonce.spendNonce(address(this), 255);
         unorderedNonce.spendNonce(address(this), 256);
 
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 255);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 256);
     }
 
@@ -40,9 +40,9 @@ contract UnorderedNonceTest is Test {
         unorderedNonce.spendNonce(address(this), 2 ** 240);
         unorderedNonce.spendNonce(address(this), 2 ** 240 + 1);
 
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 2 ** 240);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 2 ** 240 + 1);
 
         unorderedNonce.spendNonce(address(this), 2 ** 240 + 2);
@@ -51,13 +51,13 @@ contract UnorderedNonceTest is Test {
     function testInvalidateFullWord() public {
         unorderedNonce.batchSpendNonces(0, 2 ** 256 - 1);
 
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 0);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 1);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 254);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 255);
         unorderedNonce.spendNonce(address(this), 256);
     }
@@ -68,9 +68,9 @@ contract UnorderedNonceTest is Test {
         unorderedNonce.spendNonce(address(this), 0);
         unorderedNonce.spendNonce(address(this), 254);
         unorderedNonce.spendNonce(address(this), 255);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 256);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), 511);
         unorderedNonce.spendNonce(address(this), 512);
     }
@@ -87,21 +87,21 @@ contract UnorderedNonceTest is Test {
         nonce = bound(nonce, 0, (word + 2) * 256);
 
         if ((word * 256) <= nonce && nonce < ((word + 1) * 256)) {
-            vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+            vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         }
         unorderedNonce.spendNonce(address(this), nonce);
     }
 
     function test_fuzz_UsingNonceTwiceFails(uint256 nonce) public {
         unorderedNonce.spendNonce(address(this), nonce);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.spendNonce(address(this), nonce);
     }
 
     function test_fuzz_UseTwoRandomNonces(uint256 first, uint256 second) public {
         unorderedNonce.spendNonce(address(this), first);
         if (first == second) {
-            vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+            vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
             unorderedNonce.spendNonce(address(this), second);
         } else {
             unorderedNonce.spendNonce(address(this), second);
@@ -110,13 +110,13 @@ contract UnorderedNonceTest is Test {
 
     function test_fuzz_revokeNonce(uint256 nonce) public {
         unorderedNonce.revokeNonce(nonce);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.revokeNonce(nonce);
     }
 
     function test_fuzz_revokeNonce_twoNonces(uint256 first, uint256 second) public {
         unorderedNonce.revokeNonce(first);
-        if (first == second) vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        if (first == second) vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         unorderedNonce.revokeNonce(second);
     }
 }
diff --git a/test/V4Quoter.t.sol b/test/V4Quoter.t.sol
index 3b8b03f8..1c0fd5d1 100644
--- a/test/V4Quoter.t.sol
+++ b/test/V4Quoter.t.sol
@@ -4,8 +4,7 @@ pragma solidity ^0.8.20;
 
 import {Test} from "forge-std/Test.sol";
 import {PathKey} from "../src/libraries/PathKey.sol";
-import {IV4Quoter} from "../src/interfaces/IV4Quoter.sol";
-import {V4Quoter} from "../src/lens/V4Quoter.sol";
+import {Deploy, IV4Quoter} from "../test/shared/Deploy.sol";
 import {BaseV4Quoter} from "../src/base/BaseV4Quoter.sol";
 
 // v4-core
@@ -39,7 +38,7 @@ contract QuoterTest is Test, Deployers {
 
     uint256 internal constant CONTROLLER_GAS_LIMIT = 500000;
 
-    V4Quoter quoter;
+    IV4Quoter quoter;
 
     PoolModifyLiquidityTest positionManager;
 
@@ -55,7 +54,7 @@ contract QuoterTest is Test, Deployers {
 
     function setUp() public {
         deployFreshManagerAndRouters();
-        quoter = new V4Quoter(IPoolManager(manager));
+        quoter = Deploy.v4Quoter(address(manager), hex"00");
         positionManager = new PoolModifyLiquidityTest(manager);
 
         // salts are chosen so that address(token0) < address(token1) && address(token1) < address(token2)
diff --git a/test/erc721Permit/ERC721Permit.permit.t.sol b/test/erc721Permit/ERC721Permit.permit.t.sol
index 0a741381..00a50e80 100644
--- a/test/erc721Permit/ERC721Permit.permit.t.sol
+++ b/test/erc721Permit/ERC721Permit.permit.t.sol
@@ -8,7 +8,7 @@ import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol";
 import {MockERC721Permit} from "../mocks/MockERC721Permit.sol";
 import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol";
 import {IERC721} from "forge-std/interfaces/IERC721.sol";
-import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol";
+import {IUnorderedNonce} from "../../src/interfaces/IUnorderedNonce.sol";
 
 contract ERC721PermitTest is Test {
     MockERC721Permit erc721Permit;
@@ -169,7 +169,7 @@ contract ERC721PermitTest is Test {
         bytes memory signature = abi.encodePacked(r, s, v);
 
         vm.startPrank(alice);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         erc721Permit.permit(bob, tokenIdAlice, block.timestamp, nonce, signature);
         vm.stopPrank();
     }
@@ -192,7 +192,7 @@ contract ERC721PermitTest is Test {
         bytes memory signature = abi.encodePacked(r, s, v);
 
         vm.startPrank(alice);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         erc721Permit.permit(bob, tokenIdAlice2, block.timestamp, nonce, signature);
         vm.stopPrank();
     }
diff --git a/test/erc721Permit/ERC721Permit.permitForAll.t.sol b/test/erc721Permit/ERC721Permit.permitForAll.t.sol
index c8150895..4cfa43a8 100644
--- a/test/erc721Permit/ERC721Permit.permitForAll.t.sol
+++ b/test/erc721Permit/ERC721Permit.permitForAll.t.sol
@@ -8,7 +8,7 @@ import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol";
 import {MockERC721Permit} from "../mocks/MockERC721Permit.sol";
 import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol";
 import {IERC721} from "forge-std/interfaces/IERC721.sol";
-import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol";
+import {IUnorderedNonce} from "../../src/interfaces/IUnorderedNonce.sol";
 
 contract ERC721PermitForAllTest is Test {
     MockERC721Permit erc721Permit;
@@ -141,7 +141,7 @@ contract ERC721PermitForAllTest is Test {
         bytes memory signature = abi.encodePacked(r, s, v);
 
         vm.startPrank(alice);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         erc721Permit.permitForAll(alice, bob, true, block.timestamp, nonce, signature);
         vm.stopPrank();
     }
@@ -292,7 +292,7 @@ contract ERC721PermitForAllTest is Test {
 
         // Nonce does not work with permitForAll
         vm.startPrank(bob);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature);
         vm.stopPrank();
     }
@@ -311,7 +311,7 @@ contract ERC721PermitForAllTest is Test {
 
         // Nonce does not work with permitForAll
         vm.startPrank(bob);
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature);
         vm.stopPrank();
     }
diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol
index d9c99cbb..a430185d 100644
--- a/test/mocks/MockBadSubscribers.sol
+++ b/test/mocks/MockBadSubscribers.sol
@@ -2,13 +2,13 @@
 pragma solidity ^0.8.20;
 
 import {ISubscriber} from "../../src/interfaces/ISubscriber.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
+import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
 import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
 import {PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol";
 
 /// @notice A subscriber contract that returns values from the subscriber entrypoints
 contract MockReturnDataSubscriber is ISubscriber {
-    PositionManager posm;
+    IPositionManager posm;
 
     uint256 public notifySubscribeCount;
     uint256 public notifyUnsubscribeCount;
@@ -20,7 +20,7 @@ contract MockReturnDataSubscriber is ISubscriber {
 
     uint256 memPtr;
 
-    constructor(PositionManager _posm) {
+    constructor(IPositionManager _posm) {
         posm = _posm;
     }
 
@@ -48,9 +48,7 @@ contract MockReturnDataSubscriber is ISubscriber {
         notifyModifyLiquidityCount++;
     }
 
-    function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued)
-        external
-    {
+    function notifyBurn(uint256, address, PositionInfo, uint256, BalanceDelta) external pure {
         return;
     }
 
@@ -61,13 +59,13 @@ contract MockReturnDataSubscriber is ISubscriber {
 
 /// @notice A subscriber contract that returns values from the subscriber entrypoints
 contract MockRevertSubscriber is ISubscriber {
-    PositionManager posm;
+    IPositionManager posm;
 
     error NotAuthorizedNotifer(address sender);
 
     error TestRevert(string);
 
-    constructor(PositionManager _posm) {
+    constructor(IPositionManager _posm) {
         posm = _posm;
     }
 
@@ -92,9 +90,7 @@ contract MockRevertSubscriber is ISubscriber {
         revert TestRevert("notifyModifyLiquidity");
     }
 
-    function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued)
-        external
-    {
+    function notifyBurn(uint256, address, PositionInfo, uint256, BalanceDelta) external pure {
         return;
     }
 
diff --git a/test/mocks/MockReenterHook.sol b/test/mocks/MockReenterHook.sol
index e6d6f2db..527475c9 100644
--- a/test/mocks/MockReenterHook.sol
+++ b/test/mocks/MockReenterHook.sol
@@ -5,10 +5,11 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
 import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
 import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
 import {BaseTestHooks} from "@uniswap/v4-core/src/test/BaseTestHooks.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
+import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
+import {IERC721} from "forge-std/interfaces/IERC721.sol";
 
 contract MockReenterHook is BaseTestHooks {
-    PositionManager posm;
+    IPositionManager posm;
 
     function beforeAddLiquidity(
         address,
@@ -21,8 +22,8 @@ contract MockReenterHook is BaseTestHooks {
         }
         (bytes4 selector, address owner, uint256 tokenId) = abi.decode(functionSelector, (bytes4, address, uint256));
 
-        if (selector == posm.transferFrom.selector) {
-            posm.transferFrom(owner, address(this), tokenId);
+        if (selector == IERC721(address(posm)).transferFrom.selector) {
+            IERC721(address(posm)).transferFrom(owner, address(this), tokenId);
         } else if (selector == posm.subscribe.selector) {
             posm.subscribe(tokenId, address(this), "");
         } else if (selector == posm.unsubscribe.selector) {
@@ -31,7 +32,7 @@ contract MockReenterHook is BaseTestHooks {
         return this.beforeAddLiquidity.selector;
     }
 
-    function setPosm(PositionManager _posm) external {
+    function setPosm(IPositionManager _posm) external {
         posm = _posm;
     }
 }
diff --git a/test/mocks/MockSubscriber.sol b/test/mocks/MockSubscriber.sol
index 64a167ab..fb126927 100644
--- a/test/mocks/MockSubscriber.sol
+++ b/test/mocks/MockSubscriber.sol
@@ -2,13 +2,13 @@
 pragma solidity ^0.8.20;
 
 import {ISubscriber} from "../../src/interfaces/ISubscriber.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
+import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
 import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
 import {PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol";
 
 /// @notice A subscriber contract that ingests updates from the v4 position manager
 contract MockSubscriber is ISubscriber {
-    PositionManager posm;
+    IPositionManager posm;
 
     uint256 public notifySubscribeCount;
     uint256 public notifyUnsubscribeCount;
@@ -23,7 +23,7 @@ contract MockSubscriber is ISubscriber {
 
     error NotImplemented();
 
-    constructor(PositionManager _posm) {
+    constructor(IPositionManager _posm) {
         posm = _posm;
     }
 
@@ -47,10 +47,7 @@ contract MockSubscriber is ISubscriber {
         feesAccrued = _feesAccrued;
     }
 
-    function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued)
-        external
-        onlyByPosm
-    {
+    function notifyBurn(uint256, address, PositionInfo, uint256, BalanceDelta) external onlyByPosm {
         notifyBurnCount++;
     }
 }
diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol
index dd99d86b..f9eea6a2 100644
--- a/test/position-managers/Execute.t.sol
+++ b/test/position-managers/Execute.t.sol
@@ -16,9 +16,9 @@ import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol
 import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
 
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
+import {IERC721} from "forge-std/interfaces/IERC721.sol";
 
 import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {PositionConfig} from "../shared/PositionConfig.sol";
 import {ActionConstants} from "../../src/libraries/ActionConstants.sol";
 import {Actions} from "../../src/libraries/Actions.sol";
@@ -232,7 +232,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         // old position was burned
         vm.expectRevert();
-        lpm.ownerOf(tokenId);
+        IERC721(address(lpm)).ownerOf(tokenId);
 
         {
             // old position has no liquidity
@@ -241,7 +241,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers {
 
             // new token was minted
             uint256 newTokenId = lpm.nextTokenId() - 1;
-            assertEq(lpm.ownerOf(newTokenId), address(this));
+            assertEq(IERC721(address(lpm)).ownerOf(newTokenId), address(this));
 
             // new token has expected liquidity
 
diff --git a/test/position-managers/FeeCollection.t.sol b/test/position-managers/FeeCollection.t.sol
index c2508536..16976301 100644
--- a/test/position-managers/FeeCollection.t.sol
+++ b/test/position-managers/FeeCollection.t.sol
@@ -13,7 +13,6 @@ import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
 
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
 
-import {PositionManager} from "../../src/PositionManager.sol";
 import {PositionConfig} from "../shared/PositionConfig.sol";
 
 import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol";
diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol
index a0264d2b..bfda58e7 100644
--- a/test/position-managers/IncreaseLiquidity.t.sol
+++ b/test/position-managers/IncreaseLiquidity.t.sol
@@ -19,7 +19,6 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
 
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
 
-import {PositionManager} from "../../src/PositionManager.sol";
 import {DeltaResolver} from "../../src/base/DeltaResolver.sol";
 import {PositionConfig} from "../shared/PositionConfig.sol";
 import {SlippageCheck} from "../../src/libraries/SlippageCheck.sol";
diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol
index c79f74de..2d7ef94e 100644
--- a/test/position-managers/NativeToken.t.sol
+++ b/test/position-managers/NativeToken.t.sol
@@ -20,12 +20,11 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
 import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
 
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
+import {IERC721} from "forge-std/interfaces/IERC721.sol";
 import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
-import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
 
 import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
 import {Actions} from "../../src/libraries/Actions.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {ActionConstants} from "../../src/libraries/ActionConstants.sol";
 
 import {MockSubscriber} from "../mocks/MockSubscriber.sol";
@@ -250,7 +249,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         // OZ 721 will revert if the token does not exist
         vm.expectRevert();
-        lpm.ownerOf(1);
+        IERC721(address(lpm)).ownerOf(1);
 
         // no tokens were lost, TODO: fuzzer showing off by 1 sometimes
         assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei);
@@ -308,7 +307,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         // OZ 721 will revert if the token does not exist
         vm.expectRevert();
-        lpm.ownerOf(1);
+        IERC721(address(lpm)).ownerOf(1);
 
         // no tokens were lost, TODO: fuzzer showing off by 1 sometimes
         assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei);
@@ -356,7 +355,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         // OZ 721 will revert if the token does not exist
         vm.expectRevert();
-        lpm.ownerOf(1);
+        IERC721(address(lpm)).ownerOf(1);
 
         // no tokens were lost, TODO: fuzzer showing off by 1 sometimes
         assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei);
@@ -409,7 +408,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         // OZ 721 will revert if the token does not exist
         vm.expectRevert();
-        lpm.ownerOf(1);
+        IERC721(address(lpm)).ownerOf(1);
 
         // no tokens were lost, TODO: fuzzer showing off by 1 sometimes
         assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei);
diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol
index 0ce9a5b9..e60c1d27 100644
--- a/test/position-managers/Permit.t.sol
+++ b/test/position-managers/Permit.t.sol
@@ -15,7 +15,7 @@ import {SignatureVerification} from "permit2/src/libraries/SignatureVerification
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
 import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol";
 import {ERC721Permit_v4} from "../../src/base/ERC721Permit_v4.sol";
-import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol";
+import {IUnorderedNonce} from "../../src/interfaces/IUnorderedNonce.sol";
 
 import {PositionConfig} from "../shared/PositionConfig.sol";
 import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
@@ -234,7 +234,7 @@ contract PermitTest is Test, PosmTestSetup {
         (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest);
         bytes memory signature = abi.encodePacked(r, s, v);
 
-        vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector);
+        vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector);
         lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature);
     }
 
diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol
index fec63940..a88d2a2d 100644
--- a/test/position-managers/PositionManager.gas.t.sol
+++ b/test/position-managers/PositionManager.gas.t.sol
@@ -15,9 +15,8 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
 
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
 
-import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
+import {IPositionManager, IPoolInitializer} from "../../src/interfaces/IPositionManager.sol";
 import {Actions} from "../../src/libraries/Actions.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {PositionConfig} from "../shared/PositionConfig.sol";
 import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol";
 import {Planner, Plan} from "../shared/Planner.sol";
@@ -396,7 +395,7 @@ contract PosMGasTest is Test, PosmTestSetup {
 
         // Use multicall to initialize a pool and mint liquidity
         bytes[] memory calls = new bytes[](2);
-        calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1);
+        calls[0] = abi.encodeWithSelector(IPoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1);
 
         config = PositionConfig({
             poolKey: key,
diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol
index 66475861..f2e5674e 100644
--- a/test/position-managers/PositionManager.modifyLiquidities.t.sol
+++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol
@@ -3,6 +3,7 @@ pragma solidity ^0.8.24;
 
 import "forge-std/Test.sol";
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
+import {IERC721} from "forge-std/interfaces/IERC721.sol";
 
 import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol";
 import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
@@ -26,7 +27,6 @@ import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
 import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol";
 import {ReentrancyLock} from "../../src/base/ReentrancyLock.sol";
 import {Actions} from "../../src/libraries/Actions.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {PositionConfig} from "../shared/PositionConfig.sol";
 import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol";
 
@@ -123,8 +123,8 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         liquidity = lpm.getPositionLiquidity(hookTokenId);
         assertEq(liquidity, newLiquidity);
 
-        assertEq(lpm.ownerOf(tokenId), address(this)); // original position owned by this contract
-        assertEq(lpm.ownerOf(hookTokenId), address(hookModifyLiquidities)); // hook position owned by hook
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this)); // original position owned by this contract
+        assertEq(IERC721(address(lpm)).ownerOf(hookTokenId), address(hookModifyLiquidities)); // hook position owned by hook
     }
 
     /// @dev hook must be approved to increase liquidity
@@ -134,7 +134,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         mint(config, initialLiquidity, address(this), ZERO_BYTES);
 
         // approve the hook for increasing liquidity
-        lpm.approve(address(hookModifyLiquidities), tokenId);
+        IERC721(address(lpm)).approve(address(hookModifyLiquidities), tokenId);
 
         // hook increases liquidity in beforeSwap via hookData
         uint256 newLiquidity = 10e18;
@@ -154,7 +154,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         mint(config, initialLiquidity, address(this), ZERO_BYTES);
 
         // approve the hook for decreasing liquidity
-        lpm.approve(address(hookModifyLiquidities), tokenId);
+        IERC721(address(lpm)).approve(address(hookModifyLiquidities), tokenId);
 
         // hook decreases liquidity in beforeSwap via hookData
         uint256 liquidityToDecrease = 10e18;
@@ -174,7 +174,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         mint(config, initialLiquidity, address(this), ZERO_BYTES);
 
         // approve the hook for collecting liquidity
-        lpm.approve(address(hookModifyLiquidities), tokenId);
+        IERC721(address(lpm)).approve(address(hookModifyLiquidities), tokenId);
 
         // donate to generate revenue
         uint256 feeRevenue0 = 1e18;
@@ -212,7 +212,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         BalanceDelta mintDelta = hookModifyLiquidities.deltas(hookModifyLiquidities.numberDeltasReturned() - 1);
 
         // approve the hook for burning liquidity
-        lpm.approve(address(hookModifyLiquidities), tokenId);
+        IERC721(address(lpm)).approve(address(hookModifyLiquidities), tokenId);
 
         uint256 balance0HookBefore = currency0.balanceOf(address(hookModifyLiquidities));
         uint256 balance1HookBefore = currency1.balanceOf(address(hookModifyLiquidities));
@@ -227,7 +227,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         assertEq(liquidity, 0);
         // 721 will revert if the token does not exist
         vm.expectRevert();
-        lpm.ownerOf(tokenId);
+        IERC721(address(lpm)).ownerOf(tokenId);
 
         // hook claimed the burned liquidity
         assertEq(
@@ -417,7 +417,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         // The full eth amount was "spent" because some was wrapped into weth and refunded.
         assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 102 ether, 1 wei);
         assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei);
-        assertEq(lpm.ownerOf(tokenId), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this));
         assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount);
         assertEq(_WETH9.balanceOf(address(lpm)), 0);
         assertEq(address(lpm).balance, 0);
@@ -479,7 +479,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         // Approx 100 eth was spent because the extra 2 were refunded.
         assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 100 ether, 1 wei);
         assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei);
-        assertEq(lpm.ownerOf(tokenId), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this));
         assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount);
         assertEq(_WETH9.balanceOf(address(lpm)), 0);
         assertEq(address(lpm).balance, 0);
@@ -539,7 +539,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         // The full eth amount was "spent" because some was wrapped into weth and refunded.
         assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 100 ether, 1 wei);
         assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei);
-        assertEq(lpm.ownerOf(tokenId), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this));
         assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount);
         assertEq(_WETH9.balanceOf(address(lpm)), 0);
         assertEq(address(lpm).balance, 0);
@@ -756,7 +756,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
 
         uint256 fotBalanceAfter = Currency.wrap(address(fotToken)).balanceOf(address(this));
 
-        assertEq(lpm.ownerOf(tokenId), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this));
         assertEq(lpm.getPositionLiquidity(tokenId), expectedLiquidity);
         assertEq(fotBalanceBefore - fotBalanceAfter, 1000e18);
     }
@@ -768,7 +768,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
 
         mint(fotConfig, initialLiquidity, address(this), ZERO_BYTES);
 
-        assertEq(lpm.ownerOf(tokenId), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this));
         assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity);
 
         Plan memory planner = Planner.init();
@@ -801,7 +801,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
 
         mint(fotConfig, initialLiquidity, address(this), ZERO_BYTES);
 
-        assertEq(lpm.ownerOf(tokenId), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this));
         assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity);
 
         // Use a 1% fee.
@@ -837,6 +837,16 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity + newLiquidity);
     }
 
+    struct BalanceDiff {
+        uint256 _before;
+        uint256 _after;
+    }
+
+    struct Balance {
+        uint256 _0;
+        uint256 _1;
+    }
+
     function test_fuzz_mintFromDeltas_burn_fot(
         uint256 bips,
         uint256 amount0,
@@ -846,7 +856,6 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
     ) public {
         bips = bound(bips, 1, 10_000);
         MockFOT(address(fotToken)).setFee(bips);
-
         tickLower = int24(
             bound(
                 tickLower,
@@ -867,28 +876,30 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         vm.assume(tickUpper > tickLower);
 
         (uint160 sqrtPriceX96,,,) = manager.getSlot0(fotKey.toId());
-        uint128 maxLiquidityPerTick = Pool.tickSpacingToMaxLiquidityPerTick(fotKey.tickSpacing);
-
-        (uint256 maxAmount0, uint256 maxAmount1) = LiquidityAmounts.getAmountsForLiquidity(
-            sqrtPriceX96,
-            TickMath.getSqrtPriceAtTick(tickLower),
-            TickMath.getSqrtPriceAtTick(tickUpper),
-            maxLiquidityPerTick
-        );
+        {
+            uint128 maxLiquidityPerTick = Pool.tickSpacingToMaxLiquidityPerTick(fotKey.tickSpacing);
+            (uint256 maxAmount0, uint256 maxAmount1) = LiquidityAmounts.getAmountsForLiquidity(
+                sqrtPriceX96,
+                TickMath.getSqrtPriceAtTick(tickLower),
+                TickMath.getSqrtPriceAtTick(tickUpper),
+                maxLiquidityPerTick
+            );
 
-        maxAmount0 = maxAmount0 == 0 ? 1 : maxAmount0 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount0;
-        maxAmount1 = maxAmount1 == 0 ? 1 : maxAmount1 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount1;
-        amount0 = bound(amount0, 1, maxAmount0);
-        amount1 = bound(amount1, 1, maxAmount1);
+            maxAmount0 = maxAmount0 == 0 ? 1 : maxAmount0 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount0;
+            maxAmount1 = maxAmount1 == 0 ? 1 : maxAmount1 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount1;
+            amount0 = bound(amount0, 1, maxAmount0);
+            amount1 = bound(amount1, 1, maxAmount1);
+        }
 
         uint256 tokenId = lpm.nextTokenId();
 
-        uint256 balance0 = fotKey.currency0.balanceOf(address(this));
-        uint256 balance1 = fotKey.currency1.balanceOf(address(this));
-        uint256 balance0PM = fotKey.currency0.balanceOf(address(manager));
-        uint256 balance1PM = fotKey.currency1.balanceOf(address(manager));
+        BalanceDiff memory balance0 = BalanceDiff(fotKey.currency0.balanceOf(address(this)), 0);
+        BalanceDiff memory balance1 = BalanceDiff(fotKey.currency1.balanceOf(address(this)), 0);
+        BalanceDiff memory balance0PM = BalanceDiff(fotKey.currency0.balanceOf(address(manager)), 0);
+        BalanceDiff memory balance1PM = BalanceDiff(fotKey.currency1.balanceOf(address(manager)), 0);
 
         Plan memory planner = Planner.init();
+
         planner.add(Actions.SETTLE, abi.encode(fotKey.currency0, amount0, true));
         planner.add(Actions.SETTLE, abi.encode(fotKey.currency1, amount1, true));
         planner.add(
@@ -906,60 +917,69 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF
         // take the excess of each currency
         planner.add(Actions.TAKE_PAIR, abi.encode(fotKey.currency0, fotKey.currency1, ActionConstants.MSG_SENDER));
 
-        bytes memory actions = planner.encode();
-
-        bool currency0IsFOT = fotKey.currency0 == Currency.wrap(address(fotToken));
-        bool positionIsEntirelyInOtherToken = currency0IsFOT
-            ? tickUpper <= TickMath.getTickAtSqrtPrice(sqrtPriceX96)
-            : tickLower > TickMath.getTickAtSqrtPrice(sqrtPriceX96);
-
-        if (bips == 10000 && !positionIsEntirelyInOtherToken) {
+        // needed to remove variables because of stack too deep
+        // Read below as:
+        // bool currency0IsFOT = fotKey.currency0 == Currency.wrap(address(fotToken));
+        // bool positionIsEntirelyInOtherToken = currency0IsFOT
+        //     ? tickUpper <= TickMath.getTickAtSqrtPrice(sqrtPriceX96)
+        //     : tickLower > TickMath.getTickAtSqrtPrice(sqrtPriceX96);
+        // if (bips == 10000 && !positionIsEntirelyInOtherToken) {
+        if (
+            bips == 10000
+                && !(
+                    fotKey.currency0 == Currency.wrap(address(fotToken))
+                        ? tickUpper <= TickMath.getTickAtSqrtPrice(sqrtPriceX96)
+                        : tickLower > TickMath.getTickAtSqrtPrice(sqrtPriceX96)
+                )
+        ) {
             vm.expectRevert(Position.CannotUpdateEmptyPosition.selector);
-            lpm.modifyLiquidities(actions, _deadline);
+            lpm.modifyLiquidities(planner.encode(), _deadline);
         } else {
             // MINT FROM DELTAS.
-            lpm.modifyLiquidities(actions, _deadline);
+            lpm.modifyLiquidities(planner.encode(), _deadline);
 
-            uint256 balance0After = fotKey.currency0.balanceOf(address(this));
-            uint256 balance1After = fotKey.currency1.balanceOf(address(this));
-            uint256 balance0PMAfter = fotKey.currency0.balanceOf(address(manager));
-            uint256 balance1PMAfter = fotKey.currency1.balanceOf(address(manager));
+            balance0._after = fotKey.currency0.balanceOf(address(this));
+            balance1._after = fotKey.currency1.balanceOf(address(this));
+            balance0PM._after = fotKey.currency0.balanceOf(address(manager));
+            balance1PM._after = fotKey.currency1.balanceOf(address(manager));
 
             // Calculate the expected resulting balances used to create liquidity after the fee is applied.
-            uint256 amountInFOT = currency0IsFOT ? amount0 : amount1;
-            uint256 expectedFee = amountInFOT.calculatePortion(bips);
-            (uint256 expected0, uint256 expected1) = currency0IsFOT
-                ? (balance0 - balance0After - expectedFee, balance1 - balance1After)
-                : (balance0 - balance0After, balance1 - balance1After - expectedFee);
-
-            assertEq(expected0, balance0PMAfter - balance0PM);
-            assertEq(expected1, balance1PMAfter - balance1PM);
-
-            // the liquidity that was created is a diff of the balance change
-            uint128 expectedLiquidity = LiquidityAmounts.getLiquidityForAmounts(
-                sqrtPriceX96,
-                TickMath.getSqrtPriceAtTick(tickLower),
-                TickMath.getSqrtPriceAtTick(tickUpper),
-                expected0,
-                expected1
-            );
-
-            assertEq(lpm.ownerOf(tokenId), address(this));
-            assertEq(lpm.getPositionLiquidity(tokenId), expectedLiquidity);
-
+            Balance memory expected;
+            {
+                bool currency0IsFOT = fotKey.currency0 == Currency.wrap(address(fotToken));
+                uint256 expectedFee = (currency0IsFOT ? amount0 : amount1).calculatePortion(bips);
+                (expected._0, expected._1) = currency0IsFOT
+                    ? (balance0._before - balance0._after - expectedFee, balance1._before - balance1._after)
+                    : (balance0._before - balance0._after, balance1._before - balance1._after - expectedFee);
+            }
+            assertEq(expected._0, balance0PM._after - balance0PM._before);
+            assertEq(expected._1, balance1PM._after - balance1PM._before);
+            {
+                // the liquidity that was created is a diff of the balance change
+                uint128 expectedLiquidity = LiquidityAmounts.getLiquidityForAmounts(
+                    sqrtPriceX96,
+                    TickMath.getSqrtPriceAtTick(tickLower),
+                    TickMath.getSqrtPriceAtTick(tickUpper),
+                    expected._0,
+                    expected._1
+                );
+
+                assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this));
+                assertEq(lpm.getPositionLiquidity(tokenId), expectedLiquidity);
+            }
             // BURN.
             planner = Planner.init();
             // Note that the slippage does not include the fee from the transfer.
             planner.add(
                 Actions.BURN_POSITION,
-                abi.encode(tokenId, expected0 == 0 ? 0 : expected0 - 1, expected1 == 0 ? 0 : expected1 - 1, ZERO_BYTES)
+                abi.encode(
+                    tokenId, expected._0 == 0 ? 0 : expected._0 - 1, expected._1 == 0 ? 0 : expected._1 - 1, ZERO_BYTES
+                )
             );
 
             planner.add(Actions.TAKE_PAIR, abi.encode(fotKey.currency0, fotKey.currency1, ActionConstants.MSG_SENDER));
 
-            actions = planner.encode();
-
-            lpm.modifyLiquidities(actions, _deadline);
+            lpm.modifyLiquidities(planner.encode(), _deadline);
 
             assertEq(lpm.getPositionLiquidity(tokenId), 0);
         }
diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol
index 087c2102..18efd831 100644
--- a/test/position-managers/PositionManager.multicall.t.sol
+++ b/test/position-managers/PositionManager.multicall.t.sol
@@ -16,18 +16,17 @@ import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
 import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
 
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
+import {IERC721} from "forge-std/interfaces/IERC721.sol";
 
-import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
-import {PoolInitializer} from "../../src/base/PoolInitializer.sol";
+import {IPositionManager, IPoolInitializer} from "../../src/interfaces/IPositionManager.sol";
 import {Actions} from "../../src/libraries/Actions.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {PositionConfig} from "../shared/PositionConfig.sol";
 import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol";
 import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol";
 import {Planner, Plan} from "../shared/Planner.sol";
 import {PosmTestSetup} from "../shared/PosmTestSetup.sol";
 import {Permit2SignatureHelpers} from "../shared/Permit2SignatureHelpers.sol";
-import {Permit2Forwarder} from "../../src/base/Permit2Forwarder.sol";
+import {Permit2Forwarder, IPermit2Forwarder} from "../../src/base/Permit2Forwarder.sol";
 import {ActionConstants} from "../../src/libraries/ActionConstants.sol";
 import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol";
 
@@ -98,7 +97,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest
 
         // Use multicall to initialize a pool and mint liquidity
         bytes[] memory calls = new bytes[](2);
-        calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1);
+        calls[0] = abi.encodeWithSelector(IPoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1);
 
         config = PositionConfig({
             poolKey: key,
@@ -139,7 +138,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest
 
         // Use multicall to initialize the pool again.
         bytes[] memory calls = new bytes[](2);
-        calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1);
+        calls[0] = abi.encodeWithSelector(IPoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1);
 
         config = PositionConfig({
             poolKey: key,
@@ -185,7 +184,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest
 
         // Use multicall to initialize a pool and mint liquidity
         bytes[] memory calls = new bytes[](2);
-        calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1);
+        calls[0] = abi.encodeWithSelector(IPoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1);
 
         config = PositionConfig({
             poolKey: key,
@@ -341,7 +340,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest
 
         assertEq(_amount, permitAmount);
         assertEq(liquidity, 10e18);
-        assertEq(lpm.ownerOf(tokenId), bob);
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), bob);
     }
 
     function test_multicall_permit_batch_mint() public {
@@ -398,7 +397,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest
         assertEq(_amount0, permitAmount);
         assertEq(_amount1, permitAmount);
         assertEq(liquidity, 10e18);
-        assertEq(lpm.ownerOf(tokenId), bob);
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), bob);
     }
 
     /// @notice test that a front-ran permit does not fail a multicall with permit
@@ -440,20 +439,20 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest
 
         // charlie tries to mint an LP token with multicall(permit, permit, mint)
         bytes[] memory calls = new bytes[](3);
-        calls[0] = abi.encodeWithSelector(Permit2Forwarder(lpm).permit.selector, charlie, permit0, sig0);
-        calls[1] = abi.encodeWithSelector(Permit2Forwarder(lpm).permit.selector, charlie, permit1, sig1);
+        calls[0] = abi.encodeWithSelector(IPermit2Forwarder.permit.selector, charlie, permit0, sig0);
+        calls[1] = abi.encodeWithSelector(IPermit2Forwarder.permit.selector, charlie, permit1, sig1);
         bytes memory mintCall = getMintEncoded(config, 10e18, charlie, ZERO_BYTES);
         calls[2] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, mintCall, _deadline);
 
         uint256 tokenId = lpm.nextTokenId();
         vm.expectRevert();
-        lpm.ownerOf(tokenId); // token does not exist
+        IERC721(address(lpm)).ownerOf(tokenId); // token does not exist
 
         bytes[] memory results = lpm.multicall(calls);
         assertEq(results[0], abi.encode(abi.encodeWithSelector(InvalidNonce.selector)));
         assertEq(results[1], abi.encode(abi.encodeWithSelector(InvalidNonce.selector)));
 
-        assertEq(lpm.ownerOf(tokenId), charlie);
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), charlie);
     }
 
     /// @notice test that a front-ran permitBatch does not fail a multicall with permitBatch
@@ -492,17 +491,17 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest
 
         // charlie tries to mint an LP token with multicall(permitBatch, mint)
         bytes[] memory calls = new bytes[](2);
-        calls[0] = abi.encodeWithSelector(Permit2Forwarder(lpm).permitBatch.selector, charlie, permit, sig);
+        calls[0] = abi.encodeWithSelector(lpm.permitBatch.selector, charlie, permit, sig);
         bytes memory mintCall = getMintEncoded(config, 10e18, charlie, ZERO_BYTES);
         calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, mintCall, _deadline);
 
         uint256 tokenId = lpm.nextTokenId();
         vm.expectRevert();
-        lpm.ownerOf(tokenId); // token does not exist
+        IERC721(address(lpm)).ownerOf(tokenId); // token does not exist
 
         bytes[] memory results = lpm.multicall(calls);
         assertEq(results[0], abi.encode(abi.encodeWithSelector(InvalidNonce.selector)));
 
-        assertEq(lpm.ownerOf(tokenId), charlie);
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), charlie);
     }
 }
diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol
index 5dd2aea8..b69d0a86 100644
--- a/test/position-managers/PositionManager.notifier.t.sol
+++ b/test/position-managers/PositionManager.notifier.t.sol
@@ -22,6 +22,7 @@ import {INotifier} from "../../src/interfaces/INotifier.sol";
 import {MockReturnDataSubscriber, MockRevertSubscriber} from "../mocks/MockBadSubscribers.sol";
 import {PositionInfoLibrary, PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol";
 import {MockReenterHook} from "../mocks/MockReenterHook.sol";
+import {IERC721} from "forge-std/interfaces/IERC721.sol";
 
 contract PositionManagerNotifierTest is Test, PosmTestSetup {
     using PoolIdLibrary for PoolKey;
@@ -90,7 +91,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -109,7 +110,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         vm.expectRevert(INotifier.NoCodeSubscriber.selector);
@@ -122,7 +123,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         // successfully subscribe
@@ -141,7 +142,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -170,7 +171,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -197,7 +198,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -220,7 +221,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -228,7 +229,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
         assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true);
         assertEq(address(lpm.subscriber(tokenId)), address(sub));
 
-        lpm.transferFrom(alice, bob, tokenId);
+        IERC721(address(lpm)).transferFrom(alice, bob, tokenId);
 
         assertEq(sub.notifyUnsubscribeCount(), 1);
         assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false);
@@ -241,7 +242,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -252,7 +253,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
         vm.etch(address(sub), ZERO_BYTES);
 
         // unsubscribe happens anyway
-        lpm.transferFrom(alice, bob, tokenId);
+        IERC721(address(lpm)).transferFrom(alice, bob, tokenId);
 
         assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false);
         assertEq(address(lpm.subscriber(tokenId)), address(0));
@@ -264,7 +265,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -272,7 +273,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
         assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true);
         assertEq(address(lpm.subscriber(tokenId)), address(sub));
 
-        lpm.safeTransferFrom(alice, bob, tokenId);
+        IERC721(address(lpm)).safeTransferFrom(alice, bob, tokenId);
 
         assertEq(sub.notifyUnsubscribeCount(), 1);
         assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false);
@@ -285,7 +286,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -296,7 +297,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
         vm.etch(address(sub), ZERO_BYTES);
 
         // unsubscribe happens anyway
-        lpm.safeTransferFrom(alice, bob, tokenId);
+        IERC721(address(lpm)).safeTransferFrom(alice, bob, tokenId);
 
         assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false);
         assertEq(address(lpm.subscriber(tokenId)), address(0));
@@ -308,7 +309,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -316,7 +317,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
         assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true);
         assertEq(address(lpm.subscriber(tokenId)), address(sub));
 
-        lpm.safeTransferFrom(alice, bob, tokenId, "");
+        IERC721(address(lpm)).safeTransferFrom(alice, bob, tokenId, "");
 
         assertEq(sub.notifyUnsubscribeCount(), 1);
         assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false);
@@ -329,7 +330,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -347,7 +348,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(badSubscriber), ZERO_BYTES);
@@ -367,7 +368,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -467,7 +468,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         vm.expectRevert(INotifier.NotSubscribed.selector);
@@ -480,7 +481,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -499,7 +500,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), subData);
@@ -516,7 +517,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         revertSubscriber.setRevert(true);
@@ -539,7 +540,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(revertSubscriber), ZERO_BYTES);
@@ -574,7 +575,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), subData);
@@ -601,7 +602,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
 
         // approve this contract to operate on alices liq
         vm.startPrank(alice);
-        lpm.approve(address(this), tokenId);
+        IERC721(address(lpm)).approve(address(this), tokenId);
         vm.stopPrank();
 
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
@@ -626,7 +627,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
         bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData);
 
         // approve hook as it should not revert because it does not have permissions
-        lpm.approve(address(reenterHook), tokenId);
+        IERC721(address(lpm)).approve(address(reenterHook), tokenId);
         // subscribe as it should not revert because there is no subscriber
         lpm.subscribe(tokenId, address(sub), ZERO_BYTES);
 
@@ -651,7 +652,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
         bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData);
 
         // approve hook as it should not revert because it does not have permissions
-        lpm.approve(address(reenterHook), tokenId);
+        IERC721(address(lpm)).approve(address(reenterHook), tokenId);
 
         // should revert since the pool manager is unlocked
         vm.expectRevert(
@@ -670,11 +671,11 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup {
         uint256 tokenId = lpm.nextTokenId();
         mint(reenterConfig, 10e18, address(this), ZERO_BYTES);
 
-        bytes memory hookData = abi.encode(lpm.transferFrom.selector, address(this), tokenId);
+        bytes memory hookData = abi.encode(IERC721(address(lpm)).transferFrom.selector, address(this), tokenId);
         bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData);
 
         // approve hook as it should not revert because it does not have permissions
-        lpm.approve(address(reenterHook), tokenId);
+        IERC721(address(lpm)).approve(address(reenterHook), tokenId);
 
         // should revert since the pool manager is unlocked
         vm.expectRevert(
diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol
index 7db64e9b..a0dc970d 100644
--- a/test/position-managers/PositionManager.t.sol
+++ b/test/position-managers/PositionManager.t.sol
@@ -19,10 +19,10 @@ import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
 import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
 
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
+import {IERC721} from "forge-std/interfaces/IERC721.sol";
 
 import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
 import {Actions} from "../../src/libraries/Actions.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {DeltaResolver} from "../../src/base/DeltaResolver.sol";
 import {PositionConfig} from "../shared/PositionConfig.sol";
 import {SlippageCheck} from "../../src/libraries/SlippageCheck.sol";
@@ -124,7 +124,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         assertEq(tokenId, 1);
         assertEq(lpm.nextTokenId(), 2);
-        assertEq(lpm.ownerOf(tokenId), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this));
 
         uint256 liquidity = lpm.getPositionLiquidity(tokenId);
 
@@ -159,7 +159,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         uint256 balance1After = currency1.balanceOfSelf();
 
         assertEq(tokenId, 1);
-        assertEq(lpm.ownerOf(1), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(1), address(this));
 
         assertEq(uint256(int256(-delta.amount0())), amount0Desired);
         assertEq(uint256(int256(-delta.amount1())), amount1Desired);
@@ -194,7 +194,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         uint256 balance1After = currency1.balanceOfSelf();
 
         assertEq(tokenId, 1);
-        assertEq(lpm.ownerOf(1), alice);
+        assertEq(IERC721(address(lpm)).ownerOf(1), alice);
 
         assertEq(uint256(int256(-delta.amount0())), amount0Desired);
         assertEq(uint256(int256(-delta.amount1())), amount1Desired);
@@ -219,7 +219,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         BalanceDelta delta = getLastDelta();
 
         assertEq(tokenId, 1);
-        assertEq(lpm.ownerOf(tokenId), alice);
+        assertEq(IERC721(address(lpm)).ownerOf(tokenId), alice);
 
         // alice was not the payer
         assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0())));
@@ -355,7 +355,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         PositionConfig memory config =
             PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper});
         assertEq(tokenId, 1);
-        assertEq(lpm.ownerOf(1), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(1), address(this));
 
         uint256 liquidity = lpm.getPositionLiquidity(tokenId);
 
@@ -381,7 +381,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         // 721 will revert if the token does not exist
         vm.expectRevert();
-        lpm.ownerOf(1);
+        IERC721(address(lpm)).ownerOf(1);
 
         // no tokens were lost, TODO: fuzzer showing off by 1 sometimes
         // Potentially because we round down in core. I believe this is known in V3. But let's check!
@@ -399,7 +399,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         PositionConfig memory config =
             PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper});
         assertEq(tokenId, 1);
-        assertEq(lpm.ownerOf(1), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(1), address(this));
 
         uint256 liquidity = lpm.getPositionLiquidity(tokenId);
 
@@ -432,7 +432,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         // OZ 721 will revert if the token does not exist
         vm.expectRevert();
-        lpm.ownerOf(1);
+        IERC721(address(lpm)).ownerOf(1);
 
         // no tokens were lost, TODO: fuzzer showing off by 1 sometimes
         // Potentially because we round down in core. I believe this is known in V3. But let's check!
@@ -750,7 +750,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         BalanceDelta mintDelta = getLastDelta();
 
         // transfer to alice
-        lpm.transferFrom(address(this), alice, tokenId);
+        IERC721(address(lpm)).transferFrom(address(this), alice, tokenId);
 
         // alice can burn the position
         bytes memory calls = getBurnEncoded(tokenId, config, ZERO_BYTES);
@@ -763,7 +763,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
 
         // token was burned and does not exist anymore
         vm.expectRevert();
-        lpm.ownerOf(tokenId);
+        IERC721(address(lpm)).ownerOf(tokenId);
 
         // alice received the principal liquidity
         assertApproxEqAbs(currency0.balanceOf(alice) - balance0BeforeAlice, uint128(-mintDelta.amount0()), 1 wei);
@@ -782,7 +782,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         donateRouter.donate(key, feeRevenue0, feeRevenue1, ZERO_BYTES);
 
         // transfer to alice
-        lpm.transferFrom(address(this), alice, tokenId);
+        IERC721(address(lpm)).transferFrom(address(this), alice, tokenId);
 
         // alice can collect the fees
         uint256 balance0BeforeAlice = currency0.balanceOf(alice);
@@ -806,7 +806,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         mint(config, liquidity, ActionConstants.MSG_SENDER, ZERO_BYTES);
 
         // transfer to alice
-        lpm.transferFrom(address(this), alice, tokenId);
+        IERC721(address(lpm)).transferFrom(address(this), alice, tokenId);
 
         // alice increases liquidity and is the payer
         uint256 balance0BeforeAlice = currency0.balanceOf(alice);
@@ -846,7 +846,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         donateRouter.donate(key, feeRevenue0, feeRevenue1, ZERO_BYTES);
 
         // transfer to alice
-        lpm.transferFrom(address(this), alice, tokenId);
+        IERC721(address(lpm)).transferFrom(address(this), alice, tokenId);
 
         {
             // alice decreases liquidity and is the recipient
@@ -980,7 +980,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers {
         assertEq(
             currency1.balanceOfSelf(), balanceBefore1 - uint256(-int256(deltaDecrease.amount1() + deltaMint.amount1()))
         );
-        assertEq(lpm.ownerOf(tokenIdMint), address(this));
+        assertEq(IERC721(address(lpm)).ownerOf(tokenIdMint), address(this));
         assertLt(currency1.balanceOfSelf(), balanceBefore1); // currency1 was owed
         assertLt(uint256(int256(deltaDecrease.amount1())), uint256(int256(-deltaMint.amount1()))); // amount1 in the second position was greater than amount1 in the first position
     }
diff --git a/test/shared/Deploy.sol b/test/shared/Deploy.sol
new file mode 100644
index 00000000..4b671d8c
--- /dev/null
+++ b/test/shared/Deploy.sol
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+import {Vm} from "forge-std/Vm.sol";
+import {IPositionDescriptor} from "../../src/interfaces/IPositionDescriptor.sol";
+import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
+import {IV4Quoter} from "../../src/interfaces/IV4Quoter.sol";
+import {IStateView} from "../../src/interfaces/IStateView.sol";
+
+library Deploy {
+    Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
+
+    function positionManager(
+        address poolManager,
+        address permit2,
+        uint256 unsubscribeGasLimit,
+        address positionDescriptor_,
+        address wrappedNative,
+        bytes memory salt
+    ) internal returns (IPositionManager manager) {
+        bytes memory args = abi.encode(poolManager, permit2, unsubscribeGasLimit, positionDescriptor_, wrappedNative);
+        bytes memory initcode = abi.encodePacked(vm.getCode("PositionManager.sol:PositionManager"), args);
+        assembly {
+            manager := create2(0, add(initcode, 0x20), mload(initcode), salt)
+        }
+    }
+
+    function stateView(address poolManager, bytes memory salt) internal returns (IStateView stateView_) {
+        bytes memory args = abi.encode(poolManager);
+        bytes memory initcode = abi.encodePacked(vm.getCode("StateView.sol:StateView"), args);
+        assembly {
+            stateView_ := create2(0, add(initcode, 0x20), mload(initcode), salt)
+        }
+    }
+
+    function v4Quoter(address poolManager, bytes memory salt) internal returns (IV4Quoter quoter) {
+        bytes memory args = abi.encode(poolManager);
+        bytes memory initcode = abi.encodePacked(vm.getCode("V4Quoter.sol:V4Quoter"), args);
+        assembly {
+            quoter := create2(0, add(initcode, 0x20), mload(initcode), salt)
+        }
+    }
+
+    function positionDescriptor(
+        address poolManager,
+        address wrappedNative,
+        string memory nativeCurrencyLabel,
+        bytes memory salt
+    ) internal returns (IPositionDescriptor descriptor) {
+        bytes memory args = abi.encode(poolManager, wrappedNative, nativeCurrencyLabel);
+        bytes memory initcode = abi.encodePacked(vm.getCode("PositionDescriptor.sol:PositionDescriptor"), args);
+        assembly {
+            descriptor := create2(0, add(initcode, 0x20), mload(initcode), salt)
+        }
+    }
+}
diff --git a/test/shared/FeeMath.sol b/test/shared/FeeMath.sol
index bb593eeb..b1b547e2 100644
--- a/test/shared/FeeMath.sol
+++ b/test/shared/FeeMath.sol
@@ -12,7 +12,6 @@ import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
 import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
 
 import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {PositionConfig} from "../shared/PositionConfig.sol";
 
 library FeeMath {
diff --git a/test/shared/LiquidityOperations.sol b/test/shared/LiquidityOperations.sol
index 80d2d81e..9c65223c 100644
--- a/test/shared/LiquidityOperations.sol
+++ b/test/shared/LiquidityOperations.sol
@@ -9,7 +9,8 @@ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
 import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol";
 import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
 
-import {PositionManager, Actions} from "../../src/PositionManager.sol";
+import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
+import {Actions} from "../../src/libraries/Actions.sol";
 import {PositionConfig} from "./PositionConfig.sol";
 import {Planner, Plan} from "../shared/Planner.sol";
 import {HookSavesDelta} from "./HookSavesDelta.sol";
@@ -18,7 +19,7 @@ abstract contract LiquidityOperations is CommonBase {
     using Planner for Plan;
     using SafeCast for *;
 
-    PositionManager lpm;
+    IPositionManager lpm;
 
     uint256 _deadline = block.timestamp + 1;
 
diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol
index 0d5ca4fc..f457207f 100644
--- a/test/shared/Planner.sol
+++ b/test/shared/Planner.sol
@@ -3,7 +3,6 @@ pragma solidity ^0.8.20;
 
 import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
 
-import {IPositionManager} from "../../src/interfaces/IPositionManager.sol";
 import {Actions} from "../../src/libraries/Actions.sol";
 import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
 import {ActionConstants} from "../../src/libraries/ActionConstants.sol";
diff --git a/test/shared/PosmTestSetup.sol b/test/shared/PosmTestSetup.sol
index bce86b97..8798220e 100644
--- a/test/shared/PosmTestSetup.sol
+++ b/test/shared/PosmTestSetup.sol
@@ -8,14 +8,13 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
 import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
 import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
 import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
 import {LiquidityOperations} from "./LiquidityOperations.sol";
 import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
 import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol";
 import {HookSavesDelta} from "./HookSavesDelta.sol";
 import {HookModifyLiquidities} from "./HookModifyLiquidities.sol";
-import {PositionDescriptor} from "../../src/PositionDescriptor.sol";
+import {Deploy, IPositionDescriptor} from "./Deploy.sol";
 import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol";
 import {IWETH9} from "../../src/interfaces/external/IWETH9.sol";
 import {WETH} from "solmate/src/tokens/WETH.sol";
@@ -29,7 +28,7 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations {
     uint256 constant STARTING_USER_BALANCE = 10_000_000 ether;
 
     IAllowanceTransfer permit2;
-    PositionDescriptor public positionDescriptor;
+    IPositionDescriptor public positionDescriptor;
     HookSavesDelta hook;
     address hookAddr = address(uint160(Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG));
     IWETH9 public _WETH9 = IWETH9(address(new WETH()));
@@ -68,8 +67,11 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations {
     function deployPosm(IPoolManager poolManager) internal {
         // We use deployPermit2() to prevent having to use via-ir in this repository.
         permit2 = IAllowanceTransfer(deployPermit2());
-        positionDescriptor = new PositionDescriptor(poolManager, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, "ETH");
-        lpm = new PositionManager(poolManager, permit2, 100_000, positionDescriptor, _WETH9);
+        positionDescriptor =
+            Deploy.positionDescriptor(address(poolManager), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, "ETH", hex"00");
+        lpm = Deploy.positionManager(
+            address(poolManager), address(permit2), 100_000, address(positionDescriptor), address(_WETH9), hex"03"
+        );
     }
 
     function seedBalance(address to) internal {
diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol
index 67b8f601..b29972b9 100644
--- a/test/shared/RoutingTestHelpers.sol
+++ b/test/shared/RoutingTestHelpers.sol
@@ -14,7 +14,6 @@ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol
 import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
 import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
 import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol";
-import {PositionManager} from "../../src/PositionManager.sol";
 import {IERC20} from "forge-std/interfaces/IERC20.sol";
 import {LiquidityOperations} from "./LiquidityOperations.sol";
 import {IV4Router} from "../../src/interfaces/IV4Router.sol";