> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monad.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Exercise and verify the pool

> On this page, you will initialize a testnet pool, mint an owned position, swap through Universal Router, remove liquidity, and revoke approvals.

## Prerequisites

Complete [Deploy](/guides/uniswap-v4-hooks/deploy) and confirm the hook and both mock-token deployments. Run from the same `monad-hook` directory. It contains [`shared-infrastructure.json`](https://github.com/monad-developers/uniswap-v4-hooks-example/blob/c3cc78a88f10e5ba141cd6fb632f31db83c0daf8/shared-infrastructure.json); the deployment scripts created `deployments/tokens.json` and `deployments/hook.json`. If you opened a new shell, restore the account, network, and contract addresses:

```sh theme={null}
export DEPLOYER="$(cast wallet address --account monad-hooks-testnet)"
export MONAD_TESTNET_RPC_URL="https://testnet-rpc.monad.xyz"
export EXPECTED_CHAIN_ID=10143
export SHARED_INFRA="shared-infrastructure.json"
export POOL_MANAGER="$(jq -er .poolManager "$SHARED_INFRA")"
export POSITION_MANAGER="$(jq -er .positionManager "$SHARED_INFRA")"
export UNIVERSAL_ROUTER="$(jq -er .universalRouter "$SHARED_INFRA")"
export STATE_VIEW="$(jq -er .stateView "$SHARED_INFRA")"
export QUOTER="$(jq -er .quoter "$SHARED_INFRA")"
export POSITION_DESCRIPTOR="$(jq -er .positionDescriptor "$SHARED_INFRA")"
export PERMIT2="$(jq -er .permit2 "$SHARED_INFRA")"
export WMON="$(jq -er .wrappedNative "$SHARED_INFRA")"
export TOKEN_A="$(jq -er .tokenA deployments/tokens.json)"
export TOKEN_B="$(jq -er .tokenB deployments/tokens.json)"
export HOOK_ADDRESS="$(jq -er .hook deployments/hook.json)"
```

The script uses `deployments/position.json` to identify your NFT after it is minted.

## Initialize and exercise one pool

[`PoolLifecycle.s.sol`](https://github.com/monad-developers/uniswap-v4-hooks-example/blob/c3cc78a88f10e5ba141cd6fb632f31db83c0daf8/script/monad/PoolLifecycle.s.sol) runs each operation as a separate stage. The function below simulates each stage before broadcasting it and stops if a command fails:

```sh theme={null}
(
  set -eu

  run_stage() {
    local stage="$1"
    local zero_for_one="${2:-true}"
    export STAGE="$stage" ZERO_FOR_ONE="$zero_for_one"

    FOUNDRY_PROFILE=monad_testnet forge script \
      script/monad/PoolLifecycle.s.sol:PoolLifecycle \
      --network monad --rpc-url "$MONAD_TESTNET_RPC_URL" \
      --account monad-hooks-testnet \
      --sender "$DEPLOYER"

    FOUNDRY_PROFILE=monad_testnet forge script \
      script/monad/PoolLifecycle.s.sol:PoolLifecycle \
      --network monad --rpc-url "$MONAD_TESTNET_RPC_URL" \
      --account monad-hooks-testnet \
      --sender "$DEPLOYER" \
      --broadcast --slow
  }

  run_stage initialize
  run_stage fundAndApprove
  run_stage addLiquidity

  # Run this immediately after addLiquidity. The next forge run overwrites run-latest.json.
  python3 tools/record_position.py \
    --broadcast broadcast/PoolLifecycle.s.sol/10143/run-latest.json \
    --position-manager "$POSITION_MANAGER" \
    --deployer "$DEPLOYER" \
    --output deployments/position.json
  export TOKEN_ID="$(jq -er .tokenId deployments/position.json)"
  cast call "$POSITION_MANAGER" "ownerOf(uint256)(address)" "$TOKEN_ID" \
    --rpc-url "$MONAD_TESTNET_RPC_URL"
  FOUNDRY_PROFILE=monad_testnet forge script \
    script/monad/ReadState.s.sol:ReadState \
    --network monad --rpc-url "$MONAD_TESTNET_RPC_URL" \
    --sender "$DEPLOYER"

  run_stage swapExactInput true
  run_stage swapExactInput false
  run_stage swapExactOutput true
  run_stage swapExactOutput false
  run_stage removeLiquidity
  run_stage revokeApprovals
)
```

During `addLiquidity`, `PositionManager` issues a position NFT to your account. [`record_position.py`](https://github.com/monad-developers/uniswap-v4-hooks-example/blob/c3cc78a88f10e5ba141cd6fb632f31db83c0daf8/tools/record_position.py) reads your NFT’s `Transfer` event from the mint receipt and saves its token ID. Run it immediately after `addLiquidity`, before the next broadcast overwrites `run-latest.json`. PositionManager is used by multiple accounts, so the token ID must come from your receipt.

`fundAndApprove` mints mock tokens and approves Permit2 to transfer them. For each token, it grants PositionManager `4e18` units and Universal Router `1e16` units through Permit2, with a one-day expiry. The ERC-20 allowance to Permit2 covers their combined total.

The script uses these pool settings:

| Setting              | Value                                                                                      |
| -------------------- | ------------------------------------------------------------------------------------------ |
| Currency order       | Token addresses sorted numerically in ascending order.                                     |
| Hook and pool        | `HOOK_ADDRESS` in the `PoolKey`.                                                           |
| Fee and tick spacing | `3000` (0.3%) and `60`.                                                                    |
| Initial price        | `sqrtPriceX96 = 2**96`, which is price 1 for equal-decimal tokens.                         |
| Position range       | Ticks `-600` to `600`.                                                                     |
| Funding              | `1000e18` of each mock token minted to `DEPLOYER`.                                         |
| Liquidity            | `100e18` liquidity units through PositionManager.                                          |
| Swap input/output    | `1e15` token units (`0.001` tokens) per swap.                                              |
| Swap protection      | `99%` minimum quoted output for exact input; `101%` maximum quoted input for exact output. |

The four swap stages cover exact input and exact output in both directions. Universal Router executes the swaps using Permit2 for token transfers. Each swap uses a fresh quote with a one-percent slippage bound.

This example routes swaps through the Counter pool you created. Universal Router executes the supplied route; it does not certify the hook’s safety. Applications that select among pools must evaluate their hooks as well as their quoted prices.

## Check the results

[`ReadState.s.sol`](https://github.com/monad-developers/uniswap-v4-hooks-example/blob/c3cc78a88f10e5ba141cd6fb632f31db83c0daf8/script/monad/ReadState.s.sol) reports pool state, hook counters, position liquidity, token balances, and allowances. Run it after the lifecycle completes:

```sh theme={null}
FOUNDRY_PROFILE=monad_testnet forge script \
  script/monad/ReadState.s.sol:ReadState \
  --network monad --rpc-url "$MONAD_TESTNET_RPC_URL" \
  --sender "$DEPLOYER"
```

For a newly deployed hook, the completed sequence produces these results:

| Value                                  | Expected result                                                            |
| -------------------------------------- | -------------------------------------------------------------------------- |
| `beforeAddLiquidityCount`              | `1`                                                                        |
| `beforeRemoveLiquidityCount`           | `1`                                                                        |
| `beforeSwapCount` and `afterSwapCount` | `4` each                                                                   |
| Position liquidity                     | `0`; `removeLiquidity` burns the position NFT.                             |
| Token approvals                        | `0` for both ERC-20 allowances to Permit2 and all four Permit2 allowances. |

## Verify the deployed hook and mock tokens

Follow the [Foundry verification guide](/guides/verify-smart-contract/foundry) to publish your contract source on an explorer. For Counter, supply the PoolManager address as its constructor argument:

```sh theme={null}
FOUNDRY_PROFILE=monad_testnet forge verify-contract \
  "$HOOK_ADDRESS" "src/Counter.sol:Counter" \
  --chain 10143 \
  --constructor-args "$(cast abi-encode 'f(address)' "$POOL_MANAGER")" \
  --verifier etherscan \
  --etherscan-api-key "$MONADSCAN_API_KEY" \
  --watch
```

The compiler settings are configured in [`foundry.toml`](https://github.com/monad-developers/uniswap-v4-hooks-example/blob/c3cc78a88f10e5ba141cd6fb632f31db83c0daf8/foundry.toml). For the mock tokens, [`DeployMockTokens.s.sol`](https://github.com/monad-developers/uniswap-v4-hooks-example/blob/c3cc78a88f10e5ba141cd6fb632f31db83c0daf8/script/monad/DeployMockTokens.s.sol) specifies the constructor arguments: `("Monad Hooks Test A", "MHTA", 18)` and `("Monad Hooks Test B", "MHTB", 18)`.

## Next step

Continue to [Customize and publish](/guides/uniswap-v4-hooks/customize-and-publish) to change the hook's behavior and learn about mainnet infrastructure and registry submission.
