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:
@@ -72,8 +72,11 @@ underlying asset stays with its issuer.
|
||||
- **QianRWAFactory** launches tokens paired to an approved quote asset (a
|
||||
tokenized stock or USDG) instead of WETH. The launch works like the core
|
||||
factories: full supply into a locked one-sided Uniswap V4 position. The quote
|
||||
is a per-launch parameter against a controller allowlist, each quote carries
|
||||
its own liquidity tiers, and the deploy fee is exact (no dev buy).
|
||||
is a per-launch parameter against a controller allowlist, and each quote carries
|
||||
its own liquidity tiers. ETH sent above the deploy fee becomes a same-transaction
|
||||
dev buy: the factory routes it through `QianRWARouter` along the caller's path,
|
||||
bounded by the caller's `devBuyMinOut`, and appends the new pool itself so the
|
||||
final hop cannot be spoofed. Nobody can trade the token before its creator.
|
||||
- **QianRWAFeeRouter** splits a quote-paired token's accrued trading fees and
|
||||
pays them out as plain ERC20 transfers of the quote. A transfer the recipient
|
||||
cannot take, for example when a stock issuer blocklists the address, accrues
|
||||
@@ -90,11 +93,12 @@ source-verified on https://robinhoodchain.blockscout.com.
|
||||
|
||||
| Contract | Address |
|
||||
| --- | --- |
|
||||
| QianRWAFactory | `0x5A4ecC6fb7Ab62EC69cB0902397A5EB0a3Cb16D3` |
|
||||
| QianRWAFactory (V2, dev buy) | `0x04953F4322BB1b800C438a9426cDacfCD9785860` |
|
||||
| QianRWAFactory V1 | `0x5A4ecC6fb7Ab62EC69cB0902397A5EB0a3Cb16D3` |
|
||||
| QianRWAFeeRouter | `0xc3a89be96b8BDb1123d2Fa40f18000e3FB1A7aAf` |
|
||||
| QianRWARouter | `0x8495A347f71e819dB9e5eC12c1FC64C79DbD8708` |
|
||||
| QianLocker (RWA stack) | `0x38ceAf1eF6528Aa5775820696A7D14e64136a22d` |
|
||||
| GuardHook (RWA stack) | `0x10bbb04ad4ef277daffeecc37a3019dc08dee000` |
|
||||
| GuardHook (RWA stack) | `0x2725462a10d14d863ce9a2ec5d95895d7d65a000` |
|
||||
|
||||
## Layout
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"deployedAt": "2026-07-19T20:51:38.473Z",
|
||||
"deployer": "0x92B92aA9fa5AefC957ab77904f23cf14d7369fA7",
|
||||
"locker": "0x38ceAf1eF6528Aa5775820696A7D14e64136a22d",
|
||||
"rwaFactory": "0x5A4ecC6fb7Ab62EC69cB0902397A5EB0a3Cb16D3",
|
||||
"rwaFactory": "0x04953F4322BB1b800C438a9426cDacfCD9785860",
|
||||
"rwaFeeRouter": "0xc3a89be96b8BDb1123d2Fa40f18000e3FB1A7aAf",
|
||||
"create2Factory": "0xe744510974F2c61dDAC2290238f8CBd375AeF1f2",
|
||||
"guardHook": "0x10bbb04ad4ef277daffeecc37a3019dc08dee000",
|
||||
"guardHookSalt": "0xcd353772db37b52865c269b4cf6643eb600565237ae1dbc6ae0bf1f57b2cfaec",
|
||||
"guardHook": "0x2725462a10d14d863ce9a2ec5d95895d7d65a000",
|
||||
"guardHookSalt": "0x3c5b0ba1f92f0e2c2c4e15a4789e7b6ead500fe74fcfd4e93f43a893d62a1759",
|
||||
"tiersUsd": [
|
||||
1900,
|
||||
3700,
|
||||
@@ -27,5 +27,8 @@
|
||||
"META": "0xc0D6457C16Cc70d6790Dd43521C899C87ce02f35",
|
||||
"AMD": "0x86923f96303D656E4aa86D9d42D1e57ad2023fdC",
|
||||
"USDG": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168"
|
||||
}
|
||||
},
|
||||
"rwaFactoryV1": "0x5A4ecC6fb7Ab62EC69cB0902397A5EB0a3Cb16D3",
|
||||
"guardHookV1": "0x10bbb04ad4ef277daffeecc37a3019dc08dee000",
|
||||
"factoryV2Note": "V2 2026-07-19: same-tx dev buy via router V2 (devBuyPath/devBuyMinOut); V1 factory still authorized in the locker"
|
||||
}
|
||||
@@ -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