StonkNFTAMMVault
0xe302733accf4800146e55fc45b46b4e4ffc032d2
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
12,658 bytes
ABI entries
88
41 read · 17 write
License
none
Contract information
- Address
- 0xe302733accf4800146e55fc45b46b4e4ffc032d2
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0xb668382cF4…A809787CDa
- Creation tx
- 0xbbee0cf552…c7b85669e8
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (41)
BOOSTER_SHARE_BPS() → uint16
DEFAULT_ADMIN_ROLE() → bytes32
GOVERNOR_ROLE() → bytes32
INGEST_ROLE() → bytes32
MAX_FEE_BPS() → uint16
PAUSER_ROLE() → bytes32
collection() → address
escrow() → address
ethNotionalPerNFT() → uint256
feeExempt(address) → bool
feeOracleStatus() → bool, bool, uint256, uint256, uint256, uint32, uint256
getNextTokenId(uint256) → uint256
getRoleAdmin(bytes32) → bytes32
hasRole(bytes32, address) → bool
inventory(uint256) → bool
inventoryCount() → uint256
inventoryLength() → uint256
liveEthNotionalPerNFT() → uint256
maxNotionalWei() → uint256
minNotionalWei() → uint256
newestTokenId() → uint256
oldestTokenId() → uint256
paused() → bool
protocolTreasury() → address
quoteRandomBuy() → uint256, uint256, uint256, uint256
quoteSellNFT() → uint256, uint256
quoteSpecificBuy(uint256) → uint256, uint256, bool
randomFeeBps() → uint16
specificFeeBps() → uint16
spikeCapBps() → uint16
stockBooster() → address
supportsInterface(bytes4) → bool
token() → address
tokensPerNFT() → uint256
twapFastWindow() → uint32
twapMidPool() → address
twapMidToken() → address
twapPool() → address
twapQuoteToken() → address
twapWindow() → uint32
twapWiredAt() → uint64
Events (13)
EthNotionalUpdatedFeeExemptUpdatedFeesUpdatedNFTBoughtNFTSoldNotionalBoundsUpdatedPausedRoleAdminChangedRoleGrantedRoleRevokedTwapFastWindowUpdatedTwapOracleUpdatedUnpaused
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "token_",
"type": "address"
},
{
"internalType": "address",
"name": "collection_",
"type": "address"
},
{
"internalType": "address",
"name": "escrow_",
"type": "address"
},
{
"internalType": "address",
"name": "stockBooster_",
"type": "address"
},
{
"internalType": "address",
"name": "protocolTreasury_",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokensPerNFT_",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "ethNotionalPerNFT_",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "randomFeeBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "specificFeeBps_",
"type": "uint16"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AccessControlBadConfirmation",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "bytes32",
"name": "neededRole",
"type": "bytes32"
}
],
"name": "AccessControlUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "EmptyInventory",
"type": "error"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "EthTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientEthFee",
"type": "error"
},
{
"inputs": [],
"name": "InvalidConfig",
"type": "error"
},
{
"inputs": [],
"name": "InvalidRandomFee",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSpecificFee",
"type": "error"
},
{
"inputs": [],
"name": "NotInInventory",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "SlippageExceeded",
"type": "error"
},
{
"inputs": [],
"name": "T",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "vault",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "oldNotionalWei",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newNotionalWei",
"type": "uint256"
}
],
"name": "EthNotionalUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "vault",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "exempt",
"type": "bool"
}
],
"name": "FeeExemptUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "vault",
"type": "address"
},
{
"indexed": false,
"internalType": "uint16",
"name": "randomFeeBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "specificFeeBps",
"type": "uint16"
}
],
"name": "FeesUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "buyer",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethFeePaid",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "boosterShare",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "protocolShare",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "isSpecific",
"type": "bool"
}
],
"name": "NFTBought",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "seller",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensOut",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethFeePaid",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "boosterShare",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "protocolShare",
"type": "uint256"
}
],
"name": "NFTSold",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "vault",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "minWei",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "maxWei",
"type": "uint256"
}
],
"name": "NotionalBoundsUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "previousAdminRole",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "newAdminRole",
"type": "bytes32"
}
],
"name": "RoleAdminChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleGranted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleRevoked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "vault",
"type": "address"
},
{
"indexed": false,
"internalType": "uint32",
"name": "fastWindow",
"type": "uint32"
},
{
"indexed": false,
"internalType": "uint16",
"name": "capBps",
"type": "uint16"
}
],
"name": "TwapFastWindowUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "vault",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "quoteToken",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "midPool",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "midToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint32",
"name": "window",
"type": "uint32"
}
],
"name": "TwapOracleUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [],
"name": "BOOSTER_SHARE_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DEFAULT_ADMIN_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "GOVERNOR_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "INGEST_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_FEE_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PAUSER_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "maxCost",
"type": "uint256"
}
],
"name": "buyRandomNFT",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxCost",
"type": "uint256"
}
],
"name": "buySpecificNFT",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "collection",
"outputs": [
{
"internalType": "contract IERC721",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "escrow",
"outputs": [
{
"internalType": "contract ITokenEscrowReserve",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ethNotionalPerNFT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "feeExempt",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeOracleStatus",
"outputs": [
{
"internalType": "bool",
"name": "wired",
"type": "bool"
},
{
"internalType": "bool",
"name": "live",
"type": "bool"
},
{
"internalType": "uint256",
"name": "notionalWei",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "swapFeeWei",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "snipeFeeWei",
"type": "uint256"
},
{
"internalType": "uint32",
"name": "window",
"type": "uint32"
},
{
"internalType": "uint256",
"name": "readyAt",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getNextTokenId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
}
],
"name": "getRoleAdmin",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "grantRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "hasRole",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ingestNFT",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "inventory",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "inventoryCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "inventoryLength",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "liveEthNotionalPerNFT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxNotionalWei",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "minNotionalWei",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "newestTokenId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "oldestTokenId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "onERC721Received",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolTreasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "quoteRandomBuy",
"outputs": [
{
"internalType": "uint256",
"name": "tokenCost",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "ethFee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "inventorySize",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "nextTokenId",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "quoteSellNFT",
"outputs": [
{
"internalType": "uint256",
"name": "tokenPayout",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "ethFee",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "quoteSpecificBuy",
"outputs": [
{
"internalType": "uint256",
"name": "tokenCost",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "ethFee",
"type": "uint256"
},
{
"internalType": "bool",
"name": "available",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "randomFeeBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "callerConfirmation",
"type": "address"
}
],
"name": "renounceRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "rescueEth",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "revokeRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minPayout",
"type": "uint256"
}
],
"name": "sellNFT",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newNotionalWei",
"type": "uint256"
}
],
"name": "setEthNotionalPerNFT",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "bool",
"name": "exempt",
"type": "bool"
}
],
"name": "setFeeExempt",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "randomFeeBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "specificFeeBps_",
"type": "uint16"
}
],
"name": "setFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "minWei",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxWei",
"type": "uint256"
}
],
"name": "setNotionalBounds",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "fastWindow",
"type": "uint32"
},
{
"internalType": "uint16",
"name": "capBps",
"type": "uint16"
}
],
"name": "setTwapFastWindow",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "address",
"name": "quoteToken",
"type": "address"
},
{
"internalType": "uint32",
"name": "window",
"type": "uint32"
},
{
"internalType": "address",
"name": "midPool",
"type": "address"
},
{
"internalType": "address",
"name": "midToken",
"type": "address"
}
],
"name": "setTwapOracle",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "specificFeeBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "spikeCapBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "stockBooster",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokensPerNFT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "twapFastWindow",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "twapMidPool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "twapMidToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "twapPool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "twapQuoteToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "twapWindow",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "twapWiredAt",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import {ERC721Holder} from "openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {Pausable} from "openzeppelin-contracts/contracts/utils/Pausable.sol";
import {AccessControl} from "openzeppelin-contracts/contracts/access/AccessControl.sol";
import {ITokenEscrowReserve} from "../interfaces/ITokenEscrowReserve.sol";
import {TwapQuote} from "../libs/TwapQuote.sol";
import {
ZeroAddress,
EmptyInventory,
NotInInventory,
SlippageExceeded,
InvalidRandomFee,
InvalidSpecificFee,
InsufficientEthFee,
EthTransferFailed,
InvalidConfig
} from "../libs/Errors.sol";
import {Events} from "../libs/Events.sol";
/// @dev Token surface of the STONKBROKER/WETH v3 pool needed to validate oracle wiring.
interface ITwapPoolTokens {
function token0() external view returns (address);
function token1() external view returns (address);
}
/// @title StonkNFTAMMVault
/// @notice StonkBrokers fork of Anvil's NFTAMMVault with one deliberate economic change:
/// trade fees are charged in native ETH instead of as a cut of the collection token, and the
/// collected ETH is split 70% to the StockBooster (which swaps it into stock tokens and
/// airdrops them to activated brokers' token-bound wallets) and 30% to the protocol treasury.
///
/// All of Anvil's token-denominated fee buckets are zeroed for this market:
/// - no staker fee cut of the collection token (there is no NFTStakingVault in this market),
/// - no protocol treasury bps, no burn bps.
/// Buys and sells therefore move exactly `tokensPerNFT` of $STONKBROKER against the escrow;
/// the only levy anywhere is the ETH fee computed as bps of the ETH NOTIONAL of one NFT.
///
/// The notional is LIVE by default (per launch decision — the fee must track real market
/// value, not a hand-set peg): once the governor wires the STONKBROKER/WETH pool via
/// setTwapOracle(), every fee is bps of the TWAP value of `tokensPerNFT` over `twapWindow`
/// seconds, clamped to governor-set [minNotionalWei, maxNotionalWei] bounds. The stored
/// `ethNotionalPerNFT` then serves as the fail-open fallback: every successful oracle read
/// refreshes it (last-good cache), and any oracle failure — pool migrated, cardinality too
/// low, observe revert — silently falls back to it, so a trade can NEVER fail because of
/// the fee oracle. Before the oracle is wired (seeding), the stored value alone is the fee
/// base, and 0 means fee-free.
///
/// Two trade classes, both fee-split 70/30 booster/protocol:
/// - SWAP (`sellNFT` / `buyRandomNFT`) — charged `randomFeeBps` (launch: 1000 = 10%)
/// - SNIPE (`buySpecificNFT`) — charged `specificFeeBps` (launch: 1500 = 15%,
/// the MAX_FEE_BPS ceiling — picking an exact broker out of inventory costs a premium).
///
/// Inventory management (FIFO doubly-linked list), escrow interaction, pause and ingest paths
/// are inherited from the audited Anvil vault design unchanged.
contract StonkNFTAMMVault is ERC721Holder, ReentrancyGuard, Pausable, AccessControl {
using SafeERC20 for IERC20;
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");
bytes32 public constant INGEST_ROLE = keccak256("INGEST_ROLE");
uint16 public constant MAX_FEE_BPS = 1500; // 15% hard cap, same as Anvil
/// @notice Booster share of every ETH fee; remainder (including rounding dust) goes to protocol.
uint16 public constant BOOSTER_SHARE_BPS = 7000; // 70%
IERC20 public immutable token;
IERC721 public immutable collection;
ITokenEscrowReserve public immutable escrow;
/// @notice Receives 70% of every ETH trade fee (the StockBooster).
address public immutable stockBooster;
/// @notice Receives 30% of every ETH trade fee.
address public immutable protocolTreasury;
uint256 public immutable tokensPerNFT;
/// @notice Fallback/bootstrap ETH notional of one NFT. Primary fee base only until the
/// TWAP oracle is wired; afterwards it is the fail-open cache, auto-refreshed by every
/// successful oracle read during a trade. Launch policy: NEVER zero — admin seeding is
/// handled per-address via `feeExempt`, not by turning fees off globally.
/// Example: notional 0.2 ETH, randomFeeBps 500 => 0.01 ETH fee.
uint256 public ethNotionalPerNFT;
uint16 public randomFeeBps;
uint16 public specificFeeBps;
/// @notice v3-style pool the live notional is read from (0 = oracle off). Either the
/// direct STONKBROKER/WETH pool, or — when `twapMidPool` is set — the STONKBROKER/mid
/// leg of a two-hop route (e.g. STONKBROKER/USDG + USDG/WETH), so the oracle survives a
/// liquidity migration to a non-WETH main pair.
address public twapPool;
/// @notice The token the notional is quoted in (WETH — fees are charged in native ETH).
address public twapQuoteToken;
/// @notice Optional second hop: pool holding `twapMidToken`/`twapQuoteToken` (0 = direct).
address public twapMidPool;
/// @notice Intermediate token of the two-hop route (e.g. USDG). 0 when direct.
address public twapMidToken;
/// @notice TWAP averaging window in seconds, applied to BOTH hops. Longer = more
/// manipulation-resistant, slower to track real moves. Launch target: 1800 (30 min).
uint32 public twapWindow;
/// @notice Optional FAST window (launch target: 300s / 5 min) for spike capture. During
/// a rally the slow TWAP lags and undercharges; when set, the fee base is
/// max(slow, min(fast, slow * spikeCapBps / 1e4))
/// so genuine spikes are captured within ~one fast window instead of one slow window,
/// while manipulation stays bounded: pushing the price DOWN can never cut fees below the
/// slow average (outer max), and pushing it UP inflates fees at most spikeCapBps beyond
/// the slow average (inner min) — at real cost against arbitrage. 0 = disabled.
/// The 5-minute averaging also keeps quotes stable for users: the fee a wallet displays
/// at signing time cannot jump materially in the seconds before inclusion, because even
/// a violent spot move only enters the fee base at 1/300th per second.
uint32 public twapFastWindow;
/// @notice Ceiling on the fast window's influence, in bps of the slow TWAP (e.g. 30000 =
/// fast can raise the fee base to at most 3x slow). Must be >= 1e4 when fast is enabled.
uint16 public spikeCapBps;
/// @notice Timestamp the oracle was (re)wired — anchors the UI's warmup countdown
/// (readyAt ≈ twapWiredAt + twapWindow). Purely informational; `live` in
/// feeOracleStatus() is the authoritative signal.
uint64 public twapWiredAt;
/// @notice Clamp bounds on the oracle-derived notional (wei). A manipulated or broken
/// pool can only move the fee (and the synced fallback cache) within this governor-set
/// band, which is why a non-zero maxNotionalWei is REQUIRED while the oracle is live.
uint256 public minNotionalWei;
uint256 public maxNotionalWei;
/// @notice Addresses that trade with a ZERO ETH fee — the admin seeding switch. Fees are
/// live for everyone from block one; the deployer/admin is exempted just long enough to
/// seed AMM inventory and the LP, then the exemption is revoked. Per-address (not a
/// global off switch) so seeding can never accidentally open a fee-free window for the
/// public, and revocation is one call that can't be front-run into free trades by others.
mapping(address => bool) public feeExempt;
mapping(uint256 => bool) public inventory;
mapping(uint256 => uint256) private _nextTokenId;
mapping(uint256 => uint256) private _prevTokenId;
uint256 public oldestTokenId;
uint256 public newestTokenId;
uint256 public inventoryCount;
constructor(
address token_,
address collection_,
address escrow_,
address stockBooster_,
address protocolTreasury_,
uint256 tokensPerNFT_,
uint256 ethNotionalPerNFT_,
uint16 randomFeeBps_,
uint16 specificFeeBps_
) {
if (
token_ == address(0) || collection_ == address(0) || escrow_ == address(0) || stockBooster_ == address(0)
|| protocolTreasury_ == address(0)
) revert ZeroAddress();
if (randomFeeBps_ > MAX_FEE_BPS) revert InvalidRandomFee();
if (specificFeeBps_ > MAX_FEE_BPS) revert InvalidSpecificFee();
if (specificFeeBps_ < randomFeeBps_) revert InvalidSpecificFee();
token = IERC20(token_);
collection = IERC721(collection_);
escrow = ITokenEscrowReserve(escrow_);
stockBooster = stockBooster_;
protocolTreasury = protocolTreasury_;
tokensPerNFT = tokensPerNFT_;
ethNotionalPerNFT = ethNotionalPerNFT_;
randomFeeBps = randomFeeBps_;
specificFeeBps = specificFeeBps_;
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(PAUSER_ROLE, msg.sender);
}
/// @notice Sell one NFT into the vault for the full `tokensPerNFT` payout. The seller pays
/// the ETH trade fee via msg.value (excess refunded); the token payout itself is uncut.
/// @dev Strict CEI: the fee is CHECKED up front, all token/NFT effects complete, and only
/// then is ETH distributed/refunded (external calls last, on top of nonReentrant).
function sellNFT(uint256 tokenId, uint256 minPayout) external payable nonReentrant whenNotPaused {
uint256 ethFee = _ethFeeAndSync(randomFeeBps);
if (msg.value < ethFee) revert InsufficientEthFee();
uint256 payout = tokensPerNFT;
if (payout < minPayout) revert SlippageExceeded();
collection.safeTransferFrom(msg.sender, address(this), tokenId);
_pushInventory(tokenId);
escrow.release(address(this), payout, ITokenEscrowReserve.BucketType.AMM);
token.safeTransfer(msg.sender, payout);
(uint256 boosterShare, uint256 protocolShare) = _distributeEthFee(ethFee);
emit Events.NFTSold(msg.sender, tokenId, payout, ethFee, boosterShare, protocolShare);
}
/// @notice Buy the oldest NFT in inventory for exactly `tokensPerNFT` plus the ETH fee.
function buyRandomNFT(uint256 maxCost) external payable nonReentrant whenNotPaused {
if (inventoryCount == 0) revert EmptyInventory();
uint256 ethFee = _ethFeeAndSync(randomFeeBps);
if (msg.value < ethFee) revert InsufficientEthFee();
uint256 cost = tokensPerNFT;
if (cost > maxCost) revert SlippageExceeded();
token.safeTransferFrom(msg.sender, address(this), cost);
token.forceApprove(address(escrow), cost);
escrow.returnTokens(cost, ITokenEscrowReserve.BucketType.AMM);
uint256 tokenId = oldestTokenId;
_removeInventory(tokenId);
collection.safeTransferFrom(address(this), msg.sender, tokenId);
(uint256 boosterShare, uint256 protocolShare) = _distributeEthFee(ethFee);
emit Events.NFTBought(msg.sender, tokenId, cost, ethFee, boosterShare, protocolShare, false);
}
/// @notice Buy a specific NFT out of inventory; uses the higher `specificFeeBps` ETH fee.
function buySpecificNFT(uint256 tokenId, uint256 maxCost) external payable nonReentrant whenNotPaused {
if (!inventory[tokenId]) revert NotInInventory();
uint256 ethFee = _ethFeeAndSync(specificFeeBps);
if (msg.value < ethFee) revert InsufficientEthFee();
uint256 cost = tokensPerNFT;
if (cost > maxCost) revert SlippageExceeded();
token.safeTransferFrom(msg.sender, address(this), cost);
token.forceApprove(address(escrow), cost);
escrow.returnTokens(cost, ITokenEscrowReserve.BucketType.AMM);
_removeInventory(tokenId);
collection.safeTransferFrom(address(this), msg.sender, tokenId);
(uint256 boosterShare, uint256 protocolShare) = _distributeEthFee(ethFee);
emit Events.NFTBought(msg.sender, tokenId, cost, ethFee, boosterShare, protocolShare, true);
}
// ------------------------------------------------------------------
// Quotes
// ------------------------------------------------------------------
function quoteRandomBuy()
external
view
returns (uint256 tokenCost, uint256 ethFee, uint256 inventorySize, uint256 nextTokenId)
{
tokenCost = tokensPerNFT;
ethFee = _ethFee(randomFeeBps);
inventorySize = inventoryCount;
nextTokenId = oldestTokenId;
}
function quoteSpecificBuy(uint256 tokenId)
external
view
returns (uint256 tokenCost, uint256 ethFee, bool available)
{
tokenCost = tokensPerNFT;
ethFee = _ethFee(specificFeeBps);
available = inventory[tokenId];
}
function quoteSellNFT() external view returns (uint256 tokenPayout, uint256 ethFee) {
tokenPayout = tokensPerNFT;
ethFee = _ethFee(randomFeeBps);
}
/// @notice One-call UI read for the fee oracle: what fees cost right now, whether they
/// come from the live TWAP or the fixed launch rate, and — during warmup — when the
/// oracle is expected to take over.
/// @return wired oracle configured (pool set)
/// @return live this block's notional came from the TWAP (false = fixed fallback,
/// the launch mode / outage mode)
/// @return notionalWei current fee base (live TWAP value of tokensPerNFT, clamped; or
/// the fallback)
/// @return swapFeeWei ETH fee for sells / random buys right now
/// @return snipeFeeWei ETH fee for specific buys right now
/// @return window TWAP averaging window (seconds) — the fee is a rolling average
/// over this period, re-computed every block
/// @return readyAt estimated timestamp the TWAP starts answering (wiredAt + window);
/// 0 once live or when not wired. A UI hint only — `live` is the
/// authoritative flag and can flip true earlier if the pool already
/// had history when wired.
function feeOracleStatus()
external
view
returns (
bool wired,
bool live,
uint256 notionalWei,
uint256 swapFeeWei,
uint256 snipeFeeWei,
uint32 window,
uint256 readyAt
)
{
wired = twapPool != address(0);
(notionalWei, live) = _liveNotional();
swapFeeWei = (notionalWei * randomFeeBps) / 10_000;
snipeFeeWei = (notionalWei * specificFeeBps) / 10_000;
window = twapWindow;
if (wired && !live) {
uint256 estimate = uint256(twapWiredAt) + window;
readyAt = estimate > block.timestamp ? estimate : 0;
}
}
function inventoryLength() external view returns (uint256) {
return inventoryCount;
}
/// @notice Returns the next tokenId in the FIFO linked list (decoded). Returns 0 if tail.
function getNextTokenId(uint256 tokenId) external view returns (uint256) {
uint256 encoded = _nextTokenId[tokenId];
return encoded == 0 ? 0 : _decodeTokenId(encoded);
}
// ------------------------------------------------------------------
// Admin
// ------------------------------------------------------------------
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
function setFees(uint16 randomFeeBps_, uint16 specificFeeBps_) external onlyRole(GOVERNOR_ROLE) {
if (randomFeeBps_ > MAX_FEE_BPS) revert InvalidRandomFee();
if (specificFeeBps_ > MAX_FEE_BPS) revert InvalidSpecificFee();
if (specificFeeBps_ < randomFeeBps_) revert InvalidSpecificFee();
randomFeeBps = randomFeeBps_;
specificFeeBps = specificFeeBps_;
emit Events.FeesUpdated(address(this), randomFeeBps_, specificFeeBps_);
}
/// @notice Grants/revokes a zero-ETH-fee exemption for one address — the admin seeding
/// switch. Turn on for the seeding wallet, seed AMM inventory + LP, turn off. Fees for
/// everyone else are never affected.
function setFeeExempt(address account, bool exempt) external onlyRole(GOVERNOR_ROLE) {
if (account == address(0)) revert ZeroAddress();
feeExempt[account] = exempt;
emit Events.FeeExemptUpdated(address(this), account, exempt);
}
/// @notice Updates the fallback ETH notional per NFT — the fixed launch fee base until
/// the oracle warms up, and the outage fallback after (successful reads overwrite it).
/// Launch policy: keep this non-zero; admin seeding uses setFeeExempt instead of zeroing
/// fees for everyone.
function setEthNotionalPerNFT(uint256 newNotionalWei) external onlyRole(GOVERNOR_ROLE) {
emit Events.EthNotionalUpdated(address(this), ethNotionalPerNFT, newNotionalWei);
ethNotionalPerNFT = newNotionalWei;
}
/// @notice Wires (or re-points / disables with pool=0) the live-notional TWAP oracle.
/// Direct route: pool = STONKBROKER/quoteToken, midPool = midToken = 0. Two-hop route
/// (main LP not paired with WETH, e.g. STONKBROKER/USDG): pool = STONKBROKER/midToken and
/// midPool = midToken/quoteToken. Every leg's token pair is validated on-chain so a
/// fat-fingered pool address can never silently misprice fees, and re-pointing after a
/// liquidity migration is this one governor call — trades never stop, because any gap or
/// unwarmed pool just serves the fallback notional.
///
/// A clamp band (setNotionalBounds with maxWei != 0) is REQUIRED first: it is the blast-
/// radius limit that keeps even a successfully manipulated pool from runaway fees or
/// poisoning the fallback cache. Prerequisite before the oracle answers:
/// increaseObservationCardinalityNext on each pool and one full window of history.
function setTwapOracle(address pool, address quoteToken, uint32 window, address midPool, address midToken)
external
onlyRole(GOVERNOR_ROLE)
{
if (pool == address(0)) {
twapPool = address(0);
twapQuoteToken = address(0);
twapMidPool = address(0);
twapMidToken = address(0);
twapWindow = 0;
twapWiredAt = 0;
emit Events.TwapOracleUpdated(address(this), address(0), address(0), address(0), address(0), 0);
return;
}
if (quoteToken == address(0) || window == 0) revert InvalidConfig();
if (tokensPerNFT > type(uint128).max) revert InvalidConfig();
if (maxNotionalWei == 0) revert InvalidConfig(); // band must exist while oracle is live
if (twapFastWindow != 0 && twapFastWindow >= window) revert InvalidConfig();
if (midPool == address(0)) {
if (midToken != address(0)) revert InvalidConfig();
_requirePair(pool, address(token), quoteToken);
} else {
if (midToken == address(0)) revert InvalidConfig();
_requirePair(pool, address(token), midToken);
_requirePair(midPool, midToken, quoteToken);
}
twapPool = pool;
twapQuoteToken = quoteToken;
twapMidPool = midPool;
twapMidToken = midToken;
twapWindow = window;
twapWiredAt = uint64(block.timestamp);
emit Events.TwapOracleUpdated(address(this), pool, quoteToken, midPool, midToken, window);
}
/// @dev Reverts unless `pool` holds exactly the {a, b} pair (either order).
function _requirePair(address pool, address a, address b) internal view {
address t0 = ITwapPoolTokens(pool).token0();
address t1 = ITwapPoolTokens(pool).token1();
if (!((t0 == a && t1 == b) || (t1 == a && t0 == b))) revert InvalidConfig();
}
/// @notice Governor band the oracle notional is clamped into — the blast-radius limit for
/// pool manipulation or a mispriced pool. While the oracle is live the upper clamp is
/// mandatory (maxWei != 0); it can only be lifted after disabling the oracle.
function setNotionalBounds(uint256 minWei, uint256 maxWei) external onlyRole(GOVERNOR_ROLE) {
if (maxWei != 0 && minWei > maxWei) revert InvalidConfig();
if (maxWei == 0 && twapPool != address(0)) revert InvalidConfig();
minNotionalWei = minWei;
maxNotionalWei = maxWei;
emit Events.NotionalBoundsUpdated(address(this), minWei, maxWei);
}
/// @notice Configures spike capture: a fast TWAP window that lets the fee base track
/// genuine rallies within ~fastWindow instead of ~twapWindow. fastWindow = 0 disables.
/// The fast window must be strictly shorter than the slow window, and capBps >= 1e4 so
/// the fast leg can only ever RAISE the fee base above the slow floor, never cut it.
function setTwapFastWindow(uint32 fastWindow, uint16 capBps) external onlyRole(GOVERNOR_ROLE) {
if (fastWindow != 0) {
if (capBps < 10_000) revert InvalidConfig();
if (twapWindow != 0 && fastWindow >= twapWindow) revert InvalidConfig();
}
twapFastWindow = fastWindow;
spikeCapBps = capBps;
emit Events.TwapFastWindowUpdated(address(this), fastWindow, capBps);
}
/// @notice Loan liquidations transfer collateral here then register it via this call.
function ingestNFT(uint256 tokenId) external onlyRole(INGEST_ROLE) {
require(collection.ownerOf(tokenId) == address(this), "NFT not held by vault");
if (inventory[tokenId]) return;
_pushInventory(tokenId);
}
// ------------------------------------------------------------------
// Internal: ETH fee handling
// ------------------------------------------------------------------
/// @notice The ETH notional one NFT is currently worth: live TWAP value of `tokensPerNFT`
/// (clamped to the governor band) when the oracle answers, else the stored fallback.
function liveEthNotionalPerNFT() public view returns (uint256) {
(uint256 notional,) = _liveNotional();
return notional;
}
/// @dev (notional, fromOracle). Never reverts: any oracle failure — either hop — returns
/// the fallback. When the fast window is enabled and answers, the fee base is
/// max(slow, min(fast, slow * spikeCapBps / 1e4)); a fast-window failure silently
/// degrades to slow-only. The clamp band is applied AFTER blending so it bounds what
/// trades pay and what the fallback cache can be poisoned to.
function _liveNotional() internal view returns (uint256, bool) {
if (twapPool != address(0) && twapWindow > 0) {
(bool ok, uint256 quoted) = _twapNotional(twapWindow);
if (ok) {
if (twapFastWindow > 0 && spikeCapBps >= 10_000) {
(bool fastOk, uint256 fast) = _twapNotional(twapFastWindow);
if (fastOk && fast > quoted) {
uint256 cap = (quoted * spikeCapBps) / 10_000;
quoted = fast < cap ? fast : cap;
}
}
if (quoted < minNotionalWei) quoted = minNotionalWei;
if (maxNotionalWei != 0 && quoted > maxNotionalWei) quoted = maxNotionalWei;
return (quoted, true);
}
}
return (ethNotionalPerNFT, false);
}
/// @dev One full (possibly two-hop) TWAP quote of tokensPerNFT at the given window.
function _twapNotional(uint32 window) internal view returns (bool ok, uint256 quoted) {
if (twapMidPool == address(0)) {
(ok, quoted) = TwapQuote.tryQuote(twapPool, window, uint128(tokensPerNFT), address(token), twapQuoteToken);
} else {
// Hop 1: tokensPerNFT -> mid token value.
(ok, quoted) = TwapQuote.tryQuote(twapPool, window, uint128(tokensPerNFT), address(token), twapMidToken);
// Hop 2: mid value -> WETH. Oversized hop-1 output (deliberately broken mid
// pair) fails soft rather than truncating into a wrong fee.
if (ok && quoted <= type(uint128).max) {
(ok, quoted) = TwapQuote.tryQuote(twapMidPool, window, uint128(quoted), twapMidToken, twapQuoteToken);
} else {
ok = false;
}
}
}
function _ethFee(uint16 feeBps) internal view returns (uint256) {
if (feeExempt[msg.sender]) return 0;
(uint256 notional,) = _liveNotional();
return (notional * feeBps) / 10_000;
}
/// @dev Trade-path variant of _ethFee: additionally refreshes the fallback cache whenever
/// the oracle answered, so an oracle outage later charges the last true market value
/// instead of a stale hand-set number. The cache sync runs even for fee-exempt callers
/// so seeding trades keep the fallback fresh.
function _ethFeeAndSync(uint16 feeBps) internal returns (uint256) {
(uint256 notional, bool fromOracle) = _liveNotional();
if (fromOracle && notional != ethNotionalPerNFT) ethNotionalPerNFT = notional;
if (feeExempt[msg.sender]) return 0;
return (notional * feeBps) / 10_000;
}
/// @dev Splits the (already-checked) fee 70/30 booster/protocol (dust to protocol),
/// forwards both shares, and refunds any excess to the caller. Always called as the LAST
/// step of a trade so every external ETH send happens after all token/NFT state changes
/// (strict CEI); reentrancy is additionally blocked by nonReentrant on every entry point.
function _distributeEthFee(uint256 ethFee) internal returns (uint256 boosterShare, uint256 protocolShare) {
boosterShare = (ethFee * BOOSTER_SHARE_BPS) / 10_000;
protocolShare = ethFee - boosterShare;
if (boosterShare > 0) _sendEth(stockBooster, boosterShare);
if (protocolShare > 0) _sendEth(protocolTreasury, protocolShare);
uint256 refund = msg.value - ethFee;
if (refund > 0) _sendEth(msg.sender, refund);
}
/// @notice Admin rescue for ETH force-sent to the vault (e.g. selfdestruct). The vault
/// never holds ETH between transactions in normal operation — trade fees are checked
/// against msg.value and fully distributed/refunded within the same call — so anything
/// sitting here is stray by definition and safe to sweep.
function rescueEth(address to, uint256 amount) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (to == address(0)) revert ZeroAddress();
_sendEth(to, amount);
}
function _sendEth(address to, uint256 amount) internal {
(bool ok,) = payable(to).call{value: amount}("");
if (!ok) revert EthTransferFailed();
}
// ------------------------------------------------------------------
// Internal: FIFO inventory (unchanged from Anvil)
// ------------------------------------------------------------------
/// @dev Appends tokenId to the tail of the doubly-linked inventory list. Reverts if already tracked.
function _pushInventory(uint256 tokenId) internal {
require(!inventory[tokenId], "already tracked");
inventory[tokenId] = true;
if (inventoryCount == 0) {
oldestTokenId = tokenId;
newestTokenId = tokenId;
} else {
_nextTokenId[newestTokenId] = _encodeTokenId(tokenId);
_prevTokenId[tokenId] = _encodeTokenId(newestTokenId);
newestTokenId = tokenId;
}
inventoryCount += 1;
}
/// @dev Unlinks tokenId from the doubly-linked inventory list and clears its tracking state.
function _removeInventory(uint256 tokenId) internal {
if (!inventory[tokenId]) revert NotInInventory();
uint256 prevEncoded = _prevTokenId[tokenId];
uint256 nextEncoded = _nextTokenId[tokenId];
bool hasPrev = prevEncoded != 0;
bool hasNext = nextEncoded != 0;
uint256 prevId = hasPrev ? _decodeTokenId(prevEncoded) : 0;
uint256 nextId = hasNext ? _decodeTokenId(nextEncoded) : 0;
if (!hasPrev) {
oldestTokenId = nextId;
} else {
_nextTokenId[prevId] = hasNext ? _encodeTokenId(nextId) : 0;
}
if (!hasNext) {
newestTokenId = prevId;
} else {
_prevTokenId[nextId] = hasPrev ? _encodeTokenId(prevId) : 0;
}
delete _prevTokenId[tokenId];
delete _nextTokenId[tokenId];
delete inventory[tokenId];
inventoryCount -= 1;
if (inventoryCount == 0) {
oldestTokenId = 0;
newestTokenId = 0;
}
}
function _encodeTokenId(uint256 tokenId) private pure returns (uint256) {
return tokenId + 1;
}
/// @dev Reverses _encodeTokenId: (encoded - 1) recovers the original tokenId.
function _decodeTokenId(uint256 encodedTokenId) private pure returns (uint256) {
return encodedTokenId - 1;
}
}
Chain explorer5930msChain node88ms