QuiverFactoryS
0x9b205b212b08d02dd255a4c98404c61548414663
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
22,023 bytes
ABI entries
45
20 read · 8 write
License
none
Contract information
- Address
- 0x9b205b212b08d02dd255a4c98404c61548414663
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x56D9d0E521…E75B5b959b
- Creation tx
- 0x4dc75ab959…7a7c419639
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (20)
INITIAL_MARKET_CAP_USD_8() → uint256
LP_FEE() → uint24
MAX_TAX_BPS() → uint16
TICK_SPACING() → int24
TOTAL_SUPPLY() → uint256
allTokens(uint256) → address
hook() → address
launchesPaused() → bool
listings(address) → address, address, uint16, uint64, bytes32
owner() → address
poolManager() → address
positions(address) → int24, int24, uint128
protocolAdmin() → address
rewardStocksOf(address) → address[]
stockListed(address) → bool
stockPool(address) → tuple
stockUsd8(address) → uint256
tokensByCreator(address) → address[]
totalTokens() → uint256
usdg() → address
Events (6)
LaunchedLaunchesPausedSetOwnershipTransferredPositionUnwoundStockDelistedStockListed
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
},
{
"internalType": "address",
"name": "protocolAdmin_",
"type": "address"
},
{
"internalType": "contract IPoolManager",
"name": "poolManager_",
"type": "address"
},
{
"internalType": "contract QuiverHookS",
"name": "hook_",
"type": "address"
},
{
"internalType": "address",
"name": "usdg_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "BadVanity",
"type": "error"
},
{
"inputs": [],
"name": "DeadPricePool",
"type": "error"
},
{
"inputs": [],
"name": "InvalidParams",
"type": "error"
},
{
"inputs": [],
"name": "LaunchesPaused",
"type": "error"
},
{
"inputs": [],
"name": "NotProtocolAdmin",
"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": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "StockNotListed",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "pairStock",
"type": "address"
},
{
"indexed": false,
"internalType": "uint16",
"name": "taxBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
}
],
"name": "Launched",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "paused",
"type": "bool"
}
],
"name": "LaunchesPausedSet",
"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": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidityRemoved",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "stockAmount",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "PositionUnwound",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "stock",
"type": "address"
}
],
"name": "StockDelisted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "stock",
"type": "address"
}
],
"name": "StockListed",
"type": "event"
},
{
"inputs": [],
"name": "INITIAL_MARKET_CAP_USD_8",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "LP_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_TAX_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TOTAL_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "allTokens",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stock",
"type": "address"
}
],
"name": "delistStock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "hook",
"outputs": [
{
"internalType": "contract QuiverHookS",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "metadataURI",
"type": "string"
},
{
"internalType": "address",
"name": "pairStock",
"type": "address"
},
{
"internalType": "uint16",
"name": "taxBps",
"type": "uint16"
},
{
"internalType": "address[]",
"name": "rewardStocks",
"type": "address[]"
}
],
"internalType": "struct QuiverFactoryS.LaunchParams",
"name": "p",
"type": "tuple"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"name": "launch",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "launchesPaused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stock",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "usdgStockPool",
"type": "tuple"
}
],
"name": "listStock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "listings",
"outputs": [
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "address",
"name": "pairStock",
"type": "address"
},
{
"internalType": "uint16",
"name": "taxBps",
"type": "uint16"
},
{
"internalType": "uint64",
"name": "createdAt",
"type": "uint64"
},
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "positions",
"outputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolAdmin",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "rewardStocksOf",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "paused",
"type": "bool"
}
],
"name": "setLaunchesPaused",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stock",
"type": "address"
}
],
"name": "stockListed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stock",
"type": "address"
}
],
"name": "stockPool",
"outputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stock",
"type": "address"
}
],
"name": "stockUsd8",
"outputs": [
{
"internalType": "uint256",
"name": "price8",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"name": "tokensByCreator",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalTokens",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint16",
"name": "liquidityBps",
"type": "uint16"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "unwindPosition",
"outputs": [
{
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "stockAmount",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "usdg",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
import {QuiverTokenM} from "./QuiverTokenM.sol";
import {QuiverHookS, IQuiverTokenM} from "./QuiverHookS.sol";
/// @title QuiverFactoryS
/// @notice One-transaction launcher for STOCK-PAIRED markets: mints a
/// fixed-supply token, opens a token/stock Uniswap V4 pool bound to
/// QuiverHookS, and seeds the entire supply single-sided at a fixed
/// USD starting market cap — priced live from the paired stock's
/// on-chain USDG pool, so no stale price constants exist anywhere.
///
/// Every listed stock's USDG pricing pool is verified to have
/// IN-RANGE liquidity at listing time (the guard the v1 factory
/// lacked). Creators pick 1 pair stock and a reward basket of up to 5
/// listed stocks for holders.
contract QuiverFactoryS is Ownable, ReentrancyGuard, IUnlockCallback {
using SafeERC20 for IERC20;
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
uint256 public constant TOTAL_SUPPLY = 1_000_000_000 ether;
uint256 public constant INITIAL_MARKET_CAP_USD_8 = 3_000 * 1e8; // $3,000
int24 public constant TICK_SPACING = 60;
uint24 public constant LP_FEE = 0; // the hook tax is the only fee
uint16 public constant MAX_TAX_BPS = 1000;
IPoolManager public immutable poolManager;
QuiverHookS public immutable hook;
address public immutable usdg;
bool public launchesPaused;
struct Listing {
address creator;
address pairStock;
uint16 taxBps;
uint64 createdAt;
bytes32 poolId;
}
/// @notice Listed stocks mapped to their LIQUID USDG pricing pool.
mapping(address stock => PoolKey) internal _stockPool;
mapping(address stock => bool) public stockListed;
mapping(address token => Listing) public listings;
/// @notice Reward basket per launched token (1..5 stocks).
mapping(address token => address[]) internal _rewardStocks;
address[] public allTokens;
mapping(address creator => address[]) internal _tokensByCreator;
struct Position {
int24 tickLower;
int24 tickUpper;
uint128 liquidity;
}
mapping(address token => Position) public positions;
struct LaunchParams {
string name;
string symbol;
string metadataURI;
address pairStock; // the stock the pool trades against (must be listed)
uint16 taxBps;
address[] rewardStocks; // 1..5 listed stocks for the holder basket
}
event Launched(
address indexed token,
address indexed creator,
address indexed pairStock,
uint16 taxBps,
bytes32 poolId
);
event StockListed(address indexed stock);
event StockDelisted(address indexed stock);
event PositionUnwound(
address indexed token,
uint128 liquidityRemoved,
uint256 tokenAmount,
uint256 stockAmount,
address indexed recipient
);
event LaunchesPausedSet(bool paused);
error LaunchesPaused();
error InvalidParams();
error StockNotListed();
error DeadPricePool();
error BadVanity();
error NotProtocolAdmin();
/// @notice Permanent protocol administrator — the only privilege that
/// survives renounceOwnership(); gates unwindPosition only.
address public immutable protocolAdmin;
modifier onlyProtocolAdmin() {
if (msg.sender != protocolAdmin) revert NotProtocolAdmin();
_;
}
constructor(
address owner_,
address protocolAdmin_,
IPoolManager poolManager_,
QuiverHookS hook_,
address usdg_
) Ownable(owner_) {
require(usdg_ != address(0), "usdg=0");
require(protocolAdmin_ != address(0), "admin=0");
protocolAdmin = protocolAdmin_;
poolManager = poolManager_;
hook = hook_;
usdg = usdg_;
}
// ---------------------------------------------------------------------
// Admin
// ---------------------------------------------------------------------
/// @notice List a stock with its liquid USDG pool. Reverts unless that pool
/// has a live price AND in-range liquidity right now.
function listStock(address stock, PoolKey calldata usdgStockPool) external onlyOwner {
require(stock != address(0), "stock=0");
bool stockInKey = Currency.unwrap(usdgStockPool.currency0) == stock
|| Currency.unwrap(usdgStockPool.currency1) == stock;
bool usdgInKey = Currency.unwrap(usdgStockPool.currency0) == usdg
|| Currency.unwrap(usdgStockPool.currency1) == usdg;
if (!stockInKey || !usdgInKey) revert InvalidParams();
PoolId pid = usdgStockPool.toId();
(uint160 sqrtP, , , ) = poolManager.getSlot0(pid);
if (sqrtP == 0 || poolManager.getLiquidity(pid) == 0) revert DeadPricePool();
_stockPool[stock] = usdgStockPool;
stockListed[stock] = true;
emit StockListed(stock);
}
function delistStock(address stock) external onlyOwner {
stockListed[stock] = false;
emit StockDelisted(stock);
}
function stockPool(address stock) external view returns (PoolKey memory) {
return _stockPool[stock];
}
function setLaunchesPaused(bool paused) external onlyOwner {
launchesPaused = paused;
emit LaunchesPausedSet(paused);
}
// ---------------------------------------------------------------------
// Launch
// ---------------------------------------------------------------------
/// @param salt CREATE2 salt mined off-chain so the token address ends 0x4663.
function launch(LaunchParams calldata p, bytes32 salt)
external
nonReentrant
returns (address token, bytes32 poolId)
{
if (launchesPaused) revert LaunchesPaused();
if (bytes(p.name).length == 0 || bytes(p.symbol).length == 0) revert InvalidParams();
if (p.taxBps > MAX_TAX_BPS) revert InvalidParams();
if (!stockListed[p.pairStock]) revert StockNotListed();
uint256 n = p.rewardStocks.length;
if (n == 0 || n > 5) revert InvalidParams();
for (uint256 i; i < n; ++i) {
if (!stockListed[p.rewardStocks[i]]) revert StockNotListed();
}
// 1. Mint via CREATE2 for the 4663 vanity; factory holds the supply.
QuiverTokenM qt = new QuiverTokenM{salt: salt}(
p.name,
p.symbol,
p.metadataURI,
TOTAL_SUPPLY,
msg.sender,
address(this),
p.taxBps,
p.rewardStocks
);
token = address(qt);
if (uint160(token) & 0xffff != 0x4663) revert BadVanity();
// 2. Pair with the chosen stock.
bool tokenIsCurrency0 = token < p.pairStock;
PoolKey memory key = PoolKey({
currency0: Currency.wrap(tokenIsCurrency0 ? token : p.pairStock),
currency1: Currency.wrap(tokenIsCurrency0 ? p.pairStock : token),
fee: LP_FEE,
tickSpacing: TICK_SPACING,
hooks: IHooks(address(hook))
});
// 3. Exclude pool endpoints from dividends, wire the hook.
{
address[] memory ex = new address[](2);
ex[0] = address(poolManager);
ex[1] = address(this);
qt.initHook(address(hook), ex);
}
// 4. Price at the USD start cap using the pair stock's LIVE on-chain
// USD price, and seed the full supply single-sided.
(uint160 sqrtPriceX96, int24 tickLower, int24 tickUpper) =
_initialPosition(tokenIsCurrency0, p.pairStock);
poolManager.initialize(key, sqrtPriceX96);
uint128 liquidity = tokenIsCurrency0
? LiquidityAmounts.getLiquidityForAmount0(
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
TOTAL_SUPPLY
)
: LiquidityAmounts.getLiquidityForAmount1(
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
TOTAL_SUPPLY
);
positions[token] = Position({tickLower: tickLower, tickUpper: tickUpper, liquidity: liquidity});
poolManager.unlock(abi.encode(uint8(0), abi.encode(key, tickLower, tickUpper, liquidity, tokenIsCurrency0)));
// 5. Register with the hook: pair route + reward basket routes.
{
QuiverHookS.RewardRoute[] memory routes = new QuiverHookS.RewardRoute[](n);
for (uint256 i; i < n; ++i) {
routes[i] = QuiverHookS.RewardRoute({
stock: p.rewardStocks[i],
usdgKey: _stockPool[p.rewardStocks[i]]
});
}
hook.registerPool(
key, token, p.pairStock, msg.sender, p.taxBps, tokenIsCurrency0, _stockPool[p.pairStock], routes
);
}
poolId = PoolId.unwrap(key.toId());
listings[token] = Listing({
creator: msg.sender,
pairStock: p.pairStock,
taxBps: p.taxBps,
createdAt: uint64(block.timestamp),
poolId: poolId
});
_rewardStocks[token] = p.rewardStocks;
allTokens.push(token);
_tokensByCreator[msg.sender].push(token);
emit Launched(token, msg.sender, p.pairStock, p.taxBps, poolId);
}
// ---------------------------------------------------------------------
// Liquidity seeding / unwind callback
// ---------------------------------------------------------------------
function unlockCallback(bytes calldata data) external override returns (bytes memory) {
require(msg.sender == address(poolManager), "not pool manager");
(uint8 action, bytes memory payload) = abi.decode(data, (uint8, bytes));
if (action == 0) {
(PoolKey memory key, int24 tickLower, int24 tickUpper, uint128 liquidity, bool tokenIsCurrency0) =
abi.decode(payload, (PoolKey, int24, int24, uint128, bool));
(BalanceDelta delta, ) = poolManager.modifyLiquidity(
key,
ModifyLiquidityParams({
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: int256(uint256(liquidity)),
salt: bytes32(0)
}),
""
);
int128 tokenOwed = tokenIsCurrency0 ? delta.amount0() : delta.amount1();
int128 stockOwed = tokenIsCurrency0 ? delta.amount1() : delta.amount0();
require(stockOwed >= 0, "stock owed");
if (tokenOwed < 0) {
Currency tokenCurrency = tokenIsCurrency0 ? key.currency0 : key.currency1;
uint256 amt = uint256(uint128(-tokenOwed));
poolManager.sync(tokenCurrency);
IERC20(Currency.unwrap(tokenCurrency)).safeTransfer(address(poolManager), amt);
poolManager.settle();
}
return "";
}
(
PoolKey memory key2,
int24 lo,
int24 hi,
uint128 removed,
address recipient,
bool tokenIsC0
) = abi.decode(payload, (PoolKey, int24, int24, uint128, address, bool));
(BalanceDelta d2, ) = poolManager.modifyLiquidity(
key2,
ModifyLiquidityParams({tickLower: lo, tickUpper: hi, liquidityDelta: -int256(uint256(removed)), salt: bytes32(0)}),
""
);
uint256 amt0 = _takePositive(key2.currency0, d2.amount0(), recipient);
uint256 amt1 = _takePositive(key2.currency1, d2.amount1(), recipient);
(uint256 tokenAmount, uint256 stockAmount) = tokenIsC0 ? (amt0, amt1) : (amt1, amt0);
return abi.encode(tokenAmount, stockAmount);
}
function _takePositive(Currency currency, int128 amount, address to) internal returns (uint256 value) {
if (amount <= 0) return 0;
value = uint256(uint128(amount));
poolManager.take(currency, to, value);
}
/// @notice Remove part of a token's factory-held liquidity (protocolAdmin
/// only; disclosed recovery lever, survives renounce).
function unwindPosition(address token, uint16 liquidityBps, address recipient)
external
onlyProtocolAdmin
nonReentrant
returns (uint256 tokenAmount, uint256 stockAmount)
{
if (liquidityBps == 0 || liquidityBps > 10_000) revert InvalidParams();
if (recipient == address(0)) revert InvalidParams();
Position storage pos = positions[token];
uint128 held = pos.liquidity;
if (held == 0) revert InvalidParams();
uint128 removed = uint128((uint256(held) * liquidityBps) / 10_000);
if (removed == 0) revert InvalidParams();
pos.liquidity = held - removed;
address pairStock = listings[token].pairStock;
bool tokenIsCurrency0 = token < pairStock;
PoolKey memory key = PoolKey({
currency0: Currency.wrap(tokenIsCurrency0 ? token : pairStock),
currency1: Currency.wrap(tokenIsCurrency0 ? pairStock : token),
fee: LP_FEE,
tickSpacing: TICK_SPACING,
hooks: IHooks(address(hook))
});
bytes memory res = poolManager.unlock(
abi.encode(uint8(1), abi.encode(key, pos.tickLower, pos.tickUpper, removed, recipient, tokenIsCurrency0))
);
(tokenAmount, stockAmount) = abi.decode(res, (uint256, uint256));
emit PositionUnwound(token, removed, tokenAmount, stockAmount, recipient);
}
// ---------------------------------------------------------------------
// Pricing
// ---------------------------------------------------------------------
/// @notice Live USD price (8 decimals) of a listed stock, read from its
/// USDG pool. Public so the frontend/keeper can reuse it.
function stockUsd8(address stock) public view returns (uint256 price8) {
PoolKey memory k = _stockPool[stock];
(uint160 sqrtP, , , ) = poolManager.getSlot0(k.toId());
if (sqrtP == 0) revert DeadPricePool();
// ratio = (sqrtP/2^96)^2 = raw currency1 per raw currency0.
uint256 r1 = Math.mulDiv(uint256(sqrtP), uint256(sqrtP), 1 << 96);
bool stockIs0 = Currency.unwrap(k.currency0) == stock;
if (stockIs0) {
// USDG (6dp) per stock (18dp): usd8 = ratio * 1e12 * 1e8 = ratio * 1e20.
price8 = Math.mulDiv(r1, 1e20, 1 << 96);
} else {
// ratio is stock-raw per USDG-raw; usd8 = 1e20 / ratio.
uint256 denom = Math.mulDiv(r1, 1e20, 1 << 96); // ratio * 1e20
if (denom == 0) revert DeadPricePool();
price8 = Math.mulDiv(1e20, 1e20, denom);
}
if (price8 == 0) revert DeadPricePool();
}
/// @dev Initial pool price (in pair-stock units) hitting the USD start cap,
/// plus the token-only seeding range adjacent to spot.
function _initialPosition(bool tokenIsCurrency0, address pairStock)
internal
view
returns (uint160 sqrtPriceX96, int24 tickLower, int24 tickUpper)
{
uint256 usd8 = stockUsd8(pairStock);
// Market cap in pair-stock raw units (18dp), then per-token price (18dp).
uint256 mcapStock = Math.mulDiv(INITIAL_MARKET_CAP_USD_8, 1e18, usd8);
uint256 priceStock = Math.mulDiv(mcapStock, 1e18, TOTAL_SUPPLY);
if (priceStock == 0) revert InvalidParams();
uint160 target = tokenIsCurrency0
? uint160(Math.sqrt(Math.mulDiv(priceStock, 1 << 192, 1e18)))
: uint160(Math.sqrt(Math.mulDiv(1e18, 1 << 192, priceStock)));
int24 tick = TickMath.getTickAtSqrtPrice(target);
int24 aligned = (tick / TICK_SPACING) * TICK_SPACING;
if (tick < 0 && tick % TICK_SPACING != 0) aligned -= TICK_SPACING;
int24 minTick = (TickMath.MIN_TICK / TICK_SPACING) * TICK_SPACING;
int24 maxTick = (TickMath.MAX_TICK / TICK_SPACING) * TICK_SPACING;
if (tokenIsCurrency0) {
sqrtPriceX96 = TickMath.getSqrtPriceAtTick(aligned);
tickLower = aligned + TICK_SPACING;
tickUpper = maxTick;
} else {
sqrtPriceX96 = TickMath.getSqrtPriceAtTick(aligned + TICK_SPACING);
tickLower = minTick;
tickUpper = aligned + TICK_SPACING;
}
}
// ---------------------------------------------------------------------
// Views
// ---------------------------------------------------------------------
function totalTokens() external view returns (uint256) {
return allTokens.length;
}
function tokensByCreator(address creator) external view returns (address[] memory) {
return _tokensByCreator[creator];
}
function rewardStocksOf(address token) external view returns (address[] memory) {
return _rewardStocks[token];
}
}
Chain explorer796msChain node99ms