QianRWAFactory V2: same-transaction dev buy through the router
ETH above the deploy fee swaps WETH -> USDG -> quote -> new pool inside the launch transaction, floored by the caller's devBuyMinOut. The factory appends its own pool as the final hop. Fresh GuardHook; V1 factory stays authorized.
This commit is contained in:
@@ -14,13 +14,19 @@ import "./interfaces/IConfigurableHook.sol";
|
||||
import "./errors/Errors.sol";
|
||||
import "./QianLocker.sol";
|
||||
|
||||
/// @notice Launches tokens against an allowlisted quote asset (tokenized stocks, stablecoins) instead of WETH; no dev buy.
|
||||
interface IQianRWARouter {
|
||||
function buy(PoolKey[] memory path, address token, uint256 minTokensOut, uint256 deadline) external payable returns (uint256);
|
||||
}
|
||||
|
||||
/// @notice Launches tokens against an allowlisted quote asset (tokenized stocks, stablecoins) instead of WETH.
|
||||
/// @dev V2: optional same-tx dev buy — ETH above deployFee routes through the swap router along the caller's path.
|
||||
contract QianRWAFactory is ReentrancyGuard {
|
||||
using SafeERC20 for IERC20;
|
||||
using LiquidityProviderV4 for LiquidityProviderV4.Config;
|
||||
using CurrencyLibrary for Currency;
|
||||
|
||||
event ERC20TokenCreated(address tokenAddress);
|
||||
event DevBuy(address indexed token, address indexed buyer, uint256 ethIn, uint256 tokensOut);
|
||||
event QuotePaired(address indexed token, address indexed quote);
|
||||
event QuoteApprovalChanged(address indexed quote, bool approved);
|
||||
event DeployFeeUpdated(uint256 oldFee, uint256 newFee);
|
||||
@@ -67,17 +73,19 @@ contract QianRWAFactory is ReentrancyGuard {
|
||||
|
||||
address public immutable poolManager;
|
||||
address public immutable locker;
|
||||
address public immutable swapRouter;
|
||||
int24 private constant TICK_SPACING = 60;
|
||||
uint256 private constant FULL_TOKEN_SUPPLY = 1_000_000_000 * 10 ** 18;
|
||||
uint24 public constant MIN_POOL_FEE = 5000;
|
||||
uint24 public constant MAX_POOL_FEE = 200000;
|
||||
|
||||
constructor(address _poolManager, address _locker) {
|
||||
if (_poolManager == address(0) || _locker == address(0)) revert BadRouterConfig();
|
||||
constructor(address _poolManager, address _locker, address _swapRouter) {
|
||||
if (_poolManager == address(0) || _locker == address(0) || _swapRouter == address(0)) revert BadRouterConfig();
|
||||
platformController = msg.sender;
|
||||
feeVault = msg.sender;
|
||||
poolManager = _poolManager;
|
||||
locker = _locker;
|
||||
swapRouter = _swapRouter;
|
||||
}
|
||||
|
||||
function deployCoin(
|
||||
@@ -90,15 +98,17 @@ contract QianRWAFactory is ReentrancyGuard {
|
||||
address hookAddress,
|
||||
bytes calldata hookConfig,
|
||||
uint256 maxDeployFee,
|
||||
uint24 poolFee
|
||||
uint24 poolFee,
|
||||
PoolKey[] calldata devBuyPath,
|
||||
uint256 devBuyMinOut
|
||||
) public payable nonReentrant returns (address tokenAddress) {
|
||||
if (!deployCoinEnabled) revert DeployDisabled();
|
||||
if (!approvedQuotes[quote]) revert InvalidConfig();
|
||||
if (configId >= liquidityConfigCount[quote]) revert InvalidConfig();
|
||||
if (deployFee > maxDeployFee) revert DeployFeeTooHigh();
|
||||
if (poolFee < MIN_POOL_FEE || poolFee > MAX_POOL_FEE) revert InvalidPoolFee();
|
||||
// no dev buy on quote pairs: exact fee only, paid in raw ETH
|
||||
if (msg.value != deployFee) revert InsufficientDeployFee();
|
||||
// ETH above deployFee is the dev buy, swapped through the router below
|
||||
if (msg.value < deployFee) revert InsufficientDeployFee();
|
||||
address hook = hookAddress == address(0) ? guardHook : hookAddress;
|
||||
if (!approvedHooks[hook]) revert HookNotApproved();
|
||||
{
|
||||
@@ -118,10 +128,20 @@ contract QianRWAFactory is ReentrancyGuard {
|
||||
IConfigurableHook(hook).configureHook(poolId, tokenAddress, hookConfig);
|
||||
emit HookConfigured(tokenAddress, hook, poolId);
|
||||
}
|
||||
if (msg.value > 0 && feeVault != address(0)) {
|
||||
(bool ok, ) = feeVault.call{value: msg.value}("");
|
||||
if (deployFee > 0 && feeVault != address(0)) {
|
||||
(bool ok, ) = feeVault.call{value: deployFee}("");
|
||||
if (!ok) revert TransferFailed();
|
||||
}
|
||||
uint256 devBuyEth = msg.value - deployFee;
|
||||
if (devBuyEth > 0) {
|
||||
// caller's prefix ends at the quote; the real pool is appended so the final hop can't be spoofed
|
||||
PoolKey[] memory path = new PoolKey[](devBuyPath.length + 1);
|
||||
for (uint256 i = 0; i < devBuyPath.length; i++) path[i] = devBuyPath[i];
|
||||
path[devBuyPath.length] = tokenToPoolKey[tokenAddress];
|
||||
uint256 bought = IQianRWARouter(swapRouter).buy{value: devBuyEth}(path, tokenAddress, devBuyMinOut, block.timestamp);
|
||||
IERC20(tokenAddress).safeTransfer(msg.sender, bought);
|
||||
emit DevBuy(tokenAddress, msg.sender, devBuyEth, bought);
|
||||
}
|
||||
tokenConfigs[tokenAddress] = TokenConfig({
|
||||
deployTime: block.timestamp,
|
||||
metadata: _metadata,
|
||||
|
||||
Reference in New Issue
Block a user