Qian V2 Contracts

Onchain token launch with permanently locked liquidity and DEX routing
(Uniswap v3 + v4). No admin keys, no mint/blacklist. MIT licensed.
Deployed addresses in README.
This commit is contained in:
Qian
2026-07-17 02:31:24 +00:00
commit 013b52f5a4
28 changed files with 2828 additions and 0 deletions
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
// Qian.ag
pragma solidity ^0.8.24;
interface INonfungiblePositionManager {
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
function factory() external view returns (address);
function WETH9() external view returns (address);
function positions(uint256 tokenId) external view returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
function createAndInitializePoolIfNecessary(
address token0,
address token1,
uint24 fee,
uint160 sqrtPriceX96
) external returns (address pool);
function mint(MintParams calldata params) external returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
);
function collect(CollectParams calldata params) external payable returns (
uint256 amount0,
uint256 amount1
);
function setApprovalForAll(address operator, bool approved) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT
// Qian.ag
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./INonfungiblePositionManager.sol";
error InvalidTickRange();
error PoolPriceHijacked();
interface IUniswapV3PoolSlot0 {
function slot0() external view returns (
uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality,
uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked
);
}
library LiquidityProvider {
struct Config {
uint160 sqrtPriceX96A;
uint160 sqrtPriceX96B;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
}
function provideLiquidity(
address positionManager,
address tokenA,
address tokenB,
Config memory config
) internal returns (uint256 tokenId) {
bool tokenAIsToken0 = tokenA < tokenB;
address token0 = tokenAIsToken0 ? tokenA : tokenB;
address token1 = tokenAIsToken0 ? tokenB : tokenA;
IERC20(token0).approve(positionManager, type(uint256).max);
IERC20(token1).approve(positionManager, type(uint256).max);
INonfungiblePositionManager manager = INonfungiblePositionManager(positionManager);
uint160 sqrtPriceX96 = tokenAIsToken0
? config.sqrtPriceX96A
: config.sqrtPriceX96B;
int24 tickLower;
int24 tickUpper;
if (tokenAIsToken0) {
tickLower = -int24(config.tickLower);
tickUpper = int24(config.tickUpper);
} else {
tickLower = -int24(config.tickUpper);
tickUpper = int24(config.tickLower);
}
if (tickLower >= tickUpper) revert InvalidTickRange();
uint256 amount0Desired = tokenAIsToken0 ? config.amount0Desired : config.amount1Desired;
uint256 amount1Desired = tokenAIsToken0 ? config.amount1Desired : config.amount0Desired;
// "IfNecessary" never reverts on an existing pool, so a front-run init at a WRONG
// price would silently skew the mint + dev buy. Assert the live price; a same-price
// front-run is harmless (the attacker just created our pool for us).
address pool = manager.createAndInitializePoolIfNecessary(token0, token1, 10000, sqrtPriceX96);
(uint160 livePrice, , , , , , ) = IUniswapV3PoolSlot0(pool).slot0();
if (livePrice != sqrtPriceX96) revert PoolPriceHijacked();
(tokenId, , , ) = manager.mint(
INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: 10000,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
})
);
}
}
+329
View File
@@ -0,0 +1,329 @@
// SPDX-License-Identifier: MIT
// Qian.ag
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./LiquidityProvider.sol";
import "./INonfungiblePositionManager.sol";
import "./QianToken.sol";
interface ISwapRouter02 {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params)
external
payable
returns (uint256 amountOut);
}
error BadRouterConfig();
error DeployDisabled();
error NewCreatorZero();
error NotController();
error Unauthorized();
error InsufficientDeployFee();
error DeployFeeTooHigh();
error InvalidConfig();
error TransferFailed();
interface IWETH {
function deposit() external payable;
function withdraw(uint256 amount) external;
}
interface IBindingLocker {
function setCreator(address token, address newCreator) external;
function creatorOf(address token) external view returns (address);
}
contract QianFactoryV3 is ReentrancyGuard {
using SafeERC20 for IERC20;
event ERC20TokenCreated(address tokenAddress);
event DeployCoinToggled(bool enabled);
event DeployFeeUpdated(uint256 oldFee, uint256 newFee);
event CreatorChanged(address indexed token, address indexed oldCreator, address indexed newCreator);
event ControllerTransferInitiated(address indexed currentController, address indexed pendingController);
event ControllerTransferred(address indexed oldController, address indexed newController);
struct TokenInfo {
address tokenAddress;
string name;
string symbol;
address deployer;
uint256 time;
string metadata;
uint256 nftId;
}
/// @notice Indexer read surface. The by-index list and tokenToNFTId mapping were
/// deleted — the token itself and the locker's binding carry the same data.
mapping(address => TokenInfo) public tokenInfoByAddress;
uint256 public tokenCount = 0;
address public platformController;
/// @dev platformController is both the admin and the fee sink (there is no
/// setFeeVault), so it must be transferable. Two-step, mirroring QianFactoryV4.
address public pendingController;
bool public deployCoinEnabled = false;
uint256 public deployFee = 0.005 ether;
/// @notice Live receiver of a token's 70% creator fees, read by QianFeeRouter.
/// Reassignable via changeTokenFeeReceiver (CTO). The token's own `creator` is an
/// immutable record of the original launcher, not this.
/// @dev The binding in the locker is the only copy — it is written atomically with the
/// lock and is what QianFeeRouter pays. This reads through so callers keep one address.
function tokenCreator(address token) public view returns (address) {
return IBindingLocker(locker).creatorOf(token);
}
struct LiquidityConfig {
uint160 sqrtPriceX96A;
uint160 sqrtPriceX96B;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
}
mapping(uint256 => LiquidityConfig) public liquidityConfigs;
uint256 public liquidityConfigCount = 0;
address public immutable positionManager;
address public immutable weth;
address public immutable swapRouter;
address public immutable locker;
// dev buy pays the same 1% QianSwapRouter charges on every other trade
uint16 public constant DEV_BUY_FEE_BPS = 100;
uint256 private constant FULL_TOKEN_SUPPLY = 1_000_000_000 * 10 ** 18;
constructor(address _positionManager, address _swapRouter, address _weth, address _locker) {
if (_positionManager == address(0) || _swapRouter == address(0) || _weth == address(0) || _locker == address(0)) {
revert BadRouterConfig();
}
platformController = msg.sender;
positionManager = _positionManager;
swapRouter = _swapRouter;
weth = _weth;
locker = _locker;
liquidityConfigs[0] = LiquidityConfig({
sqrtPriceX96A: 2_097_222_954_085_974_151_159_020,
sqrtPriceX96B: 2_993_054_087_624_369_706_988_840_588_130_593,
tickLower: 210800,
tickUpper: 310800,
amount0Desired: FULL_TOKEN_SUPPLY,
amount1Desired: 0
});
liquidityConfigCount = 1;
}
/// @param maxDeployFee caller's ceiling on deployFee, so a controller fee change
/// cannot front-run this tx
function deployCoin(string memory _name, string memory _symbol, string memory _metadata, bytes32 salt, uint256 configId, uint256 maxDeployFee) public payable nonReentrant returns (uint256 tokensReceived) {
if (!deployCoinEnabled) revert DeployDisabled();
if (configId >= liquidityConfigCount) revert InvalidConfig();
if (deployFee > maxDeployFee) revert DeployFeeTooHigh();
if (msg.value < deployFee) revert InsufficientDeployFee();
// dev buy pays the same 1% the swap router takes on every other trade;
// it ships with the deploy fee as a single transfer (no separate hop)
uint256 devBuyValue = msg.value - deployFee;
uint256 devBuyFee = (devBuyValue * DEV_BUY_FEE_BPS) / 10_000;
uint256 remainingValue = devBuyValue - devBuyFee;
QianToken t = new QianToken{salt: salt}(
_name,
_symbol,
_metadata,
msg.sender,
address(this)
);
emit ERC20TokenCreated(address(t));
address coin_address = address(t);
uint256 nftId = provideLiquidity(coin_address, weth, configId, msg.sender);
uint256 platformTake = deployFee + devBuyFee;
if (platformTake > 0) {
(bool success, ) = platformController.call{value: platformTake}("");
if (!success) revert TransferFailed();
}
tokensReceived = 0;
if (remainingValue > 0) {
ISwapRouter02(swapRouter).exactInputSingle{ value: remainingValue }(
ISwapRouter02.ExactInputSingleParams({
tokenIn: weth,
tokenOut: coin_address,
fee: 10000,
recipient: address(this),
amountIn: remainingValue,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
})
);
IERC20 token = IERC20(coin_address);
tokensReceived = token.balanceOf(address(this));
token.transfer(msg.sender, tokensReceived);
}
tokenInfoByAddress[coin_address] = TokenInfo({
tokenAddress: coin_address,
name: _name,
symbol: _symbol,
deployer: msg.sender,
time: block.timestamp,
metadata: _metadata,
nftId: nftId
});
tokenCount++;
return tokensReceived;
}
// No withdrawFees and no receive(): nothing accumulates here, so nothing needs a
// sweep. deployCoin is payable and takes msg.value directly; the dev buy and the fee
// transfer are outbound.
function toggleDeployCoin() external {
if (msg.sender != platformController) revert NotController();
deployCoinEnabled = !deployCoinEnabled;
emit DeployCoinToggled(deployCoinEnabled);
}
function setDeployFee(uint256 _deployFee) external {
if (msg.sender != platformController) revert NotController();
uint256 oldFee = deployFee;
deployFee = _deployFee;
emit DeployFeeUpdated(oldFee, _deployFee);
}
function createLiquidityConfig(
uint160 _sqrtPriceX96A,
uint160 _sqrtPriceX96B,
int24 _tickLower,
int24 _tickUpper,
uint256 _amount0Desired,
uint256 _amount1Desired
) external returns (uint256 configId) {
if (msg.sender != platformController) revert NotController();
if (_tickLower <= 0 || _tickUpper <= 0 || _tickLower >= _tickUpper) revert InvalidTickRange();
configId = liquidityConfigCount;
liquidityConfigs[configId] = LiquidityConfig({
sqrtPriceX96A: _sqrtPriceX96A,
sqrtPriceX96B: _sqrtPriceX96B,
tickLower: _tickLower,
tickUpper: _tickUpper,
amount0Desired: _amount0Desired,
amount1Desired: _amount1Desired
});
liquidityConfigCount++;
return configId;
}
function updateLiquidityConfig(
uint256 _configId,
uint160 _sqrtPriceX96A,
uint160 _sqrtPriceX96B,
int24 _tickLower,
int24 _tickUpper,
uint256 _amount0Desired,
uint256 _amount1Desired
) external {
if (msg.sender != platformController) revert NotController();
if (_configId >= liquidityConfigCount) revert InvalidConfig();
if (_tickLower <= 0 || _tickUpper <= 0 || _tickLower >= _tickUpper) revert InvalidTickRange();
liquidityConfigs[_configId] = LiquidityConfig({
sqrtPriceX96A: _sqrtPriceX96A,
sqrtPriceX96B: _sqrtPriceX96B,
tickLower: _tickLower,
tickUpper: _tickUpper,
amount0Desired: _amount0Desired,
amount1Desired: _amount1Desired
});
}
function provideLiquidity(address tokenA, address tokenB, uint256 configId, address creator) internal returns (uint256 tokenId) {
LiquidityConfig memory config = liquidityConfigs[configId];
LiquidityProvider.Config memory libConfig = LiquidityProvider.Config({
sqrtPriceX96A: config.sqrtPriceX96A,
sqrtPriceX96B: config.sqrtPriceX96B,
tickLower: config.tickLower,
tickUpper: config.tickUpper,
amount0Desired: config.amount0Desired,
amount1Desired: config.amount1Desired
});
tokenId = LiquidityProvider.provideLiquidity(
positionManager,
tokenA,
tokenB,
libConfig
);
// Position NFT goes straight into the locker, carrying the (token, creator)
// binding in the transfer data — the locker records who this position belongs
// to in the same call that permanently locks it.
INonfungiblePositionManager(positionManager).safeTransferFrom(
address(this), locker, tokenId, abi.encode(tokenA, creator)
);
}
// Fee collection now lives in QianFeeRouter.collectFees(token) — permissionless,
// fixed 30% treasury / 70% creator split, served by the locker.
/// @notice Begin handing over the controller role, and with it the fee sink.
/// @dev Two-step: the new controller must accept, so a typo cannot strand this
/// factory's admin and revenue on an address nobody holds.
function transferController(address newController) external {
if (msg.sender != platformController) revert NotController();
if (newController == address(0)) revert NewCreatorZero();
pendingController = newController;
emit ControllerTransferInitiated(platformController, newController);
}
/// @notice Complete the handover. Callable only by the pending controller.
function acceptController() external {
if (msg.sender != pendingController) revert Unauthorized();
address oldController = platformController;
platformController = pendingController;
pendingController = address(0);
emit ControllerTransferred(oldController, platformController);
}
/// @notice Controller-only: reassign a token's creator fee rights (approved CTO).
/// @dev Fee routing only; cannot touch balances, transfers or supply.
/// @dev The locker's binding is the only record; it rejects this call unless the token
/// was bound by THIS factory and this factory is still authorized.
function changeTokenFeeReceiver(address tokenAddress, address newCreator) external {
if (msg.sender != platformController) revert NotController();
if (newCreator == address(0)) revert NewCreatorZero();
address oldCreator = tokenCreator(tokenAddress);
// a token no factory ever launched has no fee rights to reassign
if (oldCreator == address(0)) revert InvalidConfig();
emit CreatorChanged(tokenAddress, oldCreator, newCreator);
IBindingLocker(locker).setCreator(tokenAddress, newCreator);
}
}
+60
View File
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
//
// ██████╗ ██╗ █████╗ ███╗ ██╗
// ██╔═══██╗██║██╔══██╗████╗ ██║
// ██║ ██║██║███████║██╔██╗ ██║
// ██║▄▄ ██║██║██╔══██║██║╚██╗██║
// ╚██████╔╝██║██║ ██║██║ ╚████║
// ╚══▀▀═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝
//
// 钱 · Qian.ag — Multichain DEX & Launchpad
// Fixed supply. No owner, no mint, no games.
// — V2.1 —
//
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
/// @notice Fixed-supply ERC20. Supply is minted once here and there is no mint function.
/// No transfer hooks, no owner, no privileged functions.
contract QianToken is ERC20, ERC20Burnable {
/// @dev Same signature as OpenZeppelin Ownable. Indexers read renouncement from this
/// event rather than calling owner().
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @notice The factory that deployed this token.
address public immutable platform;
/// @notice Original launcher. A record, not the fee receiver — fee rights are
/// reassignable, so read `tokenCreator(token)` on `platform` for who is paid today.
address public immutable creator;
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 10 ** 18;
/// @notice ipfs://<cid> for the token's image/description, so indexers need no factory.
string public metadata;
constructor(
string memory _name,
string memory _symbol,
string memory _metadata,
address _creator,
address _platform
) ERC20(_name, _symbol) {
platform = _platform;
creator = _creator;
metadata = _metadata;
_mint(_platform, MAX_SUPPLY);
emit OwnershipTransferred(address(0), _platform);
emit OwnershipTransferred(_platform, address(0));
}
/// @notice Common indexer convention — same value as metadata().
function tokenURI() external view returns (string memory) {
return metadata;
}
/// @notice No owner, no mint, no pause, no blacklist.
function owner() public pure returns (address) {
return address(0);
}
}
+94
View File
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.20;
// BondingCurveLibrary
string constant ERR_BONDING_CURVE_LIBRARY_INVALID_AMOUNT_OUT =
"BondingCurveLibrary : ERR_BONDING_CURVE_LIBRARY_INVALID_AMOUNT_OUT";
string constant ERR_BONDING_CURVE_LIBRARY_INVALID_AMOUNT_IN =
"BondingCurveLibrary : ERR_BONDING_CURVE_LIBRARY_INVALID_AMOUNT_IN";
string constant ERR_BONDING_CURVE_LIBRARY_INSUFFICIENT_LIQUIDITY =
"BondingCurveLibrary : ERR_BONDING_CURVE_LIBRARY_INSUFFICIENT_LIQUIDITY";
string constant ERR_BONDING_CURVE_LIBRARY_INVALID_INPUTS =
"BondingCurveLibrary : ERR_BONDING_CURVE_LIBRARY_INVALID_INPUTS";
//Transfer Helper
string constant ERR_TRANSFER_NATIVE_FAILED = "ERR_TRANSFER_NATIVE_FAILED";
//BondingCurve
string constant ERR_BONDING_CURVE_ONLY_OWNER = "BondingCurve : ERR_BONDING_CURVE_ONLY_OWNER";
string constant ERR_BONDING_CURVE_ONLY_FACTORY = "BondingCurve : ERR_BONDING_CURVE_ONLY_FACTORY";
string constant ERR_BONDING_CURVE_FINISHED = "BondingCurve : ERR_BONDING_CURVE_FINISHED";
string constant ERR_BONDING_CURVE_INVALID_AMOUNT_OUT = "BondingCurve : ERR_BONDING_CURVE_INVALID_AMOUNT_OUT";
string constant ERR_BONDING_CURVE_INVALID_AMOUNT_IN = "BondingCurve : ERR_BONDING_CURVE_INVALID_AMOUNT_IN";
string constant ERR_BONDING_CURVE_OVERFLOW_TARGET = "BondingCurve : ERR_BONDING_CURVE_OVERFLOW_TARGET";
string constant ERR_BONDING_CURVE_INVALID_TO = "BondingCurve : ERR_BONDING_CURVE_INVALID_TO";
string constant ERR_BONDING_CURVE_INVALID_K = "BondingCurve : ERR_BONDING_CURVE_INVALID_K";
string constant ERR_BONDING_CURVE_INSUFFICIENT_RESERVE = "BondingCurve : ERR_BONDING_CURVE_INSUFFICIENT_RESERVE";
string constant ERR_BONDING_CURVE_ONLY_LOCK = "BondingCurve : ERR_LISTING_ONLY_LOCK";
string constant ERR_BONDING_CURVE_LOCKED = "BondingCurve : ERR_BONDING_CURVE_LOCKED";
string constant ERR_BONDING_CURVE_ALREADY_LISTED = "BondingCurve : ERR_BONDING_CURVE_ALREADY_LISTED";
string constant ERR_BONDING_CURVE_MUST_LISTING = "BondingCurve : ERR_BONDING_CURVE_MUST_LISTING";
//BondingCurveFactory
string constant ERR_BONDING_CURVE_FACTORY_ONLY_OWNER = "BondingCurveFactory : ERR_BONDING_CURVE_FACTORY_ONLY_OWNER";
string constant ERR_BONDING_CURVE_FACTORY_ONLY_CORE = "BondingCurveFactory : ERR_BONDING_CURVE_FACTORY_ONLY_CORE";
//CORE
string constant ERR_CORE_EXPIRED = "Core : ERR_CORE_EXPIRED";
string constant ERR_CORE_INVALID_FEE = "Core : ERR_CORE_INVALID_FEE";
string constant ERR_CORE_INVALID_SEND_NATIVE = "Core : ERR_CORE_INVALID_SEND_NATIVE";
string constant ERR_CORE_INVALID_AMOUNT_IN = "Core : ERR_CORE_INVALID_AMOUNT_IN";
string constant ERR_CORE_INVALID_AMOUNT_OUT = "Core : ERR_CORE_INVALID_AMOUNT_OUT";
string constant ERR_CORE_INVALID_ALLOWANCE = "Core : ERR_CORE_INVALID_ALLOWANCE";
string constant ERR_CORE_INVALID_AMOUNT_IN_MAX = "Core : ERR_CORE_INVALID_AMOUNT_IN_MAX";
string constant ERR_CORE_ALREADY_INITIALIZED = "Core : ERR_CORE_ALREADY_INITIALIZED";
//LOCK
string constant ERR_LOCK_ONLY_OWNER = "Lock : ERR_LOCK_ONLY_OWNER";
string constant ERR_LOCK_INVALID_AMOUNT_IN = "Lock : ERR_LOCK_INVALID_AMOUNT_IN";
string constant ERR_TOKEN_NOT_LOCKED = "Lock : ERR_TOKEN_NOT_LOCKED";
string constant ERR_INVALID_ACCOUNT = "Lock : ERR_INVALID_ACCOUNT";
string constant ERR_INVALID_TOKEN_ADDRESS = "Lock : ERR_INVALID_TOKEN_ADDRESS";
// MINT PARTY
string constant ERR_MINT_PARTY_ONLY_OWNER = "MintParty : ERR_MINT_PARTY_ONLY_OWNER";
string constant ERR_MINT_PARTY_ONLY_FACTORY = "MintParty : ERR_MINT_PARTY_ONLY_FACTORY";
string constant ERR_MINT_PARTY_FINISHED = "MintParty : ERR_MINT_PARTY_FINISHED";
string constant ERR_MINT_PARTY_INVALID_FUNDING_AMOUNT = "MintParty : ERR_MINT_PARTY_INVALID_FUNDING_AMOUNT";
string constant ERR_MINT_PARTY_INVALID_WHITE_LIST = "MintParty : ERR_MINT_PARTY_INVALID_WHITE_LIST";
string constant ERR_MINT_PARTY_INVALID_WHITELIST = "MintParty : ERR_MINT_PARTY_INVALID_WHITELIST";
string constant ERR_MINT_PARTY_WITHDRAW_AMOUNT_IS_ZERO = "MintParty : ERR_MINT_PARTY_WITHDRAW_AMOUNT_IS_ZERO";
string constant ERR_MINT_PARTY_ALREADY_DEPOSITED = "MintParty : ERR_MINT_PARTY_ALREADY_DEPOSITED";
string constant ERR_MINT_PARTY_BALANCE_ZERO = "MintParty : ERR_MINT_PARTY_BALANCE_ZERO";
string constant ERR_MINT_PARTY_WHITELIST_ALREADY_ADDED = "MintParty : ERR_MINT_PARTY_WHITELIST_ALREADY_ADDED";
//MINT_PARTY Factory
string constant ERR_MINT_PARTY_FACTORY_NOT_FINISHED = "MintPartyFactory : ERR_MINT_PARTY_FACTORY_NOT_FINISHED";
string constant ERR_MINT_PARTY_FACTORY_INVALID_FUNDING_AMOUNT =
"MintPartyFactory : ERR_MINT_PARTY_FACTORY_INVALID_FUNDING_AMOUNT";
string constant ERR_MINT_PARTY_FACTORY_INVALID_MAXIMUM_WHITELIST =
"MintPartyFactory : ERR_MINT_PARTY_FACTORY_INVALID_MAXIMUM_WHITELIST";
string constant ERR_MINT_PARTY_FACTORY_ONLY_OWNER = "MintPartyFactory : ERR_MINT_PARTY_FACTORY_ONLY_OWNER";
//Token
string constant ERR_TOKEN_ONLY_FACTORY = "Token : ERR_TOKEN_ONLY_FACTORY";
string constant ERR_TOKEN_ONLY_ONCE_MINT = "Token : ERR_TOKEN_ONLY_ONCE_MINT";
string constant ERR_TOKEN_INVALID_EXPIRED = "Token : ERR_TOKEN_INVALID_EXPIRED";
string constant ERR_TOKEN_INVALID_SIGNATURE = "Token : ERR_TOKEN_INVALID_SIGNATURE";
//FeeVault
string constant ERR_FEE_VAULT_INVALID_WNATIVE_ADDRESS = "FeeVault : ERR_FEE_VAULT_INVALID_WNATIVE_ADDRESS";
string constant ERR_FEE_VAULT_INVALID_OWNER = "FeeVault : ERR_FEE_VAULT_INVALID_OWNER";
string constant ERR_FEE_VAULT_NOT_OWNER = "FeeVault : ERR_FEE_VAULT_NOT_OWNER";
string constant ERR_FEE_VAULT_INVALID_RECEIVER = "FeeVault : ERR_FEE_VAULT_INVALID_RECEIVER";
string constant ERR_FEE_VAULT_INVALID_AMOUNT = "FeeVault : ERR_FEE_VAULT_INVALID_AMOUNT";
string constant ERR_FEE_VAULT_INSUFFICIENT_BALANCE = "FeeVault : ERR_FEE_VAULT_INSUFFICIENT_BALANCE";
string constant ERR_FEE_VAULT_TRANSFER_FAILED = "FeeVault : ERR_FEE_VAULT_TRANSFER_FAILED";
string constant ERR_FEE_VAULT_REENTRANT = "FeeVault : ERR_FEE_VAULT_REENTRANT";
//DexRouter
string constant ERR_DEX_ROUTER_EXPIRED = "DexRouter : ERR_DEX_ROUTER_EXPIRED";
string constant ERR_DEX_ROUTER_INVALID_SEND_NATIVE = "DexRouter : ERR_DEX_ROUTER_INVALID_SEND_NATIVE";
string constant ERR_DEX_ROUTER_INVALID_AMOUNT_IN = "DexRouter : ERR_DEX_ROUTER_INVALID_AMOUNT_IN";
string constant ERR_DEX_ROUTER_INVALID_FEE = "DexRouter : ERR_DEX_ROUTER_INVALID_FEE";
string constant ERR_DEX_ROUTER_INVALID_ALLOWANCE = "DexRouter : ERR_DEX_ROUTER_INVALID_ALLOWANCE";
string constant ERR_DEX_ROUTER_INVALID_AMOUNT_OUT_MIN = "DexRouter : ERR_DEX_ROUTER_INVALID_AMOUNT_OUT_MIN";
string constant ERR_DEX_ROUTER_INVALID_AMOUNT_IN_MAX = "DexRouter : ERR_DEX_ROUTER_INVALID_AMOUNT_IN_MAX";