PadQuiverFactory

0x1b03d637e05ae493e8924c496fdd5f1584b2d2d5

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
13,018 bytes
ABI entries
31
18 read · 7 write
License
none

Contract information

Address
0x1b03d637e05ae493e8924c496fdd5f1584b2d2d5
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x0cc8a425fb…7fbe8a82ea

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (18)

WEBSITE()string
computeSaltHash(address, bytes32)bytes32
creationFee()uint256
defaultCallerTipBps()uint256
defaultMaxSlippageBps()uint256
defaultPlatformFeeBps()uint256
defaultTwapWindow()uint32
distributorDeployer()address
distributorOf(address)address
feeRecipient()address
observationCardinality()uint16
owner()address
positionManager()address
swapRouter()address
tokens(uint256)address, address, address, address, address, uint256
tokensCount()uint256
v3Factory()address
weth()address

Events (2)

OwnershipTransferredTokenCreated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "v3Factory_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "swapRouter_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "feeRecipient_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "distributorDeployer_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "creationFee_",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "defaultPlatformFeeBps_",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "distributor",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "rewardToken",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positionTokenId",
        "type": "uint256"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "WEBSITE",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      }
    ],
    "name": "computeSaltHash",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "uint256",
            "name": "supply",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "rewardToken",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "poolFee",
            "type": "uint24"
          },
          {
            "internalType": "uint24",
            "name": "rewardPoolFee",
            "type": "uint24"
          },
          {
            "internalType": "uint160",
            "name": "sqrtPriceX96",
            "type": "uint160"
          },
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          },
          {
            "internalType": "uint256",
            "name": "minRewardBalance",
            "type": "uint256"
          }
        ],
        "internalType": "struct PadQuiverFactory.CreateParams",
        "name": "p",
        "type": "tuple"
      }
    ],
    "name": "createToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "distributor",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creationFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultCallerTipBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultMaxSlippageBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultPlatformFeeBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultTwapWindow",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "distributorDeployer",
    "outputs": [
      {
        "internalType": "contract PadQuiverDeployer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "distributorOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "observationCardinality",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "setCreationFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "bps",
        "type": "uint256"
      }
    ],
    "name": "setDefaultPlatformFeeBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "r",
        "type": "address"
      }
    ],
    "name": "setFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint32",
        "name": "window",
        "type": "uint32"
      },
      {
        "internalType": "uint256",
        "name": "maxSlippageBps",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "callerTipBps",
        "type": "uint256"
      },
      {
        "internalType": "uint16",
        "name": "cardinality",
        "type": "uint16"
      }
    ],
    "name": "setTwapDefaults",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapRouter",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "tokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "distributor",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "rewardToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "positionTokenId",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokensCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "v3Factory",
    "outputs": [
      {
        "internalType": "contract IUniswapV3Factory",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {PadQuiverToken} from "./PadQuiverToken.sol";
import {PadQuiverDistributor} from "./PadQuiverDistributor.sol";
import {PadQuiverDeployer} from "./PadQuiverDeployer.sol";
import {IUniswapV3Factory, IUniswapV3Pool, INonfungiblePositionManager} from "./interfaces/IUniswapV3.sol";

/**
 * @title PadQuiverFactory
 * @author PadQuiver Launchpad — https://padquiver.com/
 * @custom:website https://padquiver.com/
 * @notice PadQuiver launchpad'inin kalbi. Müşteri ödüllü token'ını oluşturur: factory
 *         token'ı basar, Uniswap V3 havuzu açar, tüm arzı TEK TARAFLI (sermayesiz)
 *         pozisyona koyar, NFT'yi ödül dağıtıcıya (distributor) verir. Likidite kalıcı
 *         kilitli; sadece komisyon toplanıp ödül olarak dağıtılır.
 *
 *         GÜVENLİK (denetim düzeltmeleri):
 *         - Token CREATE2 ile deploy edilir (salt = keccak(creator, userSalt)); adres
 *           saldırganca tahmin edilemez ve zehirlenirse yeni salt'la kaçılabilir
 *           (kalıcı-brick DoS'u kapatır).
 *         - Havuz oluşturulduktan sonra slot0.tick DOĞRULANIR: tek-taraflı kenarda değilse
 *           gürültülü revert (sessiz fiyat-override ve yanlış-oran mint'i önler).
 *         - Tick hizası (tickSpacing) doğrulanır.
 *         - Artan token yakılır; fazla ödenen creation fee iade edilir.
 */
contract PadQuiverFactory is Ownable, ReentrancyGuard {
    /// @notice Resmi PadQuiver launchpad web sitesi (on-chain okunabilir).
    string public constant WEBSITE = "https://padquiver.com/";

    address private constant DEAD = 0x000000000000000000000000000000000000dEaD;

    IUniswapV3Factory public immutable v3Factory;
    INonfungiblePositionManager public immutable positionManager;
    address public immutable swapRouter;
    address public immutable weth;
    PadQuiverDeployer public immutable distributorDeployer;

    uint256 public creationFee;
    address public feeRecipient;
    uint256 public defaultPlatformFeeBps;

    // Bot'suz harvest için TWAP koruma parametreleri (yeni token'lara verilir)
    uint32 public defaultTwapWindow = 600; // 10 dk
    uint256 public defaultMaxSlippageBps = 500; // %5
    uint256 public defaultCallerTipBps = 25; // %0.25 (harvest'i çağırana)
    uint16 public observationCardinality = 120; // havuz oracle tampon boyutu

    struct TokenRecord {
        address token;
        address distributor;
        address pool;
        address creator;
        address rewardToken;
        uint256 positionTokenId;
    }

    TokenRecord[] public tokens;
    mapping(address => address) public distributorOf;

    event TokenCreated(
        address indexed creator,
        address indexed token,
        address distributor,
        address pool,
        address rewardToken,
        uint256 positionTokenId
    );

    struct CreateParams {
        string name;
        string symbol;
        uint256 supply;
        address rewardToken;
        uint24 poolFee;
        uint24 rewardPoolFee;
        uint160 sqrtPriceX96;
        int24 tickLower;
        int24 tickUpper;
        bytes32 salt; // CREATE2 tuzu (client seçer; adresi tahmin edilemez kılar)
        uint256 minRewardBalance; // ödül için min holder bakiyesi (0 = varsayılan; yüksek = sadece büyük holder'lar)
    }

    constructor(
        address v3Factory_,
        address positionManager_,
        address swapRouter_,
        address weth_,
        address feeRecipient_,
        address distributorDeployer_,
        uint256 creationFee_,
        uint256 defaultPlatformFeeBps_
    ) Ownable(msg.sender) {
        require(defaultPlatformFeeBps_ <= 2_000, "RRF: platform fee too high");
        require(distributorDeployer_ != address(0), "RRF: zero deployer");
        v3Factory = IUniswapV3Factory(v3Factory_);
        positionManager = INonfungiblePositionManager(positionManager_);
        swapRouter = swapRouter_;
        weth = weth_;
        feeRecipient = feeRecipient_;
        distributorDeployer = PadQuiverDeployer(distributorDeployer_);
        creationFee = creationFee_;
        defaultPlatformFeeBps = defaultPlatformFeeBps_;
    }

    function createToken(CreateParams calldata p)
        external
        payable
        nonReentrant
        returns (address token, address distributor, address pool, uint256 tokenId)
    {
        uint256 fee = feeRecipient == address(0) ? 0 : creationFee; // feeRecipient yoksa ücret alma
        require(msg.value >= fee, "RRF: creation fee");
        require(p.rewardToken != address(0), "RRF: zero reward token");
        require(p.supply > 0, "RRF: zero supply");
        require(p.tickLower < p.tickUpper, "RRF: bad ticks");
        // Ödül eşiği: 0 = varsayılan (~arzın %0.001'i); en fazla arzın %10'u (herkesi dışlamayı önler)
        uint256 minRewardBalance = p.minRewardBalance == 0 ? p.supply / 100_000 : p.minRewardBalance;
        require(minRewardBalance <= p.supply / 10, "RRF: reward threshold too high");
        // Ödül coin'inin WETH havuzu OLMALI, yoksa harvest ödülü çeviremez (stranded WETH)
        address rewardPool;
        if (p.rewardToken != weth) {
            rewardPool = v3Factory.getPool(weth, p.rewardToken, p.rewardPoolFee);
            require(rewardPool != address(0), "RRF: no reward/WETH pool");
        }

        // 1) Token'ı CREATE2 ile bas (adres tahmin edilemez; zehirlenirse yeni salt ile kaçış)
        bytes32 saltHash = keccak256(abi.encodePacked(msg.sender, p.salt));
        PadQuiverToken t = new PadQuiverToken{salt: saltHash}(
            p.name, p.symbol, p.supply, address(this), msg.sender
        );
        token = address(t);
        require(token != weth && token != p.rewardToken, "RRF: token clash");

        bool tokenIsToken0 = token < weth;
        (address token0, address token1) = tokenIsToken0 ? (token, weth) : (weth, token);

        // 2) Havuzu oluştur + başlangıç fiyatını ayarla
        pool = v3Factory.getPool(token0, token1, p.poolFee);
        bool preExisted = pool != address(0);
        if (!preExisted) {
            pool = v3Factory.createPool(token0, token1, p.poolFee);
            // Taze havuz: initialize DOĞRUDAN — kötü sqrtPriceX96 gürültülü revert etsin
            IUniswapV3Pool(pool).initialize(p.sqrtPriceX96);
        } else {
            // Zaten var (nadir/adversarial): 'already initialized' revert'ini yut,
            // aşağıdaki slot0 kontrolü fiyatı yine de doğrular
            try IUniswapV3Pool(pool).initialize(p.sqrtPriceX96) {} catch {}
        }

        // 2b) DOĞRULA: tick hizası + fiyatın tek-taraflı kenarda olması (front-run/override koruması)
        int24 spacing = IUniswapV3Pool(pool).tickSpacing();
        require(p.tickLower % spacing == 0 && p.tickUpper % spacing == 0, "RRF: tick misaligned");
        (, int24 currentTick,,,,,) = IUniswapV3Pool(pool).slot0();
        if (tokenIsToken0) {
            require(currentTick < p.tickLower, "RRF: price not below range");
        } else {
            require(currentTick >= p.tickUpper, "RRF: price not above range");
        }

        // 2c) Oracle tamponunu büyüt — TWAP korumasının geçmiş biriktirebilmesi için
        //     (hem token havuzu hem ödül havuzu; ödül havuzu cardinality=1 olabilir)
        IUniswapV3Pool(pool).increaseObservationCardinalityNext(observationCardinality);
        if (rewardPool != address(0)) {
            IUniswapV3Pool(rewardPool).increaseObservationCardinalityNext(observationCardinality);
        }

        // 3) Distributor'ı kur (ayrı deployer ile — factory kod-boyutu sınırı için).
        //    Config.factory bu factory'yi işaret eder (deployer'ı değil).
        distributor = distributorDeployer.deploy(
            PadQuiverDistributor.Config({
                factory: address(this),
                token: token,
                positionManager: address(positionManager),
                swapRouter: swapRouter,
                weth: weth,
                rewardToken: p.rewardToken,
                pool: pool,
                rewardPool: rewardPool,
                poolFee: p.poolFee,
                rewardPoolFee: p.rewardPoolFee,
                tokenIsToken0: tokenIsToken0,
                platformWallet: feeRecipient,
                platformFeeBps: defaultPlatformFeeBps,
                twapWindow: defaultTwapWindow,
                maxSlippageBps: defaultMaxSlippageBps,
                callerTipBps: defaultCallerTipBps,
                minimumBalanceForDividends: minRewardBalance
            })
        );
        t.initializeDistributor(distributor);

        // 4) Tüm arzı tek taraflı pozisyona koy; NFT distributor'a
        t.approve(address(positionManager), p.supply);
        (tokenId,,,) = positionManager.mint(
            INonfungiblePositionManager.MintParams({
                token0: token0,
                token1: token1,
                fee: p.poolFee,
                tickLower: p.tickLower,
                tickUpper: p.tickUpper,
                amount0Desired: tokenIsToken0 ? p.supply : 0,
                amount1Desired: tokenIsToken0 ? 0 : p.supply,
                amount0Min: 0,
                amount1Min: 0,
                recipient: distributor,
                deadline: block.timestamp
            })
        );
        PadQuiverDistributor(distributor).initialize(tokenId);

        // 4b) Artan token'ı (yuvarlama) yak — factory'de kilitli kalmasın
        uint256 leftover = t.balanceOf(address(this));
        if (leftover > 0) {
            t.transfer(DEAD, leftover);
        }

        // 5) Kayıt
        tokens.push(
            TokenRecord({
                token: token,
                distributor: distributor,
                pool: pool,
                creator: msg.sender,
                rewardToken: p.rewardToken,
                positionTokenId: tokenId
            })
        );
        distributorOf[token] = distributor;

        // 6) Ücret: sadece fee'yi al (feeRecipient yoksa 0), fazlasını iade et
        if (fee > 0) {
            (bool okFee,) = feeRecipient.call{value: fee}("");
            require(okFee, "RRF: fee transfer failed");
        }
        if (msg.value > fee) {
            (bool okRef,) = msg.sender.call{value: msg.value - fee}("");
            require(okRef, "RRF: refund failed");
        }

        emit TokenCreated(msg.sender, token, distributor, pool, p.rewardToken, tokenId);
    }

    function tokensCount() external view returns (uint256) {
        return tokens.length;
    }

    /// @notice Bir sonraki token'ın CREATE2 adresini önceden hesaplamaya yardım eder (client tick hesabı için).
    function computeSaltHash(address creator, bytes32 salt) external pure returns (bytes32) {
        return keccak256(abi.encodePacked(creator, salt));
    }

    // --- Admin ---
    function setCreationFee(uint256 fee) external onlyOwner {
        creationFee = fee;
    }

    function setFeeRecipient(address r) external onlyOwner {
        feeRecipient = r;
    }

    function setDefaultPlatformFeeBps(uint256 bps) external onlyOwner {
        require(bps <= 2_000, "RRF: too high");
        defaultPlatformFeeBps = bps;
    }

    /// @notice Yeni token'lara verilecek TWAP koruma parametreleri (mevcut token'ları etkilemez).
    function setTwapDefaults(uint32 window, uint256 maxSlippageBps, uint256 callerTipBps, uint16 cardinality)
        external
        onlyOwner
    {
        require(window >= 60 && window <= 3600, "RRF: bad window");
        require(maxSlippageBps <= 1_000, "RRF: slippage too high");
        require(callerTipBps <= 100, "RRF: tip too high");
        require(cardinality >= 2, "RRF: bad cardinality");
        defaultTwapWindow = window;
        defaultMaxSlippageBps = maxSlippageBps;
        defaultCallerTipBps = callerTipBps;
        observationCardinality = cardinality;
    }
}
Chain explorer4395msChain node85ms