HoodMarketsV3
0x9bddc8ddf28f5629c989a36eb5bb6c73cba60df5
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
20,038 bytes
ABI entries
62
23 read · 13 write
License
none
Contract information
- Address
- 0x9bddc8ddf28f5629c989a36eb5bb6c73cba60df5
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0xFA45A3b8d1…923D1b796C
- Creation tx
- 0xf291bfe094…7c000bad8e
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (23)
FRACTION_COUNT() → uint256
FRACTION_VAULT_PERCENTAGE() → uint8
MAX_CREATOR_REWARD() → uint256
MAX_TICK() → int24
MAX_VAULT_PERCENTAGE() → uint256
POOL_FEE() → uint24
TICK_SPACING() → int24
TOKEN_SUPPLY() → uint256
admins(address) → bool
buyerRewardRelay() → address
deploymentInfoForToken(address) → address, uint256, address, address
deprecated() → bool
fractionCollectionForToken(address) → address
fractionDeployer() → address
getTokensDeployedByUser(address) → tuple[]
liquidityLocker() → address
owner() → address
positionManager() → address
swapRouter() → address
tokensDeployedByUsers(address, uint256) → address, uint256, address, address
uniswapV3Factory() → address
vault() → address
weth() → address
Events (9)
BuyerRewardRelayUpdatedBuyerShareIssuedHoodMarketsV3DeployerUpdatedLiquidityLockerUpdatedOwnershipTransferredSetAdminSetDeprecatedTokenCreatedVaultUpdated
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "Deprecated",
"type": "error"
},
{
"inputs": [],
"name": "InvalidBuyerRewardShareCount",
"type": "error"
},
{
"inputs": [],
"name": "InvalidCreatorInfo",
"type": "error"
},
{
"inputs": [],
"name": "InvalidCreatorReward",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInterfaceInfo",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTick",
"type": "error"
},
{
"inputs": [],
"name": "InvalidVaultConfiguration",
"type": "error"
},
{
"inputs": [],
"name": "LegacyVaultDisabled",
"type": "error"
},
{
"inputs": [],
"name": "NotFound",
"type": "error"
},
{
"inputs": [],
"name": "OnlyNonOriginatingChains",
"type": "error"
},
{
"inputs": [],
"name": "OnlyOriginatingChain",
"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": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "ZeroTeamRewardRecipient",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldRelay",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newRelay",
"type": "address"
}
],
"name": "BuyerRewardRelayUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "buyer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "fractionCollection",
"type": "address"
}
],
"name": "BuyerShareIssued",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldHoodMarketsV3Deployer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newHoodMarketsV3Deployer",
"type": "address"
}
],
"name": "HoodMarketsV3DeployerUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldLocker",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newLocker",
"type": "address"
}
],
"name": "LiquidityLockerUpdated",
"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": false,
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "isAdmin",
"type": "bool"
}
],
"name": "SetAdmin",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "deprecated",
"type": "bool"
}
],
"name": "SetDeprecated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creatorAdmin",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "interfaceAdmin",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "creatorRewardRecipient",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "interfaceRewardRecipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"indexed": false,
"internalType": "int24",
"name": "startingTickIfToken0IsNewToken",
"type": "int24"
},
{
"indexed": false,
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountTokensBought",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "vaultDuration",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint8",
"name": "vaultPercentage",
"type": "uint8"
},
{
"indexed": false,
"internalType": "address",
"name": "fractionCollection",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fractionVaultAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "msgSender",
"type": "address"
}
],
"name": "TokenCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldVault",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newVault",
"type": "address"
}
],
"name": "VaultUpdated",
"type": "event"
},
{
"inputs": [],
"name": "FRACTION_COUNT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "FRACTION_VAULT_PERCENTAGE",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_CREATOR_REWARD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_TICK",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_VAULT_PERCENTAGE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "POOL_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TOKEN_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "admins",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "buyerRewardRelay",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "claimRewards",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "string",
"name": "image",
"type": "string"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "string",
"name": "context",
"type": "string"
},
{
"internalType": "uint256",
"name": "originatingChainId",
"type": "uint256"
}
],
"internalType": "struct IHoodMarketsV3.TokenConfig",
"name": "tokenConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint8",
"name": "vaultPercentage",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "vaultDuration",
"type": "uint256"
}
],
"internalType": "struct IHoodMarketsV3.VaultConfig",
"name": "vaultConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "pairedToken",
"type": "address"
},
{
"internalType": "int24",
"name": "tickIfToken0IsNewToken",
"type": "int24"
}
],
"internalType": "struct IHoodMarketsV3.PoolConfig",
"name": "poolConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint24",
"name": "pairedTokenPoolFee",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "pairedTokenSwapAmountOutMinimum",
"type": "uint256"
}
],
"internalType": "struct IHoodMarketsV3.InitialBuyConfig",
"name": "initialBuyConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256",
"name": "creatorReward",
"type": "uint256"
},
{
"internalType": "address",
"name": "creatorAdmin",
"type": "address"
},
{
"internalType": "address",
"name": "creatorRewardRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "interfaceAdmin",
"type": "address"
},
{
"internalType": "address",
"name": "interfaceRewardRecipient",
"type": "address"
}
],
"internalType": "struct IHoodMarketsV3.RewardsConfig",
"name": "rewardsConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint16",
"name": "buyerRewardShareCount",
"type": "uint16"
}
],
"internalType": "struct IHoodMarketsV3.FractionConfig",
"name": "fractionConfig",
"type": "tuple"
}
],
"internalType": "struct IHoodMarketsV3.DeploymentConfig",
"name": "deploymentConfig",
"type": "tuple"
}
],
"name": "deployToken",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "string",
"name": "image",
"type": "string"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "string",
"name": "context",
"type": "string"
},
{
"internalType": "uint256",
"name": "originatingChainId",
"type": "uint256"
}
],
"internalType": "struct IHoodMarketsV3.TokenConfig",
"name": "tokenConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint8",
"name": "vaultPercentage",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "vaultDuration",
"type": "uint256"
}
],
"internalType": "struct IHoodMarketsV3.VaultConfig",
"name": "vaultConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "pairedToken",
"type": "address"
},
{
"internalType": "int24",
"name": "tickIfToken0IsNewToken",
"type": "int24"
}
],
"internalType": "struct IHoodMarketsV3.PoolConfig",
"name": "poolConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint24",
"name": "pairedTokenPoolFee",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "pairedTokenSwapAmountOutMinimum",
"type": "uint256"
}
],
"internalType": "struct IHoodMarketsV3.InitialBuyConfig",
"name": "initialBuyConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256",
"name": "creatorReward",
"type": "uint256"
},
{
"internalType": "address",
"name": "creatorAdmin",
"type": "address"
},
{
"internalType": "address",
"name": "creatorRewardRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "interfaceAdmin",
"type": "address"
},
{
"internalType": "address",
"name": "interfaceRewardRecipient",
"type": "address"
}
],
"internalType": "struct IHoodMarketsV3.RewardsConfig",
"name": "rewardsConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint16",
"name": "buyerRewardShareCount",
"type": "uint16"
}
],
"internalType": "struct IHoodMarketsV3.FractionConfig",
"name": "fractionConfig",
"type": "tuple"
}
],
"internalType": "struct IHoodMarketsV3.DeploymentConfig",
"name": "deploymentConfig",
"type": "tuple"
},
{
"internalType": "address",
"name": "teamRewardRecipient",
"type": "address"
}
],
"name": "deployTokenWithCustomTeamRewardRecipient",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "string",
"name": "image",
"type": "string"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "string",
"name": "context",
"type": "string"
},
{
"internalType": "uint256",
"name": "originatingChainId",
"type": "uint256"
}
],
"internalType": "struct IHoodMarketsV3.TokenConfig",
"name": "tokenConfig",
"type": "tuple"
},
{
"internalType": "address",
"name": "tokenAdmin",
"type": "address"
}
],
"name": "deployTokenZeroSupply",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "deploymentInfoForToken",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "address",
"name": "locker",
"type": "address"
},
{
"internalType": "address",
"name": "fractionCollection",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "deprecated",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "fractionCollectionForToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "fractionDeployer",
"outputs": [
{
"internalType": "contract HoodMarketsV3FractionDeployer",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getTokensDeployedByUser",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "address",
"name": "locker",
"type": "address"
},
{
"internalType": "address",
"name": "fractionCollection",
"type": "address"
}
],
"internalType": "struct IHoodMarketsV3.DeploymentInfo[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "uniswapV3Factory_",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "address",
"name": "swapRouter_",
"type": "address"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
},
{
"internalType": "address",
"name": "liquidityLocker_",
"type": "address"
},
{
"internalType": "address",
"name": "vault_",
"type": "address"
},
{
"internalType": "address",
"name": "fractionDeployer_",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "buyer",
"type": "address"
}
],
"name": "issueBuyerShare",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "liquidityLocker",
"outputs": [
{
"internalType": "contract IHoodMarketsV3LpLocker",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "positionManager",
"outputs": [
{
"internalType": "contract INonfungiblePositionManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"internalType": "bool",
"name": "isAdmin",
"type": "bool"
}
],
"name": "setAdmin",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relay",
"type": "address"
}
],
"name": "setBuyerRewardRelay",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "deprecated_",
"type": "bool"
}
],
"name": "setDeprecated",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "swapRouter",
"outputs": [
{
"internalType": "contract ISwapRouter",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "tokensDeployedByUsers",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "address",
"name": "locker",
"type": "address"
},
{
"internalType": "address",
"name": "fractionCollection",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "uniswapV3Factory",
"outputs": [
{
"internalType": "contract IUniswapV3Factory",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newLocker",
"type": "address"
}
],
"name": "updateLiquidityLocker",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newVault",
"type": "address"
}
],
"name": "updateVault",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "vault",
"outputs": [
{
"internalType": "contract IHoodMarketsV3Vault",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {HoodMarketsAsciiBanner} from "../HoodMarketsAsciiBanner.sol";
import {HoodMarketsV3Deployer} from "./HoodMarketsV3Deployer.sol";
import {HoodMarketsV3FractionDeployer} from "./HoodMarketsV3FractionDeployer.sol";
import {IHoodMarketsV3} from "./interfaces/IHoodMarketsV3.sol";
import {IHoodMarketsV3TokenFraction} from "./interfaces/IHoodMarketsV3TokenFraction.sol";
import {IHoodMarketsV3Vault} from "./interfaces/IHoodMarketsV3Vault.sol";
import {IHoodMarketsV3LpLocker} from "./interfaces/IHoodMarketsV3LpLocker.sol";
import {
INonfungiblePositionManager,
ISwapRouter,
IUniswapV3Factory,
IUniswapV3Pool
} from "./interfaces/uniswapv3.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol";
/// @notice hood.markets V3 token launcher (Uniswap V3)
contract HoodMarketsV3 is Ownable, ReentrancyGuard, IHoodMarketsV3 {
using TickMath for int24;
string constant version = "0.6.0";
IUniswapV3Factory public uniswapV3Factory;
INonfungiblePositionManager public positionManager;
ISwapRouter public swapRouter;
address public weth;
uint256 public constant TOKEN_SUPPLY = 100_000_000_000_000_000_000_000_000_000; // 100b with 18 decimals
uint24 public constant POOL_FEE = 10_000; // 1%
int24 public constant TICK_SPACING = 200;
int24 public constant MAX_TICK = 887_200;
IHoodMarketsV3LpLocker public liquidityLocker;
IHoodMarketsV3Vault public vault;
HoodMarketsV3FractionDeployer public fractionDeployer;
uint256 public constant MAX_CREATOR_REWARD = 95;
uint256 public constant MAX_VAULT_PERCENTAGE = 30;
uint256 public constant FRACTION_COUNT = 1000;
uint8 public constant FRACTION_VAULT_PERCENTAGE = 10;
bool public deprecated;
mapping(address => bool) public admins;
mapping(address => address) public fractionCollectionForToken;
address public buyerRewardRelay;
mapping(address => DeploymentInfo[]) public tokensDeployedByUsers;
mapping(address => DeploymentInfo) public deploymentInfoForToken;
constructor(address owner_) Ownable(owner_) {
// only non-originating tokens deployments are enabled
// before initialization
deprecated = true;
}
function initialize(
address uniswapV3Factory_,
address positionManager_,
address swapRouter_,
address weth_,
address liquidityLocker_,
address vault_,
address fractionDeployer_
) external onlyOwner {
// uniswap configurations
uniswapV3Factory = IUniswapV3Factory(uniswapV3Factory_);
positionManager = INonfungiblePositionManager(positionManager_);
swapRouter = ISwapRouter(swapRouter_);
// weth configurations
weth = weth_;
IERC20(weth).approve(address(positionManager), type(uint256).max);
// liquidity locker configurations
liquidityLocker = IHoodMarketsV3LpLocker(liquidityLocker_);
// vault configurations
vault = IHoodMarketsV3Vault(vault_);
fractionDeployer = HoodMarketsV3FractionDeployer(fractionDeployer_);
// enable deployments
deprecated = false;
}
function getTokensDeployedByUser(address user)
external
view
returns (DeploymentInfo[] memory)
{
return tokensDeployedByUsers[user];
}
function updateLiquidityLocker(address newLocker) external onlyOwner {
address oldLocker = address(liquidityLocker);
liquidityLocker = IHoodMarketsV3LpLocker(newLocker);
emit LiquidityLockerUpdated(oldLocker, newLocker);
}
function updateVault(address newVault) external onlyOwner {
address oldVault = address(vault);
vault = IHoodMarketsV3Vault(newVault);
emit VaultUpdated(oldVault, newVault);
}
function setDeprecated(bool deprecated_) external onlyOwner {
deprecated = deprecated_;
emit SetDeprecated(deprecated_);
}
function setAdmin(address admin, bool isAdmin) external onlyOwner {
admins[admin] = isAdmin;
emit SetAdmin(admin, isAdmin);
}
function setBuyerRewardRelay(address relay) external onlyOwner {
address oldRelay = buyerRewardRelay;
buyerRewardRelay = relay;
emit BuyerRewardRelayUpdated(oldRelay, relay);
}
/// @notice Issue one buyer-reward share to a first-time pool buyer (hood.markets relay or fee admin).
function issueBuyerShare(address token, address buyer) external {
if (msg.sender != buyerRewardRelay && !admins[msg.sender] && msg.sender != owner()) {
revert Unauthorized();
}
address fractionCollection = fractionCollectionForToken[token];
if (fractionCollection == address(0)) revert NotFound();
IHoodMarketsV3TokenFraction(fractionCollection).issueBuyerShare(buyer);
emit BuyerShareIssued(token, buyer, fractionCollection);
}
function claimRewards(address token) external {
DeploymentInfo memory deploymentInfo = deploymentInfoForToken[token];
if (deploymentInfo.token == address(0)) revert NotFound();
IHoodMarketsV3LpLocker(deploymentInfo.locker).collectRewards(deploymentInfo.positionId);
}
// deploy a token on a non-originating chain with 0 supply,
// this can be used to bridge tokens between superchains.
function deployTokenZeroSupply(TokenConfig memory tokenConfig, address tokenAdmin)
external
returns (address tokenAddress)
{
if (block.chainid == tokenConfig.originatingChainId) revert OnlyNonOriginatingChains();
tokenAddress = HoodMarketsV3Deployer.deployToken(tokenConfig, tokenAdmin, TOKEN_SUPPLY);
}
// Deploy a token with a custom protocol reward recipient,
// only protocol admins can call this function
function deployTokenWithCustomTeamRewardRecipient(
DeploymentConfig memory deploymentConfig,
address teamRewardRecipient
) external payable returns (address tokenAddress, uint256 positionId) {
if (!admins[msg.sender]) revert Unauthorized();
if (teamRewardRecipient == address(0)) revert ZeroTeamRewardRecipient();
(tokenAddress, positionId) = deployToken(deploymentConfig);
// set the override protocol reward recipient for the token
liquidityLocker.setOverrideTeamRewardRecipientForToken(positionId, teamRewardRecipient);
}
// Deploy a token and pool with the option to vault the token and buy an initial amount
function deployToken(DeploymentConfig memory deploymentConfig)
public
payable
nonReentrant
returns (address tokenAddress, uint256 positionId)
{
if (deprecated) revert Deprecated();
if (block.chainid != deploymentConfig.tokenConfig.originatingChainId) {
revert OnlyOriginatingChain();
}
// deploy the token
tokenAddress = HoodMarketsV3Deployer.deployToken(
deploymentConfig.tokenConfig, deploymentConfig.rewardsConfig.creatorAdmin, TOKEN_SUPPLY
);
if (
deploymentConfig.vaultConfig.vaultDuration > 0
|| deploymentConfig.vaultConfig.vaultPercentage > 0
) {
revert LegacyVaultDisabled();
}
if (deploymentConfig.fractionConfig.buyerRewardShareCount > FRACTION_COUNT) {
revert InvalidBuyerRewardShareCount();
}
uint256 fractionVaultAmount = (TOKEN_SUPPLY * FRACTION_VAULT_PERCENTAGE) / 100;
uint256 poolSupply = TOKEN_SUPPLY - fractionVaultAmount;
address fractionCollection = address(0);
if (address(fractionDeployer) != address(0)) {
IERC20(tokenAddress).approve(address(fractionDeployer), fractionVaultAmount);
fractionCollection = fractionDeployer.deployFraction(
tokenAddress,
deploymentConfig.rewardsConfig.creatorAdmin,
fractionVaultAmount,
deploymentConfig.fractionConfig.buyerRewardShareCount
);
fractionCollectionForToken[tokenAddress] = fractionCollection;
}
// configure the pool
IERC20(tokenAddress).approve(address(positionManager), poolSupply);
address poolAddress;
(positionId, poolAddress) = _configurePool(
tokenAddress,
deploymentConfig.poolConfig.pairedToken,
deploymentConfig.poolConfig.tickIfToken0IsNewToken,
poolSupply
);
RewardsConfig memory rewardsConfig = deploymentConfig.rewardsConfig;
if (fractionCollection != address(0)) {
(,, address token0, address token1,,,,,,,,) =
positionManager.positions(positionId);
IHoodMarketsV3TokenFraction(fractionCollection).configureFeeRewards(
positionId, token0, token1, poolAddress
);
// Route creator fees to share holders unless a custom recipient was chosen
// (e.g. platform-only rate limit uses the platform treasury wallet).
if (rewardsConfig.creatorRewardRecipient == rewardsConfig.creatorAdmin) {
rewardsConfig.creatorRewardRecipient = fractionCollection;
}
}
// lock the lp tokens
_lockLPTokens(positionId, rewardsConfig);
// perform initial buy if eth was sent, use the creator admin as the recipient
uint256 amountTokensBought = msg.value > 0
? _initialBuy(
tokenAddress,
deploymentConfig.poolConfig.pairedToken,
deploymentConfig.initialBuyConfig.pairedTokenPoolFee,
deploymentConfig.initialBuyConfig.pairedTokenSwapAmountOutMinimum,
deploymentConfig.rewardsConfig.creatorAdmin
)
: 0;
// add the deployment info to the deployment info for token
DeploymentInfo memory deploymentInfo = DeploymentInfo({
token: tokenAddress,
positionId: positionId,
locker: address(liquidityLocker),
fractionCollection: fractionCollection
});
deploymentInfoForToken[tokenAddress] = deploymentInfo;
tokensDeployedByUsers[deploymentConfig.rewardsConfig.creatorAdmin].push(deploymentInfo);
emit TokenCreated({
tokenAddress: tokenAddress,
positionId: positionId,
creatorAdmin: deploymentConfig.rewardsConfig.creatorAdmin,
creatorRewardRecipient: deploymentConfig.rewardsConfig.creatorRewardRecipient,
interfaceAdmin: deploymentConfig.rewardsConfig.interfaceAdmin,
interfaceRewardRecipient: deploymentConfig.rewardsConfig.interfaceRewardRecipient,
name: deploymentConfig.tokenConfig.name,
symbol: deploymentConfig.tokenConfig.symbol,
startingTickIfToken0IsNewToken: deploymentConfig.poolConfig.tickIfToken0IsNewToken,
metadata: deploymentConfig.tokenConfig.metadata,
amountTokensBought: amountTokensBought,
vaultDuration: 0,
vaultPercentage: FRACTION_VAULT_PERCENTAGE,
fractionCollection: fractionCollection,
fractionVaultAmount: fractionVaultAmount,
msgSender: msg.sender
});
}
// Lock the lp tokens and add the user reward recipient to the liquidity locker
function _lockLPTokens(uint256 positionId, RewardsConfig memory rewardsConfig) internal {
// ensure the creator reward is not greater than the max creator reward or zero
if (rewardsConfig.creatorReward > MAX_CREATOR_REWARD || rewardsConfig.creatorReward == 0) {
revert InvalidCreatorReward();
}
// ensure that the creator admin is set
if (rewardsConfig.creatorAdmin == address(0)) {
revert InvalidCreatorInfo();
}
// if the creator reward is not max, ensure the interface admin is set
if (
rewardsConfig.creatorReward < MAX_CREATOR_REWARD
&& rewardsConfig.interfaceAdmin == address(0)
) {
revert InvalidInterfaceInfo();
}
// transfer the lp position to the liquidity locker
positionManager.safeTransferFrom(address(this), address(liquidityLocker), positionId);
// add the user reward recipient to the liquidity locker
liquidityLocker.addTokenReward(
IHoodMarketsV3LpLocker.TokenRewardInfo({
lpTokenId: positionId,
creatorReward: rewardsConfig.creatorReward,
creator: IHoodMarketsV3LpLocker.RewardRecipient({
admin: rewardsConfig.creatorAdmin,
recipient: rewardsConfig.creatorRewardRecipient
}),
interfacer: IHoodMarketsV3LpLocker.RewardRecipient({
admin: rewardsConfig.interfaceAdmin,
recipient: rewardsConfig.interfaceRewardRecipient
})
})
);
}
function _configurePool(
address newToken,
address pairedToken,
int24 tickIfToken0IsNewToken,
uint256 supplyPerPool
) internal returns (uint256 positionId, address poolAddress) {
// check tick spacing
if (tickIfToken0IsNewToken % TICK_SPACING != 0) {
revert InvalidTick();
}
// Pool ordering
bool token0IsNewToken = newToken < pairedToken;
// flip tick if token0 is not the new token
int24 tick = token0IsNewToken ? tickIfToken0IsNewToken : -tickIfToken0IsNewToken;
uint160 sqrtPriceX96 = tick.getSqrtRatioAtTick();
// Create pool
poolAddress = uniswapV3Factory.createPool(newToken, pairedToken, POOL_FEE);
// Initialize pool
IUniswapV3Pool(poolAddress).initialize(sqrtPriceX96);
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager
.MintParams(
token0IsNewToken ? newToken : pairedToken,
token0IsNewToken ? pairedToken : newToken,
POOL_FEE,
token0IsNewToken ? tick : -MAX_TICK,
token0IsNewToken ? MAX_TICK : tick,
token0IsNewToken ? supplyPerPool : 0,
token0IsNewToken ? 0 : supplyPerPool,
0,
0,
address(this),
block.timestamp
);
(positionId,,,) = positionManager.mint(params);
}
function _initialBuy(
address token,
address pairedToken,
uint24 pairedTokenPoolFee,
uint256 pairedTokenSwapAmountOutMinimum,
address recipient
) internal returns (uint256 amountTokensBought) {
// amount in is the amount of eth sent to perform the intial buy with
uint256 pairedTokenAmountIn = msg.value;
// if the paired token is not weth, we need to swap from weth to paired token
if (pairedToken != weth) {
// swap from weth to paired token
ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter
.ExactInputSingleParams({
tokenIn: weth, // The token we are exchanging from (ETH wrapped as WETH)
tokenOut: pairedToken, // The token we are exchanging to
fee: pairedTokenPoolFee, // The pool fee
recipient: address(this), // The recipient address
amountIn: msg.value, // The amount of ETH (WETH) to be swapped
amountOutMinimum: pairedTokenSwapAmountOutMinimum, // Minimum amount to receive
sqrtPriceLimitX96: 0 // No price limit
});
// execute the swap to get pair tokens for the initial buy
pairedTokenAmountIn = swapRouter.exactInputSingle{value: msg.value}(swapParams);
// approve the swap router to spend the paired token
IERC20(pairedToken).approve(address(swapRouter), pairedTokenAmountIn);
}
// swap from paired token to token
ISwapRouter.ExactInputSingleParams memory swapParamsToken = ISwapRouter
.ExactInputSingleParams({
tokenIn: pairedToken, // The token we are exchanging from
tokenOut: token, // The token we are exchanging to
fee: POOL_FEE, // The pool fee
recipient: recipient, // The recipient address
amountIn: pairedTokenAmountIn, // The amount of paired token to be swapped
amountOutMinimum: 0, // Minimum amount to receive
sqrtPriceLimitX96: 0 // No price limit
});
// execute the swap
amountTokensBought =
swapRouter.exactInputSingle{value: pairedToken == weth ? msg.value : 0}(swapParamsToken);
}
}
Chain explorer3808msChain node94ms