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
- Creator
- 0x1149ac0269…6994Dd16DA
- 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