BangTokenDeployer
0x2c0e1145234a69749fee778b96c76d5f4d2f1325
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
3,612 bytes
ABI entries
3
1 read · 2 write
License
none
Contract information
- Address
- 0x2c0e1145234a69749fee778b96c76d5f4d2f1325
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x4c4a0dA6e9…AA30BA151c
- Creation tx
- 0x88b2500717…0f592b2a5c
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (1)
factory() → address
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_factory",
"type": "address"
}
],
"name": "bindFactory",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
},
{
"internalType": "address",
"name": "mintTo",
"type": "address"
}
],
"name": "deploy",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {Currency} from "v4-core/src/types/Currency.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {ModifyLiquidityParams} from "v4-core/src/types/PoolOperation.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";
import {BangToken} from "./BangToken.sol";
import {BangDividendVault} from "./BangDividendVault.sol";
interface IBangHookRegister {
function registerPool(PoolKey calldata key, address vault, uint16 creatorRateBps) external;
}
interface IVaultDeployer {
function deploy(
address token, address hook, address universalRouter, address weth, address usdg,
address[] calldata rewardTokens, uint16[] calldata weightsBps, bytes[] calldata routes
) external returns (address);
}
interface ITokenDeployer {
function deploy(string calldata name, string calldata symbol, address mintTo) external returns (address);
}
/**
* ================================================================
* BangFactory — one transaction, one ownerless dividend machine
* ================================================================
* Launch flow, atomic:
* 1. deploy the plain ERC-20 (full supply to this factory)
* 2. deploy the dividend vault with the creator's stock basket
* 3. wire token -> vault (one-shot, never again)
* 4. register the pool config with the shared BangHook
* 5. initialize the V4 pool at the creator's chosen price
* -> starting market cap is set by PRICE, not by capital.
* This is why a $2K launch costs cents instead of 1 ETH.
* 6. add ALL supply as single-sided liquidity in a range below spot
* 7. burn any remainder
*
* The LP position is held by this factory as a raw PoolManager position
* and NO function exists to remove it. That is a stronger lock than
* minting an NFT and burning it — there is no token to lose, transfer,
* or mis-send, and no code path that can decrease liquidity.
*
* Ownerless launches: the token has no owner, the vault has no owner and
* no withdrawal path, and the hook's platform wallet + 1% are immutable.
* ================================================================
*/
contract BangFactory is IUnlockCallback {
using PoolIdLibrary for PoolKey;
address public owner;
IPoolManager public immutable poolManager;
address public immutable hook;
address public immutable universalRouter;
address public immutable weth;
address public immutable usdg;
ITokenDeployer public immutable tokenDeployer;
IVaultDeployer public immutable vaultDeployer;
address payable public feeRecipient;
uint256 public creationFee = 0.0005 ether;
uint256 public constant MAX_CREATION_FEE = 0.05 ether; // creators can't be gouged
uint16 public constant MIN_CREATOR_BPS = 100; // 1%
uint16 public constant MAX_CREATOR_BPS = 500; // 5%
uint24 public constant POOL_FEE = 0; // all economics live in the hook
int24 public constant TICK_SPACING = 60;
struct StockRoute {
bytes route; // pre-built Universal Router calldata: ETH -> USDG -> stock
bool listed;
}
mapping(address => StockRoute) public stocks;
address[] public stockList;
struct Launch {
address token;
address vault;
address creator;
uint16 creatorRateBps;
uint256 createdAt;
}
Launch[] public launches;
mapping(string => address) public tickerToToken;
event StockListed(address indexed stock);
event BangLaunched(
address indexed token, address indexed creator, address vault,
uint16 creatorRateBps, uint160 sqrtPriceX96, int24 tickLower, int24 tickUpper,
address[] basket, uint16[] weights
);
error NotOwner();
error TickerTaken();
error BadRate();
error BadFee();
error NotWhitelisted();
error BadBasket();
error NotPoolManager();
error BadRange();
modifier onlyOwner() {
if (msg.sender != owner) revert NotOwner();
_;
}
constructor(
IPoolManager _poolManager,
address _hook,
address _universalRouter,
address _weth,
address _usdg,
address payable _feeRecipient,
address _tokenDeployer,
address _vaultDeployer
) {
owner = msg.sender;
poolManager = _poolManager;
hook = _hook;
universalRouter = _universalRouter;
weth = _weth;
usdg = _usdg;
feeRecipient = _feeRecipient;
tokenDeployer = ITokenDeployer(_tokenDeployer);
vaultDeployer = IVaultDeployer(_vaultDeployer);
}
receive() external payable {}
// ---------------------------------------------------------------
// THE LAUNCH
// ---------------------------------------------------------------
/**
* @param sqrtPriceX96 starting price -> sets market cap with no capital
* @param tickLower bottom of the single-sided range (below spot)
* @param creatorRateBps dividend tax the creator picks, 100-500 (1-5%).
* Traders pay this PLUS the immutable 1% launchpad fee.
*/
function launch(
string calldata name_,
string calldata symbol_,
uint16 creatorRateBps,
uint160 sqrtPriceX96,
int24 tickLower,
address[] calldata basket,
uint16[] calldata weights
) external payable returns (address tokenAddr) {
if (msg.value < creationFee) revert BadFee();
if (creatorRateBps < MIN_CREATOR_BPS || creatorRateBps > MAX_CREATOR_BPS) revert BadRate();
if (tickerToToken[symbol_] != address(0)) revert TickerTaken();
if (bytes(symbol_).length < 2 || bytes(symbol_).length > 10) revert TickerTaken();
bytes[] memory routes = _validateBasket(basket, weights);
// 1-3. token + vault + wiring
address token = tokenDeployer.deploy(name_, symbol_, address(this));
address vault = vaultDeployer.deploy(
token, hook, universalRouter, weth, usdg, basket, weights, routes
);
BangToken(token).wireVault(vault);
// 4-6. pool: register config, initialize at price, lock liquidity
PoolKey memory key = PoolKey({
currency0: Currency.wrap(address(0)), // native ETH
currency1: Currency.wrap(token),
fee: POOL_FEE,
tickSpacing: TICK_SPACING,
hooks: IHooks(hook)
});
IBangHookRegister(hook).registerPool(key, vault, creatorRateBps);
BangDividendVault(payable(vault)).setPoolKey(key, address(poolManager));
int24 tick = poolManager.initialize(key, sqrtPriceX96);
int24 tickUpper = _alignDown(tick);
if (tickLower >= tickUpper) revert BadRange();
tickLower = _alignDown(tickLower);
poolManager.unlock(abi.encode(key, tickLower, tickUpper, token));
// 7. burn any dust the pool didn't take
uint256 rem = BangToken(token).balanceOf(address(this));
if (rem > 0) {
BangToken(token).transfer(0x000000000000000000000000000000000000dEaD, rem);
}
// fee out
(bool ok,) = feeRecipient.call{value: creationFee}("");
if (!ok) revert BadFee();
tickerToToken[symbol_] = token;
launches.push(Launch(token, vault, msg.sender, creatorRateBps, block.timestamp));
emit BangLaunched(
token, msg.sender, vault, creatorRateBps,
sqrtPriceX96, tickLower, tickUpper, basket, weights
);
return token;
}
/// Adds the entire supply as single-sided token liquidity. The position
/// belongs to this factory forever — no code path can ever remove it.
function unlockCallback(bytes calldata data) external returns (bytes memory) {
if (msg.sender != address(poolManager)) revert NotPoolManager();
(PoolKey memory key, int24 tickLower, int24 tickUpper, address token) =
abi.decode(data, (PoolKey, int24, int24, address));
uint256 amount1 = BangToken(token).balanceOf(address(this));
uint128 liquidity = LiquidityAmounts.getLiquidityForAmount1(
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
amount1
);
(BalanceDelta delta,) = poolManager.modifyLiquidity(
key,
ModifyLiquidityParams({
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: int256(uint256(liquidity)),
salt: bytes32(0)
}),
""
);
// Settle what we owe the pool in BANG tokens.
int128 owed1 = delta.amount1();
if (owed1 < 0) {
uint256 amountOwed = uint256(uint128(-owed1));
poolManager.sync(key.currency1);
BangToken(token).transfer(address(poolManager), amountOwed);
poolManager.settle();
}
return "";
}
function _validateBasket(address[] calldata basket, uint16[] calldata weights)
internal view returns (bytes[] memory routes)
{
uint256 n = basket.length;
if (n == 0 || n > 5 || weights.length != n) revert BadBasket();
routes = new bytes[](n);
uint256 sum;
for (uint256 i = 0; i < n; i++) {
StockRoute storage r = stocks[basket[i]];
if (!r.listed) revert NotWhitelisted();
if (weights[i] == 0) revert BadBasket();
for (uint256 j = 0; j < i; j++) {
if (basket[j] == basket[i]) revert BadBasket();
}
sum += weights[i];
routes[i] = r.route;
}
if (sum != 10_000) revert BadBasket();
}
function _alignDown(int24 tick) internal pure returns (int24) {
int24 compressed = tick / TICK_SPACING;
if (tick < 0 && tick % TICK_SPACING != 0) compressed--;
return compressed * TICK_SPACING;
}
// ---------------------------------------------------------------
// Views
// ---------------------------------------------------------------
function launchCount() external view returns (uint256) { return launches.length; }
function stockCount() external view returns (uint256) { return stockList.length; }
function getLaunches(uint256 start, uint256 max) external view returns (Launch[] memory out) {
uint256 n = launches.length;
if (start >= n) return new Launch[](0);
uint256 end = start + max > n ? n : start + max;
out = new Launch[](end - start);
for (uint256 i = start; i < end; i++) out[i - start] = launches[i];
}
// ---------------------------------------------------------------
// Curated admin — whitelist is ADD-ONLY; cannot touch live launches
// ---------------------------------------------------------------
function listStock(address stock, bytes calldata route) external onlyOwner {
if (stock == address(0) || route.length == 0) revert NotWhitelisted();
if (stocks[stock].listed) revert NotWhitelisted();
stocks[stock] = StockRoute(route, true);
stockList.push(stock);
emit StockListed(stock);
}
function setFees(uint256 _creationFee, address payable _recipient) external onlyOwner {
if (_creationFee > MAX_CREATION_FEE || _recipient == address(0)) revert BadFee();
creationFee = _creationFee;
feeRecipient = _recipient;
}
function transferOwnership(address newOwner) external onlyOwner {
if (newOwner == address(0)) revert NotOwner();
owner = newOwner;
}
}
// ================================================================
// Deployers — keep the factory under the 24KB EIP-170 limit
// ================================================================
abstract contract FactoryBound {
address public factory;
function bindFactory(address _factory) external {
require(factory == address(0) && _factory != address(0), "bound");
factory = _factory;
}
modifier onlyFactory() {
require(msg.sender == factory, "not factory");
_;
}
}
contract BangTokenDeployer is FactoryBound {
function deploy(string calldata name_, string calldata symbol_, address mintTo)
external onlyFactory returns (address)
{
return address(new BangToken(name_, symbol_, mintTo));
}
}
contract BangVaultDeployer is FactoryBound {
function deploy(
address token, address hook, address universalRouter, address weth, address usdg,
address[] calldata rewardTokens, uint16[] calldata weightsBps, bytes[] calldata routes
) external onlyFactory returns (address) {
return address(new BangDividendVault(
token, hook, universalRouter, weth, usdg, rewardTokens, weightsBps, routes
));
}
}
Chain explorer5096msChain node175ms