Clanker
0xf1184c7e0d04b165d22451300faf1323bf4dfbb5
Verification
Verified
v0.8.35+commit.47b9dedd
Type
Contract
17,351 bytes
ABI entries
51
18 read · 11 write
License
none
Contract information
- Address
- 0xf1184c7e0d04b165d22451300faf1323bf4dfbb5
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.35+commit.47b9dedd
- Optimization
- Enabled
- Creator
- 0xd07EA153CC…4C10B0771e
- Creation tx
- 0x41c07eb43d…4bc9878465
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (18)
MAX_CREATOR_REWARD() → uint256
MAX_TICK() → int24
MAX_VAULT_PERCENTAGE() → uint256
POOL_FEE() → uint24
TICK_SPACING() → int24
TOKEN_SUPPLY() → uint256
admins(address) → bool
deploymentInfoForToken(address) → address, uint256, address
deprecated() → bool
getTokensDeployedByUser(address) → tuple[]
liquidityLocker() → address
owner() → address
positionManager() → address
swapRouter() → address
tokensDeployedByUsers(address, uint256) → address, uint256, address
uniswapV3Factory() → address
vault() → address
weth() → address
Events (7)
ClankerDeployerUpdatedLiquidityLockerUpdatedOwnershipTransferredSetAdminSetDeprecatedTokenCreatedVaultUpdated
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "Deprecated",
"type": "error"
},
{
"inputs": [],
"name": "InvalidCreatorInfo",
"type": "error"
},
{
"inputs": [],
"name": "InvalidCreatorReward",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInterfaceInfo",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTick",
"type": "error"
},
{
"inputs": [],
"name": "InvalidVaultConfiguration",
"type": "error"
},
{
"inputs": [],
"name": "NotFound",
"type": "error"
},
{
"inputs": [],
"name": "OnlyNonOriginatingChains",
"type": "error"
},
{
"inputs": [],
"name": "OnlyOriginatingChain",
"type": "error"
},
{
"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"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "ZeroTeamRewardRecipient",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldClankerDeployer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newClankerDeployer",
"type": "address"
}
],
"name": "ClankerDeployerUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldLocker",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newLocker",
"type": "address"
}
],
"name": "LiquidityLockerUpdated",
"type": "event"
},
{
"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": false,
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "isAdmin",
"type": "bool"
}
],
"name": "SetAdmin",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "deprecated",
"type": "bool"
}
],
"name": "SetDeprecated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creatorAdmin",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "interfaceAdmin",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "creatorRewardRecipient",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "interfaceRewardRecipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"indexed": false,
"internalType": "int24",
"name": "startingTickIfToken0IsNewToken",
"type": "int24"
},
{
"indexed": false,
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountTokensBought",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "vaultDuration",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint8",
"name": "vaultPercentage",
"type": "uint8"
},
{
"indexed": false,
"internalType": "address",
"name": "msgSender",
"type": "address"
}
],
"name": "TokenCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldVault",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newVault",
"type": "address"
}
],
"name": "VaultUpdated",
"type": "event"
},
{
"inputs": [],
"name": "MAX_CREATOR_REWARD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_TICK",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_VAULT_PERCENTAGE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "POOL_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TOKEN_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "admins",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "claimRewards",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "string",
"name": "image",
"type": "string"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "string",
"name": "context",
"type": "string"
},
{
"internalType": "uint256",
"name": "originatingChainId",
"type": "uint256"
}
],
"internalType": "struct IClanker.TokenConfig",
"name": "tokenConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint8",
"name": "vaultPercentage",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "vaultDuration",
"type": "uint256"
}
],
"internalType": "struct IClanker.VaultConfig",
"name": "vaultConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "pairedToken",
"type": "address"
},
{
"internalType": "int24",
"name": "tickIfToken0IsNewToken",
"type": "int24"
}
],
"internalType": "struct IClanker.PoolConfig",
"name": "poolConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint24",
"name": "pairedTokenPoolFee",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "pairedTokenSwapAmountOutMinimum",
"type": "uint256"
}
],
"internalType": "struct IClanker.InitialBuyConfig",
"name": "initialBuyConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256",
"name": "creatorReward",
"type": "uint256"
},
{
"internalType": "address",
"name": "creatorAdmin",
"type": "address"
},
{
"internalType": "address",
"name": "creatorRewardRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "interfaceAdmin",
"type": "address"
},
{
"internalType": "address",
"name": "interfaceRewardRecipient",
"type": "address"
}
],
"internalType": "struct IClanker.RewardsConfig",
"name": "rewardsConfig",
"type": "tuple"
}
],
"internalType": "struct IClanker.DeploymentConfig",
"name": "deploymentConfig",
"type": "tuple"
}
],
"name": "deployToken",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "string",
"name": "image",
"type": "string"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "string",
"name": "context",
"type": "string"
},
{
"internalType": "uint256",
"name": "originatingChainId",
"type": "uint256"
}
],
"internalType": "struct IClanker.TokenConfig",
"name": "tokenConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint8",
"name": "vaultPercentage",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "vaultDuration",
"type": "uint256"
}
],
"internalType": "struct IClanker.VaultConfig",
"name": "vaultConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "pairedToken",
"type": "address"
},
{
"internalType": "int24",
"name": "tickIfToken0IsNewToken",
"type": "int24"
}
],
"internalType": "struct IClanker.PoolConfig",
"name": "poolConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint24",
"name": "pairedTokenPoolFee",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "pairedTokenSwapAmountOutMinimum",
"type": "uint256"
}
],
"internalType": "struct IClanker.InitialBuyConfig",
"name": "initialBuyConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256",
"name": "creatorReward",
"type": "uint256"
},
{
"internalType": "address",
"name": "creatorAdmin",
"type": "address"
},
{
"internalType": "address",
"name": "creatorRewardRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "interfaceAdmin",
"type": "address"
},
{
"internalType": "address",
"name": "interfaceRewardRecipient",
"type": "address"
}
],
"internalType": "struct IClanker.RewardsConfig",
"name": "rewardsConfig",
"type": "tuple"
}
],
"internalType": "struct IClanker.DeploymentConfig",
"name": "deploymentConfig",
"type": "tuple"
},
{
"internalType": "address",
"name": "teamRewardRecipient",
"type": "address"
}
],
"name": "deployTokenWithCustomTeamRewardRecipient",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "string",
"name": "image",
"type": "string"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "string",
"name": "context",
"type": "string"
},
{
"internalType": "uint256",
"name": "originatingChainId",
"type": "uint256"
}
],
"internalType": "struct IClanker.TokenConfig",
"name": "tokenConfig",
"type": "tuple"
},
{
"internalType": "address",
"name": "tokenAdmin",
"type": "address"
}
],
"name": "deployTokenZeroSupply",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "deploymentInfoForToken",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "address",
"name": "locker",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "deprecated",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getTokensDeployedByUser",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "address",
"name": "locker",
"type": "address"
}
],
"internalType": "struct IClanker.DeploymentInfo[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "uniswapV3Factory_",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "address",
"name": "swapRouter_",
"type": "address"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
},
{
"internalType": "address",
"name": "liquidityLocker_",
"type": "address"
},
{
"internalType": "address",
"name": "vault_",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "liquidityLocker",
"outputs": [
{
"internalType": "contract ILpLockerv2",
"name": "",
"type": "address"
}
],
"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": "address",
"name": "admin",
"type": "address"
},
{
"internalType": "bool",
"name": "isAdmin",
"type": "bool"
}
],
"name": "setAdmin",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "deprecated_",
"type": "bool"
}
],
"name": "setDeprecated",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "swapRouter",
"outputs": [
{
"internalType": "contract ISwapRouter",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "tokensDeployedByUsers",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "address",
"name": "locker",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "uniswapV3Factory",
"outputs": [
{
"internalType": "contract IUniswapV3Factory",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newLocker",
"type": "address"
}
],
"name": "updateLiquidityLocker",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newVault",
"type": "address"
}
],
"name": "updateVault",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "vault",
"outputs": [
{
"internalType": "contract IClankerVault",
"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.28;
import {ClankerDeployer} from "./ClankerDeployer.sol";
import {IClanker} from "./interfaces/IClanker.sol";
import {IClankerVault} from "./interfaces/IClankerVault.sol";
import {ILpLockerv2} from "./interfaces/ILpLockerv2.sol";
import {
INonfungiblePositionManager,
ISwapRouter,
IUniswapV3Factory,
IUniswapV3Pool
} from "./interfaces/uniswapv3.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol";
////////////////////////////////////////////////////////////////////////////////////////////
// //
// //
// /$$ /$$ //
// | $$ | $/ //
// /$$$$$$ /$$$$$$ | $$$$$$$|_/ //
// /$$__ $$ /$$__ $$| $$__ $$ //
// | $$ \ $$| $$ \ $$| $$ \ $$ //
// | $$ | $$| $$ | $$| $$ | $$ //
// | $$$$$$/| $$$$$$/| $$ | $$ //
// \______/ \______/ |__/ |__/ //
// //
// //
// //
// /$$ /$$ /$$ /$$ //
// | $$ | $$ | $$ | $$ //
// /$$$$$$$| $$ /$$$$$$ /$$$$$$$ | $$ /$$ /$$$$$$$ /$$$$$$ /$$$$$$ //
// /$$_____/| $$ |____ $$| $$__ $$| $$ /$$/ /$$__ $$ |____ $$|_ $$_/ //
// | $$ | $$ /$$$$$$$| $$ \ $$| $$$$$$/ | $$ | $$ /$$$$$$$ | $$ //
// | $$ | $$ /$$__ $$| $$ | $$| $$_ $$ | $$ | $$ /$$__ $$ | $$ /$$ //
// | $$$$$$$| $$| $$$$$$$| $$ | $$| $$ \ $$ | $$$$$$$| $$$$$$$ | $$$$/ //
// \_______/|__/ \_______/|__/ |__/|__/ \__/ \_______/ \_______/ \___/ //
// //
// //
// //
// //
// //
////////////////////////////////////////////////////////////////////////////////////////////
/// @notice Clanker Token Launcher
contract Clanker is Ownable, ReentrancyGuard, IClanker {
using TickMath for int24;
string constant version = "0.3.1";
IUniswapV3Factory public uniswapV3Factory;
INonfungiblePositionManager public positionManager;
ISwapRouter public swapRouter;
address public weth;
uint256 public constant TOKEN_SUPPLY = 100_000_000_000_000_000_000_000_000_000; // 100b with 18 decimals
uint24 public constant POOL_FEE = 10_000; // 1%
int24 public constant TICK_SPACING = 200;
int24 public constant MAX_TICK = 887_200;
ILpLockerv2 public liquidityLocker;
IClankerVault public vault;
uint256 public constant MAX_CREATOR_REWARD = 80;
uint256 public constant MAX_VAULT_PERCENTAGE = 30;
bool public deprecated;
mapping(address => bool) public admins;
mapping(address => DeploymentInfo[]) public tokensDeployedByUsers;
mapping(address => DeploymentInfo) public deploymentInfoForToken;
constructor(address owner_) Ownable(owner_) {
// only non-originating tokens deployments are enabled
// before initialization
deprecated = true;
}
function initialize(
address uniswapV3Factory_,
address positionManager_,
address swapRouter_,
address weth_,
address liquidityLocker_,
address vault_
) external onlyOwner {
// uniswap configurations
uniswapV3Factory = IUniswapV3Factory(uniswapV3Factory_);
positionManager = INonfungiblePositionManager(positionManager_);
swapRouter = ISwapRouter(swapRouter_);
// weth configurations
weth = weth_;
IERC20(weth).approve(address(positionManager), type(uint256).max);
// liquidity locker configurations
liquidityLocker = ILpLockerv2(liquidityLocker_);
// vault configurations
vault = IClankerVault(vault_);
// enable deployments
deprecated = false;
}
function getTokensDeployedByUser(address user)
external
view
returns (DeploymentInfo[] memory)
{
return tokensDeployedByUsers[user];
}
function updateLiquidityLocker(address newLocker) external onlyOwner {
address oldLocker = address(liquidityLocker);
liquidityLocker = ILpLockerv2(newLocker);
emit LiquidityLockerUpdated(oldLocker, newLocker);
}
function updateVault(address newVault) external onlyOwner {
address oldVault = address(vault);
vault = IClankerVault(newVault);
emit VaultUpdated(oldVault, newVault);
}
function setDeprecated(bool deprecated_) external onlyOwner {
deprecated = deprecated_;
emit SetDeprecated(deprecated_);
}
function setAdmin(address admin, bool isAdmin) external onlyOwner {
admins[admin] = isAdmin;
emit SetAdmin(admin, isAdmin);
}
function claimRewards(address token) external {
DeploymentInfo memory deploymentInfo = deploymentInfoForToken[token];
if (deploymentInfo.token == address(0)) revert NotFound();
ILpLockerv2(deploymentInfo.locker).collectRewards(deploymentInfo.positionId);
}
// deploy a token on a non-originating chain with 0 supply,
// this can be used to bridge tokens between superchains.
function deployTokenZeroSupply(TokenConfig memory tokenConfig, address tokenAdmin)
external
returns (address tokenAddress)
{
if (block.chainid == tokenConfig.originatingChainId) revert OnlyNonOriginatingChains();
tokenAddress = ClankerDeployer.deployToken(tokenConfig, tokenAdmin, TOKEN_SUPPLY);
}
// Deploy a token with a custom protocol reward recipient,
// only protocol admins can call this function
function deployTokenWithCustomTeamRewardRecipient(
DeploymentConfig memory deploymentConfig,
address teamRewardRecipient
) external payable returns (address tokenAddress, uint256 positionId) {
if (!admins[msg.sender]) revert Unauthorized();
if (teamRewardRecipient == address(0)) revert ZeroTeamRewardRecipient();
(tokenAddress, positionId) = deployToken(deploymentConfig);
// set the override protocol reward recipient for the token
liquidityLocker.setOverrideTeamRewardRecipientForToken(positionId, teamRewardRecipient);
}
// Deploy a token and pool with the option to vault the token and buy an initial amount
function deployToken(DeploymentConfig memory deploymentConfig)
public
payable
nonReentrant
returns (address tokenAddress, uint256 positionId)
{
if (deprecated) revert Deprecated();
if (block.chainid != deploymentConfig.tokenConfig.originatingChainId) {
revert OnlyOriginatingChain();
}
// deploy the token
tokenAddress = ClankerDeployer.deployToken(
deploymentConfig.tokenConfig, deploymentConfig.rewardsConfig.creatorAdmin, TOKEN_SUPPLY
);
uint256 poolSupply = TOKEN_SUPPLY;
uint256 vaultSupply = 0;
// attempt to vault the token if the vault config was set
if (
deploymentConfig.vaultConfig.vaultDuration > 0
|| deploymentConfig.vaultConfig.vaultPercentage > 0
) {
(poolSupply, vaultSupply) = _vaultToken(
tokenAddress,
deploymentConfig.vaultConfig.vaultPercentage,
deploymentConfig.vaultConfig.vaultDuration,
deploymentConfig.rewardsConfig.creatorAdmin
);
}
// configure the pool
IERC20(tokenAddress).approve(address(positionManager), poolSupply);
positionId = _configurePool(
tokenAddress,
deploymentConfig.poolConfig.pairedToken,
deploymentConfig.poolConfig.tickIfToken0IsNewToken,
poolSupply
);
// lock the lp tokens
_lockLPTokens(positionId, deploymentConfig.rewardsConfig);
// perform initial buy if eth was sent, use the creator admin as the recipient
uint256 amountTokensBought = msg.value > 0
? _initialBuy(
tokenAddress,
deploymentConfig.poolConfig.pairedToken,
deploymentConfig.initialBuyConfig.pairedTokenPoolFee,
deploymentConfig.initialBuyConfig.pairedTokenSwapAmountOutMinimum,
deploymentConfig.rewardsConfig.creatorAdmin
)
: 0;
// add the deployment info to the deployment info for token
DeploymentInfo memory deploymentInfo = DeploymentInfo({
token: tokenAddress,
positionId: positionId,
locker: address(liquidityLocker)
});
deploymentInfoForToken[tokenAddress] = deploymentInfo;
tokensDeployedByUsers[deploymentConfig.rewardsConfig.creatorAdmin].push(deploymentInfo);
emit TokenCreated({
tokenAddress: tokenAddress,
positionId: positionId,
creatorAdmin: deploymentConfig.rewardsConfig.creatorAdmin,
creatorRewardRecipient: deploymentConfig.rewardsConfig.creatorRewardRecipient,
interfaceAdmin: deploymentConfig.rewardsConfig.interfaceAdmin,
interfaceRewardRecipient: deploymentConfig.rewardsConfig.interfaceRewardRecipient,
name: deploymentConfig.tokenConfig.name,
symbol: deploymentConfig.tokenConfig.symbol,
startingTickIfToken0IsNewToken: deploymentConfig.poolConfig.tickIfToken0IsNewToken,
metadata: deploymentConfig.tokenConfig.metadata,
amountTokensBought: amountTokensBought,
vaultDuration: deploymentConfig.vaultConfig.vaultDuration,
vaultPercentage: deploymentConfig.vaultConfig.vaultPercentage,
msgSender: msg.sender
});
}
// Vault a token and allocate the vault supply to the vault allocation admin
function _vaultToken(
address token,
uint8 vaultPercentage,
uint256 vaultDuration,
address vaultAllocationAdmin
) internal returns (uint256 poolSupply, uint256 vaultSupply) {
if (vaultPercentage > MAX_VAULT_PERCENTAGE || vaultPercentage == 0) {
revert InvalidVaultConfiguration();
}
vaultSupply = (TOKEN_SUPPLY * vaultPercentage) / 100;
poolSupply = TOKEN_SUPPLY - vaultSupply;
// Lock up the vault allocation for the admin
IERC20(token).approve(address(vault), vaultSupply);
vault.deposit(token, vaultSupply, block.timestamp + vaultDuration, vaultAllocationAdmin);
}
// Lock the lp tokens and add the user reward recipient to the liquidity locker
function _lockLPTokens(uint256 positionId, RewardsConfig memory rewardsConfig) internal {
// ensure the creator reward is not greater than the max creator reward or zero
if (rewardsConfig.creatorReward > MAX_CREATOR_REWARD || rewardsConfig.creatorReward == 0) {
revert InvalidCreatorReward();
}
// ensure that the creator admin is set
if (rewardsConfig.creatorAdmin == address(0)) {
revert InvalidCreatorInfo();
}
// if the creator reward is not max, ensure the interface admin is set
if (
rewardsConfig.creatorReward < MAX_CREATOR_REWARD
&& rewardsConfig.interfaceAdmin == address(0)
) {
revert InvalidInterfaceInfo();
}
// transfer the lp position to the liquidity locker
positionManager.safeTransferFrom(address(this), address(liquidityLocker), positionId);
// add the user reward recipient to the liquidity locker
liquidityLocker.addTokenReward(
ILpLockerv2.TokenRewardInfo({
lpTokenId: positionId,
creatorReward: rewardsConfig.creatorReward,
creator: ILpLockerv2.RewardRecipient({
admin: rewardsConfig.creatorAdmin,
recipient: rewardsConfig.creatorRewardRecipient
}),
interfacer: ILpLockerv2.RewardRecipient({
admin: rewardsConfig.interfaceAdmin,
recipient: rewardsConfig.interfaceRewardRecipient
})
})
);
}
function _configurePool(
address newToken,
address pairedToken,
int24 tickIfToken0IsNewToken,
uint256 supplyPerPool
) internal returns (uint256 positionId) {
// check tick spacing
if (tickIfToken0IsNewToken % TICK_SPACING != 0) {
revert InvalidTick();
}
// Pool ordering
bool token0IsNewToken = newToken < pairedToken;
// flip tick if token0 is not the new token
int24 tick = token0IsNewToken ? tickIfToken0IsNewToken : -tickIfToken0IsNewToken;
uint160 sqrtPriceX96 = tick.getSqrtRatioAtTick();
// Create pool
address pool = uniswapV3Factory.createPool(newToken, pairedToken, POOL_FEE);
// Initialize pool
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager
.MintParams(
token0IsNewToken ? newToken : pairedToken,
token0IsNewToken ? pairedToken : newToken,
POOL_FEE,
token0IsNewToken ? tick : -MAX_TICK,
token0IsNewToken ? MAX_TICK : tick,
token0IsNewToken ? supplyPerPool : 0,
token0IsNewToken ? 0 : supplyPerPool,
0,
0,
address(this),
block.timestamp
);
(positionId,,,) = positionManager.mint(params);
}
function _initialBuy(
address token,
address pairedToken,
uint24 pairedTokenPoolFee,
uint256 pairedTokenSwapAmountOutMinimum,
address recipient
) internal returns (uint256 amountTokensBought) {
// amount in is the amount of eth sent to perform the intial buy with
uint256 pairedTokenAmountIn = msg.value;
// if the paired token is not weth, we need to swap from weth to paired token
if (pairedToken != weth) {
// swap from weth to paired token
ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter
.ExactInputSingleParams({
tokenIn: weth, // The token we are exchanging from (ETH wrapped as WETH)
tokenOut: pairedToken, // The token we are exchanging to
fee: pairedTokenPoolFee, // The pool fee
recipient: address(this), // The recipient address
amountIn: msg.value, // The amount of ETH (WETH) to be swapped
amountOutMinimum: pairedTokenSwapAmountOutMinimum, // Minimum amount to receive
sqrtPriceLimitX96: 0 // No price limit
});
// execute the swap to get pair tokens for the initial buy
pairedTokenAmountIn = swapRouter.exactInputSingle{value: msg.value}(swapParams);
// approve the swap router to spend the paired token
IERC20(pairedToken).approve(address(swapRouter), pairedTokenAmountIn);
}
// swap from paired token to token
ISwapRouter.ExactInputSingleParams memory swapParamsToken = ISwapRouter
.ExactInputSingleParams({
tokenIn: pairedToken, // The token we are exchanging from
tokenOut: token, // The token we are exchanging to
fee: POOL_FEE, // The pool fee
recipient: recipient, // The recipient address
amountIn: pairedTokenAmountIn, // The amount of paired token to be swapped
amountOutMinimum: 0, // Minimum amount to receive
sqrtPriceLimitX96: 0 // No price limit
});
// execute the swap
amountTokensBought =
swapRouter.exactInputSingle{value: pairedToken == weth ? msg.value : 0}(swapParamsToken);
}
}
Chain explorer1384msChain node91ms