Hook Deployment and Transaction Errors (Fix These First)

Q

Transaction keeps reverting with "Hook address not valid"

A

Your hook address doesn't match the permission flags you're trying to use. Uniswap v4 determines hook permissions by the last few bits of your deployed contract address, not from your getHookPermissions() function.Quick fix: Use the HookMiner to find the right deployment salt:solidityuint160 flags = uint160( Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG);// This is slow as shit but necessary(address hookAddress, bytes32 salt) = HookMiner.find( CREATE2_DEPLOYER, flags, type(YourHook).creationCode, constructorArgs);YourHook hook = new YourHook{salt: salt}(POOLMANAGER);

Q

Getting "CurrencyNotSettled" revert every time

A

Your hook is modifying deltas but not settling them. Every delta change must sum to zero by transaction end or Uniswap reverts.Quick fix: If your hook takes tokens, call settle(). If it gives tokens, call take():solidity// Hook takes 100 tokens as feepoolManager.settle(token0);token0.transfer(address(poolManager), 100);// Hook gives 50 tokens as rebatepoolManager.take(token1, address(this), 50);

Q

"Transaction reverted without reason" using Uniswap v4 SDK

A

The SDK examples are mostly broken.

Most "transaction reverted without reason" errors happen because:

  • You're using testnet configs on mainnet
  • Token approvals are missing or insufficient
  • Pool doesn't exist for your token pair
  • Gas estimation is failing silentlyQuick fix:

Check this Stack Overflow issue

  • typical SDK integration problems.
Q

Hook functions getting called but doing nothing

A

Your hook address has the wrong permission bits. If beforeSwap is never called, your address probably ends in 0 instead of 1 (BEFORE_SWAP_FLAG).Quick fix: Check your deployed address against required flags:javascript// If your hook needs beforeSwap, address must end in 1const address = "0x...address"; const hasBeforeSwap = parseInt(address.slice(-1), 16) & 1;console.log("Has beforeSwap permission:", !!hasBeforeSwap);

Q

"Hook delta exceeds swap amount" error

A

Your `before

Swap` hook is trying to take more tokens than the user is actually swapping, or you're returning the wrong delta direction.

Quick fix: Deltas are from the hook's perspective:

  • Taking a fee: return negative delta (-feeAmount)
  • Giving a rebate: return positive delta (+rebateAmount)
Q

Hook deployment fails even with correct permissions

A

You're probably missing the BaseHook inheritance or have mismatched function signatures.Quick fix: Always extend BaseHook and implement exact function signatures:soliditycontract YourHook is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} function getHookPermissions() external pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeSwap: true, afterSwap: false, // ... other permissions }); }}

How Hook Integration Actually Works (And Why Yours Is Broken)

Most hook integration problems come from not understanding how Uniswap v4's singleton architecture processes hooks. Unlike v3 where each pool had its own contract, v4 runs everything through one PoolManager contract that calls your hook at specific lifecycle points. The official v4 documentation covers hook basics, but the actual implementation details tell the real story.

The Hook Permission System (Where Most People Fail)

Uniswap doesn't read your getHookPermissions() function at runtime. That's just for development tooling. The real permissions come from your deployed address:

// This determines what PoolManager actually calls:
function hasPermission(IHooks self, uint160 flag) internal pure returns (bool) {
    return uint160(address(self)) & flag != 0;
}

Why this breaks: You write a hook with beforeSwap: true, deploy it at address 0x1234...0000, and wonder why beforeSwap never executes. The address ends in 0000, so bit 0 (BEFORE_SWAP_FLAG) is not set. The Uniswap Foundation's hook deployment guide explains this requirement, and QuickNode's tutorial shows working examples.

I've debugged this exact issue at 2am more times than I want to count. The hook compiles fine, deploys fine, but silently does nothing because the address permissions don't match the code.

Flash Accounting and Delta Settlement

Uniswap v4 uses "flash accounting" - it tracks IOUs during a transaction and settles everything at the end. Your hook can modify these IOUs (deltas), but they must sum to zero. The MetaLamp architecture guide explains how this works technically, and QuillAudits' swap mechanics analysis covers the delta settlement process.

Pool Manager Flow

// This happens automatically:
function unlock(bytes calldata data) external override returns (bytes memory result) {
    // Your hook executes here via callback
    result = IUnlockCallback(msg.sender).unlockCallback(data);
    
    // This check kills your transaction if deltas don't balance:
    if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith();
}

Had this hook taking 0.1% fees. Worked perfectly in my tests - I was so proud of the clean math. Deployed to mainnet, first real trade comes through, instant revert. Spent 3 hours debugging before realizing I calculated the delta right but never actually moved the tokens. Classic.

Fix: Every delta your hook creates must be settled:

function afterSwap(...) external returns (bytes4, BalanceDelta hookDelta, uint24 lpFeeOverride) {
    // Hook takes 0.1% fee
    int128 fee = int128(swapAmount * 10 / 10000);
    
    // Create the delta (negative = hook takes tokens)
    hookDelta = BalanceDeltaLibrary.toBalanceDelta(-fee, 0);
    
    // YOU MUST SETTLE THIS DELTA:
    // Transfer fee tokens to PoolManager
    IERC20(token0).transfer(address(poolManager), uint256(fee));
    poolManager.settle(Currency.wrap(address(token0)));
    
    return (BaseHook.afterSwap.selector, hookDelta, 0);
}

Hook Address Mining (The Annoying Part)

You need to mine for addresses with the right permission bits. This is slow and annoying, but there's no way around it. The HookMiner contract handles this, and SolidityDeveloper's integration guide shows practical deployment examples:

// Mine for address with beforeSwap + afterSwap permissions
uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG);

(address hookAddress, bytes32 salt) = HookMiner.find(
    CREATE2_DEPLOYER,
    flags,
    type(MyHook).creationCode,
    abi.encode(poolManager) // constructor args
);

// Deploy with the mined salt
MyHook hook = new MyHook{salt: salt}(poolManager);

Time costs: Finding a simple permission combo takes 30 seconds. Complex combos can take 5+ minutes. Factor this into your deployment scripts.

Security Issues That Will Bite You

CertiK's security analysis and Hacken's hook audit guide identify patterns that consistently cause problems:

Missing access control: Most hooks need onlyPoolManager modifiers on callback functions. Without this, anyone can call your hook directly:

// BAD - anyone can drain fees
function afterSwap(...) external returns (...) {
    feeToken.transfer(msg.sender, collectedFees);
}

// GOOD - only PoolManager can call
function afterSwap(...) external onlyPoolManager returns (...) {
    feeToken.transfer(owner, collectedFees);
}

Async hooks stealing funds: Async hooks completely replace Uniswap's swap logic. A malicious async hook can steal tokens instead of executing the swap:

// This hook steals your tokens instead of swapping
function beforeSwap(...) external returns (bytes4, BeforeSwapDelta, uint24) {
    // Take all the user's tokens
    int128 stolenAmount = -int128(params.amountSpecified);
    
    // Transfer to attacker instead of swapping
    IERC20(token).transfer(attacker, uint256(params.amountSpecified));
    
    return (selector, BeforeSwapDelta.wrap(stolenAmount), 0);
}

Hooks attached to fake pools: Anyone can create a pool using your hook with fake tokens. If your hook doesn't validate the pool, attackers can trigger it with malicious tokens that reenter or manipulate state.

Real talk: I've seen every one of these vulnerabilities in production. The same flexibility that makes hooks powerful will burn you if you don't understand the security model.

Advanced Hook Debugging (When Basic Fixes Don't Work)

Q

Hook works in local tests but fails on mainnet

A

Gas differences:

Mainnet has higher gas costs and different block conditions. Your hook might hit gas limits that don't appear locally.Quick debug: Add gas usage tracking to your hook functions:```solidityfunction before

Swap(...) external returns (...) { uint256 gasStart = gasleft(); // Your hook logic here uint256 gasUsed = gasStart

  • gasleft(); emit GasUsed("beforeSwap", gasUsed); return (...);}```State differences: Mainnet has real price oracles, different token balances, and MEV bots. Your hook assumptions might not hold.
Q

Hook causes "out of gas" on certain swaps

A

Loop optimization:

Most gas issues come from unbounded loops or expensive external calls:```solidity// BAD

  • gas scales with array size, will fuck you on large arraysfor (uint i = 0; i < authorized

Users.length; i++) { require(validateUser(authorizedUsers[i]), "Invalid user");}// GOOD

  • constant gas, use a mapping like a sane personrequire(isAuthorized[msg.sender], "Unauthorized");```External call failures:

Hooks that call external contracts (oracles, other DeFi protocols) can hit gas limits or revert chains:```solidity// BAD

  • oracle call can fail or be expensive as helluint256 price = priceOracle.getPrice(token);// GOOD
  • cached price with fallback because oracles lieuint256 price = cachedPrice;if (block.timestamp > lastUpdate + 3600) { try priceOracle.getPrice(token) returns (uint256 newPrice) { price = newPrice; cachedPrice = newPrice; lastUpdate = block.timestamp; } catch { // Use stale price if oracle shits the bed }}```
Q

Hook permissions work but functions still don't get called

A

Function signature mismatch:

Your hook function signatures must exactly match the IHooks interface:```solidity// WRONG

  • missing 'calldata' keywordfunction before

Swap( address sender, PoolKey memory key, // Should be 'calldata' SwapParams memory params, // Missing calldata keyword, idiot bytes memory hookData // Should be 'calldata') external returns (bytes4) {}// CORRECTfunction beforeSwap( address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) external returns (bytes4) {}```Return value issues:

Wrong return types make PoolManager skip your hook:```solidity// WRONG

  • returns single bytes4function before

Swap(...) external returns (bytes4) { return this.beforeSwap.selector;}// CORRECT

  • returns tuple with delta and fee overridefunction beforeSwap(...) external returns (bytes4, BeforeSwapDelta, uint24) { return (this.beforeSwap.selector, BeforeSwapDelta.wrap(0), 0);}```
Q

Hook works for some tokens but fails for others

A

Token compatibility:

Some ERC20 tokens have non-standard behavior:

  • USDT has no return value on transfer()
  • Some tokens have fees on transfer
  • Rebasing tokens change balance automaticallyQuick fix:

Use OpenZeppelin's SafeERC20 for all token operations:```solidityimport "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";using SafeERC20 for IERC20;// Instead of: token.transfer(to, amount)token.safe

Transfer(to, amount);```Token approval edge cases:

Some tokens (like USDT) require allowance to be 0 before setting a new value:```solidity// USDT-compatible approvaltoken.safe

Approve(spender, 0);token.safeApprove(spender, amount);```

Q

Pool creation fails with "Hook address not valid"

A

Address validation beyond permissions: PoolManager validates more than just permission bits:solidityfunction isValidHookAddress(IHooks hook, uint24 fee) internal pure returns (bool) { // Must have at least one permission if (!hasAnyPermission(hook)) return false; // Dynamic fee hooks need special permission if (fee.isDynamicFee() && !hook.hasPermission(Hooks.BEFORE_SWAP_FLAG)) return false; return true;}Dynamic fee gotcha: If your pool uses dynamic fees (fee == 0x800000), your hook MUST have beforeSwap permission even if you don't implement fee logic.

Q

Hook deltas cause price manipulation

A

MEV protection:

Hooks that modify swap amounts can be exploited by MEV bots. They'll sandwich your users:```solidity// BAD

  • predictable fee adjustmentfunction before

Swap(...) external returns (...) { if (swapAmount > 1000e18) { // Large trades get 50% discount int128 discount = int128(swapAmount * 50 / 100); return (..., BeforeSwapDelta.wrap(discount), 0); }}```Solution: Use private mempools or commit-reveal schemes for sensitive hooks.

Q

"Invalid hook response" errors

A

ABI encoding issues:

PoolManager expects specific return data formats. Incorrect assembly or manual encoding breaks this:```solidity// WRONG

  • manual encoding can failbytes memory data = abi.encode(selector, delta, fee);// CORRECT
  • use proper return statementreturn (Base

Hook.beforeSwap.selector, delta, fee);```Selector validation:

Always return the correct function selector:```solidity// Use the inherited selector from Base

Hookreturn (BaseHook.beforeSwap.selector, delta, fee);// Don't manually compute selectorsreturn (bytes4(keccak256("beforeSwap(...)")), delta, fee); // BAD```

Production Hook Deployment (What They Don't Tell You)

Fixed the basic shit? Cool. Now here's where you'll want to quit programming and become a bartender: deploying hooks that work reliably in production, handling edge cases, and not losing users' money.

Testing Strategy That Actually Catches Problems

Most hook failures happen because testing doesn't match production conditions. Here's what I learned the hard way. The Foundry documentation covers fork testing, Patrick Collins' testing guide shows practical examples, Base's Foundry testing tutorial provides comprehensive testing strategies, and QuickNode's Foundry introduction covers essential deployment patterns:

Test with real mainnet state: Use mainnet forks with current block state, not empty test networks:

// In your test setup
vm.createFork("https://mainnet.infura.io/v3/YOUR_KEY");
vm.selectFork(mainnetFork);

// Test with actual token balances and prices, not fantasy numbers
deal(USDC, user, 10000e6);  // Give user real USDC
deal(WETH, user, 5e18);     // Give user real WETH

Test gas usage under stress: Hooks that work with small amounts might fail with large trades due to gas limits:

function testLargeSwapGasUsage() public {
    uint256 gasStart = gasleft();
    
    // Test with realistic large swap amount
    swapRouter.swap(1000e18);  // 1000 ETH swap
    
    uint256 gasUsed = gasStart - gasleft();
    assertLt(gasUsed, 300000, "Hook uses too much gas for large swaps");
}

Test with broken oracles: External dependencies will fail in production. Your hook needs fallback behavior:

function testOracleFailure() public {
    // Mock oracle failure
    vm.mockRevert(address(priceOracle), abi.encodeWithSignature("getPrice(address)"), "Oracle down");
    
    // Hook should still work, maybe with cached price or safe defaults
    swapRouter.swap(1e18);  // This shouldn't revert
}

Hook Deployment Checklist (Skip These, Lose Money)

I maintain this checklist after debugging too many production failures:

✅ Permission verification:

## Verify your deployed hook has the right permissions
cast call $HOOK_ADDRESS "getHookPermissions()(uint256)"
## Compare with your deployed address permissions
echo "Address bits: $(cast to-base $(cast call $HOOK_ADDRESS "getHookPermissions()") 2)"

✅ Delta accounting audit:

// Every delta-modifying path must be tested
function testAllDeltaPaths() public {
    testSwapWithPositiveDelta();
    testSwapWithNegativeDelta();
    testSwapWithZeroDelta();
    testSwapWithExtremeDeltas();
}

✅ Access control verification:

## Try calling hook functions directly (should fail)
cast send $HOOK_ADDRESS "beforeSwap(...)" --from $RANDOM_ADDRESS
## Should revert with "Unauthorized" or similar

✅ Gas limit testing: Critical for production readiness as outlined in CoinsBench's gas optimization guide and Anciliar's developer guide:

// Test worst-case gas usage scenarios
function testGasLimits() public {
    // Max array sizes
    // Worst-case oracle calls
    // Maximum delta calculations
}

Monitoring and Alerting (Because Things Will Break)

Production hooks need monitoring. Failures are often silent - users just get reverts without understanding why. Tenderly's monitoring platform provides transaction debugging, OpenZeppelin Defender offers monitoring and alerting for smart contracts, Ethereum's security documentation covers monitoring tools, and Medium's Defender security guide explains practical monitoring setups. Guardrail's circuit breaker analysis and Olympix's comprehensive circuit breaker guide cover emergency response patterns.

Critical metrics to track:

  • Hook execution success rate
  • Gas usage per call type
  • Delta settlement success rate
  • External call failure rate

Universal Router Inheritance

Example monitoring setup following best practices from OpenZeppelin's incident response guide:

contract MonitoredHook is BaseHook {
    event HookExecuted(string functionName, bool success, uint256 gasUsed);
    event DeltaSettlementFailed(address token, int256 amount);
    event ExternalCallFailed(address target, bytes data);

    modifier monitored(string memory functionName) {
        uint256 gasStart = gasleft();
        _;
        emit HookExecuted(functionName, true, gasStart - gasleft());
    }

    function beforeSwap(...) external monitored("beforeSwap") returns (...) {
        // Hook logic with try-catch for external calls
        try externalOracle.getPrice(token) returns (uint256 price) {
            // Use price
        } catch {
            emit ExternalCallFailed(address(externalOracle), abi.encodeWithSignature("getPrice(address)", token));
            // Use fallback logic
        }
    }
}

Common Production Failures (From War Stories)

Delta calculation errors under extreme conditions: A hook that worked fine for months started failing when SHIB went up 1000%. The fee calculation overflowed uint128:

// BROKEN - overflows with huge token amounts, will fuck you hard
int128 fee = int128(swapAmount * feeRate / 10000);

// FIXED - safe math with bounds checking because Solidity is a dick
int256 feeCalculation = (int256(swapAmount) * feeRate) / 10000;
require(feeCalculation <= type(int128).max, "Fee calculation overflow");
int128 fee = int128(feeCalculation);

Hook gets stuck when external contracts are paused: Had my hook integrated with Compound. Worked great until they paused their markets during some security scare - suddenly my hook starts reverting on every transaction. Rapid Innovation's security guide and Yos.io's fault-tolerant contracts guide explain resilient contract patterns:

// BROKEN - hard dependency on external protocol
uint256 rate = compound.getSupplyRate();

// FIXED - graceful degradation
uint256 rate = defaultRate;
try compound.getSupplyRate() returns (uint256 newRate) {
    rate = newRate;
} catch {
    emit ExternalProtocolDown(address(compound));
    // Continue with default rate
}

Permission mismatch after contract upgrade: Upgraded a hook implementation but forgot to mine a new address with updated permissions. Users got confused when new features didn't work.

Front-running attacks on fee adjustments: Hook adjusted fees based on volatility, but MEV bots front-ran the volatility detection to get favorable rates.

Emergency Response Plan

When your hook breaks in production (not if, when), you need a plan:

1. Circuit breaker pattern: Essential emergency response mechanism as outlined in Kaia's security best practices and SitePoint's smart contract safety guide:

bool public emergencyPause = false;
address public emergencyAdmin;

modifier whenNotPaused() {
    require(!emergencyPause, "Hook paused for emergency");
    _;
}

function beforeSwap(...) external whenNotPaused returns (...) {
    // Hook logic
}

function emergencyPauseHook() external {
    require(msg.sender == emergencyAdmin, "Only emergency admin");
    emergencyPause = true;
    emit EmergencyPause(block.timestamp);
}

2. Safe mode operations:

function beforeSwap(...) external returns (...) {
    if (emergencyPause) {
        // Pass through without modifications
        return (BaseHook.beforeSwap.selector, BeforeSwapDelta.wrap(0), 0);
    }
    
    // Normal hook logic
}

3. User communication plan:

  • Discord/Twitter announcements
  • Documentation updates
  • Clear explanation of what went wrong and when it will be fixed

Real talk: I've had to use emergency procedures twice. Once for a gas optimization gone wrong, once for an oracle manipulation attack. Having these procedures ready saved users from losing funds both times. Medium's circuit breaker design patterns and Nethermind's secure coding guide provide essential patterns for emergency response, while Hedera's smart contract design patterns covers comprehensive security patterns including emergency stops.

Pool Locking Mechanism

Essential Hook Debugging Tools and Resources

Related Tools & Recommendations

tool
Similar content

Uniswap v4 Overview: Cheaper Gas, Custom Hooks & More

Finally, a DEX where pool creation won't cost you $500 in gas (usually)

Uniswap v4
/tool/uniswap-v4/overview
100%
tool
Similar content

Arbitrum Production Debugging: Fix Gas & WASM Errors in Live Dapps

Real debugging for developers who've been burned by production failures

Arbitrum SDK
/tool/arbitrum-development-tools/production-debugging-guide
82%
tool
Similar content

TaxBit Enterprise Production Troubleshooting: Debug & Fix Issues

Real errors, working fixes, and why your monitoring needs to catch these before 3AM calls

TaxBit Enterprise
/tool/taxbit-enterprise/production-troubleshooting
57%
compare
Recommended

Which ETH Staking Platform Won't Screw You Over

Ethereum staking is expensive as hell and every option has major problems

ethereum
/compare/lido/rocket-pool/coinbase-staking/kraken-staking/ethereum-staking/ethereum-staking-comparison
56%
tool
Similar content

Fix TaxAct Errors: Login, WebView2, E-file & State Rejection Guide

The 3am tax deadline debugging guide for login crashes, WebView2 errors, and all the shit that goes wrong when you need it to work

TaxAct
/tool/taxact/troubleshooting-guide
54%
tool
Similar content

LM Studio Performance: Fix Crashes & Speed Up Local AI

Stop fighting memory crashes and thermal throttling. Here's how to make LM Studio actually work on real hardware.

LM Studio
/tool/lm-studio/performance-optimization
43%
tool
Similar content

React Production Debugging: Fix App Crashes & White Screens

Five ways React apps crash in production that'll make you question your life choices.

React
/tool/react/debugging-production-issues
41%
troubleshoot
Similar content

React useEffect Not Working? Debug & Fix Infinite Loops

Complete troubleshooting guide to solve useEffect problems that break your React components

React
/troubleshoot/react-useeffect-hook-not-working/useeffect-not-working-fixes
40%
tool
Similar content

Android Studio: Google's Official IDE, Realities & Tips

Current version: Narwhal Feature Drop 2025.1.2 Patch 1 (August 2025) - The only IDE you need for Android development, despite the RAM addiction and occasional s

Android Studio
/tool/android-studio/overview
40%
tool
Similar content

Debugging AI Coding Assistant Failures: Copilot, Cursor & More

Your AI assistant just crashed VS Code again? Welcome to the club - here's how to actually fix it

GitHub Copilot
/tool/ai-coding-assistants/debugging-production-failures
40%
tool
Recommended

Optimism Production Troubleshooting - Fix It When It Breaks

The real-world debugging guide for when Optimism doesn't do what the docs promise

Optimism
/tool/optimism/production-troubleshooting
39%
tool
Recommended

Optimism - Yeah, It's Actually Pretty Good

The L2 that doesn't completely suck at being Ethereum

Optimism
/tool/optimism/overview
39%
howto
Recommended

Deploy Smart Contracts on Optimism Without Going Broke

Stop paying $200 to deploy hello world contracts. Here's how to use Optimism like a normal person.

optimism
/howto/deploy-smart-contracts-optimism/complete-deployment-guide
39%
tool
Recommended

Arbitrum - Ethereum's L2 That Actually Works

integrates with Arbitrum One

Arbitrum One
/tool/arbitrum/overview
39%
tool
Recommended

Arbitrum Orbit - Launch Your Own L2/L3 Chain (Without the Headaches)

integrates with Arbitrum Orbit

Arbitrum Orbit
/tool/arbitrum-orbit/getting-started
39%
howto
Recommended

Build Production-Ready dApps on Arbitrum Layer 2 - Complete Developer Guide

Stop Burning Money on Gas Fees - Deploy Smart Contracts for Pennies Instead of Dollars

Arbitrum
/howto/develop-arbitrum-layer-2/complete-development-guide
39%
howto
Recommended

Set Up Your Complete Polygon Development Environment - Step-by-Step Guide

Fix the bullshit Node.js conflicts, MetaMask fuckups, and gas estimation errors that waste your Saturday debugging sessions

Polygon SDK
/howto/polygon-dev-setup/complete-development-environment-setup
39%
tool
Recommended

Polygon Edge Enterprise Deployment - The Abandoned Blockchain Framework Guide

Deploy Ethereum-compatible blockchain networks that work until they don't - now with 100% chance of no official support.

Polygon Edge
/tool/polygon-edge/enterprise-deployment
39%
howto
Recommended

SSH Multiple Git Accounts - Stop Fucking Up Your Identity

Git asking for passwords every goddamn time? Personal furry fanfiction commits accidentally pushed to your company repo?

Git
/howto/configure-git-multiple-accounts/ssh-based-configuration
39%
alternatives
Recommended

Coinbase Alternatives That Won't Bleed You Dry

Stop getting ripped off by Coinbase's ridiculous fees - here are the exchanges that actually respect your money

Coinbase
/alternatives/coinbase/fee-focused-alternatives
39%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization