ActivationManager
0xacd5ae3c060c1137fe2ee86b0ab2ef697456f664
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
4,573 bytes
ABI entries
48
19 read · 8 write
License
none
Contract information
- Address
- 0xacd5ae3c060c1137fe2ee86b0ab2ef697456f664
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0xb668382cF4…A809787CDa
- Creation tx
- 0x3ed05353d3…f1142f2efb
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (19)
BPS() → uint16
TIER_COUNT() → uint8
activationOf(uint256) → bool, uint8
activeCount() → uint256
activeTokenAt(uint256) → uint256
activeWeight(uint256) → uint256
burnBps() → uint16
nft() → address
owner() → address
paused() → bool
protocolBps() → uint16
protocolTreasury() → address
quoteActivation(uint256, uint8) → uint256
quoteActivationBatch(uint256[], uint8[]) → uint256
redirectBps() → uint16
redirectReceiver() → address
stonkBroker() → address
tiers(uint256) → uint256, uint16
totalActiveWeight() → uint256
Events (7)
ActivatedActivationClearedActivationFeeSplitUpdatedActivationUpgradedOwnershipTransferredPausedUnpaused
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "nft_",
"type": "address"
},
{
"internalType": "address",
"name": "stonkBroker_",
"type": "address"
},
{
"internalType": "address",
"name": "protocolTreasury_",
"type": "address"
},
{
"internalType": "address",
"name": "owner_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"type": "error"
},
{
"inputs": [],
"name": "InvalidConfig",
"type": "error"
},
{
"inputs": [],
"name": "InvalidFeeSplit",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTier",
"type": "error"
},
{
"inputs": [],
"name": "NotTokenOwner",
"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": "TierNotUpgrade",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint8",
"name": "tier",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint256",
"name": "feePaid",
"type": "uint256"
}
],
"name": "Activated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ActivationCleared",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "burnBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "protocolBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "redirectBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "address",
"name": "redirectReceiver",
"type": "address"
}
],
"name": "ActivationFeeSplitUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint8",
"name": "fromTier",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "toTier",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint256",
"name": "feePaid",
"type": "uint256"
}
],
"name": "ActivationUpgraded",
"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": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TIER_COUNT",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "tier",
"type": "uint8"
}
],
"name": "activate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256[]",
"name": "tokenIds",
"type": "uint256[]"
},
{
"internalType": "uint8[]",
"name": "tiersWanted",
"type": "uint8[]"
}
],
"name": "activateBatch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "activationOf",
"outputs": [
{
"internalType": "bool",
"name": "activated",
"type": "bool"
},
{
"internalType": "uint8",
"name": "tier",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "activeCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "activeTokenAt",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "activeWeight",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "burnBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "clearActivation",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "nft",
"outputs": [
{
"internalType": "contract IERC721",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"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": "protocolBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolTreasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "tier",
"type": "uint8"
}
],
"name": "quoteActivation",
"outputs": [
{
"internalType": "uint256",
"name": "feeDue",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256[]",
"name": "tokenIds",
"type": "uint256[]"
},
{
"internalType": "uint8[]",
"name": "tiersWanted",
"type": "uint8[]"
}
],
"name": "quoteActivationBatch",
"outputs": [
{
"internalType": "uint256",
"name": "total",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "redirectBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "redirectReceiver",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "burnBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "protocolBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "redirectBps_",
"type": "uint16"
},
{
"internalType": "address",
"name": "redirectReceiver_",
"type": "address"
}
],
"name": "setFeeSplit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "stonkBroker",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "tiers",
"outputs": [
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "multiplierBps",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalActiveWeight",
"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": [],
"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 {ERC20Burnable} from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {Pausable} from "openzeppelin-contracts/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {
ZeroAddress,
Unauthorized,
NotTokenOwner,
InvalidTier,
TierNotUpgrade,
InvalidFeeSplit,
InvalidConfig
} from "../libs/Errors.sol";
import {Events} from "../libs/Events.sol";
/// @title ActivationManager
/// @notice Per-NFT paid activation for StonkBrokers. Holders pay a non-refundable
/// $STONKBROKER fee to activate a broker at a tier; only activated brokers receive
/// StockBooster airdrops, weighted by their tier multiplier.
///
/// Tier schedule (fees in whole $STONKBROKER, 18 decimals; multipliers in bps):
/// Base 66,666 1.00x (10000) — Floor Trader
/// T1 166,666 1.25x (12500) — Analyst
/// T2 366,666 1.60x (16000) — Portfolio Manager
/// T3 666,666 2.00x (20000) — Managing Director
/// T4 1,666,666 3.33x (33300) — Partner
///
/// Ladder economics (why these numbers): the benchmark for "one more unit of drop weight"
/// is buying a second broker from the AMM — 666,666 tokensPerNFT capital + the 10% ETH
/// swap fee + the 66,666 base activation for the new broker ≈ 800k all-in for +10000
/// weight (~80 per weight point). The anchor rung (per launch decision) is T3: reaching
/// 2.00x from scratch costs exactly the broker's 666,666 sticker price but skips the 10%
/// ETH fee and the second activation fee, needs no AMM inventory, and lives on one NFT —
/// a slightly better deal than buying one more broker. Marginal pricing per rung: 40 / 57
/// / 75 / 75 per weight point, so every upgrade is at-or-slightly-better than the broker
/// route, with the earliest rungs the best value. Earlier drafts priced rungs at up to
/// 60,000 per weight point (750x the broker route), making every tier above Base
/// economically irrational and starving the burn sink.
///
/// Upgrades are allowed at any time and cost only the DIFFERENCE between the new tier's fee
/// and the already-paid tier's fee (per launch decision — no full re-pay).
///
/// Activation clears on EVERY true ownership transfer of the NFT, with no approved-custody
/// exceptions (per launch decision): selling into the Anvil AMM, escrowing as loan collateral,
/// or plain wallet-to-wallet transfers all reset it. The NFT contract calls clearActivation()
/// from its transfer hook. Stock already delivered to the token-bound wallet stays with the
/// NFT — clearing only stops future accrual.
///
/// Fee destination is a configurable bps split across three sinks (must sum to 10000):
/// burnBps — burned via ERC20Burnable (default 50%)
/// protocolBps — protocol treasury (default 50%)
/// redirectBps — optional redirect receiver, e.g. a distributor that pushes $STONKBROKER
/// back to broker token-bound wallets (default 0%)
contract ActivationManager is Ownable, Pausable, ReentrancyGuard {
using SafeERC20 for IERC20;
struct Tier {
uint256 fee;
uint16 multiplierBps;
}
uint8 public constant TIER_COUNT = 5;
uint16 public constant BPS = 10_000;
IERC721 public immutable nft;
IERC20 public immutable stonkBroker;
address public immutable protocolTreasury;
/// @notice Fee split, in bps of every activation fee collected. Sums to 10000.
uint16 public burnBps = 5000;
uint16 public protocolBps = 5000;
uint16 public redirectBps = 0;
/// @notice Optional third sink for the redirectBps share (e.g. a $STONKBROKER
/// back-to-wallets distributor). Must be set before redirectBps can be non-zero.
address public redirectReceiver;
/// @notice tier index + 1 for activated tokens; 0 = not activated.
mapping(uint256 => uint8) private _tierPlusOne;
/// @notice Multiplier bps for activated tokens; 0 when inactive.
mapping(uint256 => uint256) public activeWeight;
uint256 public totalActiveWeight;
/// @dev Swap-and-pop enumerable set of activated tokenIds, so the StockBooster can page
/// through ONLY activated brokers (never all 4,444) when pushing airdrops.
uint256[] private _activeTokens;
mapping(uint256 => uint256) private _activeIndexPlusOne;
Tier[TIER_COUNT] public tiers;
constructor(address nft_, address stonkBroker_, address protocolTreasury_, address owner_) Ownable(owner_) {
if (nft_ == address(0) || stonkBroker_ == address(0) || protocolTreasury_ == address(0)) revert ZeroAddress();
nft = IERC721(nft_);
stonkBroker = IERC20(stonkBroker_);
protocolTreasury = protocolTreasury_;
tiers[0] = Tier({fee: 66_666e18, multiplierBps: 10_000});
tiers[1] = Tier({fee: 166_666e18, multiplierBps: 12_500});
tiers[2] = Tier({fee: 366_666e18, multiplierBps: 16_000});
tiers[3] = Tier({fee: 666_666e18, multiplierBps: 20_000});
tiers[4] = Tier({fee: 1_666_666e18, multiplierBps: 33_300});
}
// ------------------------------------------------------------------
// User actions
// ------------------------------------------------------------------
/// @notice Activates `tokenId` at `tier`, or upgrades it if already activated at a lower
/// tier. First activation pays the full tier fee; upgrades pay only the difference between
/// the new and current tier fees. Downgrades and same-tier re-activation revert.
function activate(uint256 tokenId, uint8 tier) external nonReentrant whenNotPaused {
_collectFee(msg.sender, _activateOne(tokenId, tier));
}
/// @notice Batch form of activate(): activates/upgrades many brokers in ONE transaction
/// with a SINGLE fee pull for the summed total (one transferFrom + one burn + one treasury
/// transfer instead of one set per broker). Semantics per entry are identical to sequential
/// activate() calls — including atomicity: any invalid entry (not owner, bad tier,
/// non-upgrade, duplicate at the same tier) reverts the whole batch.
function activateBatch(uint256[] calldata tokenIds, uint8[] calldata tiersWanted)
external
nonReentrant
whenNotPaused
{
uint256 n = tokenIds.length;
if (n == 0 || n != tiersWanted.length) revert InvalidConfig();
uint256 totalFee;
for (uint256 i = 0; i < n; i++) {
totalFee += _activateOne(tokenIds[i], tiersWanted[i]);
}
_collectFee(msg.sender, totalFee);
}
/// @dev Validation + state update + event for one activation/upgrade; returns the fee due.
/// Fee COLLECTION is hoisted to the external entry points so a batch can pull once for the
/// sum. State-before-collection is safe here: both entry points are nonReentrant and the
/// whole transaction reverts (state included) if the pull fails.
function _activateOne(uint256 tokenId, uint8 tier) internal returns (uint256 feeDue) {
if (tier >= TIER_COUNT) revert InvalidTier();
if (nft.ownerOf(tokenId) != msg.sender) revert NotTokenOwner();
uint8 currentPlusOne = _tierPlusOne[tokenId];
if (currentPlusOne == 0) {
feeDue = tiers[tier].fee;
} else {
uint8 currentTier = currentPlusOne - 1;
if (tier <= currentTier) revert TierNotUpgrade();
feeDue = tiers[tier].fee - tiers[currentTier].fee;
}
uint256 newWeight = tiers[tier].multiplierBps;
uint256 oldWeight = activeWeight[tokenId];
_tierPlusOne[tokenId] = tier + 1;
activeWeight[tokenId] = newWeight;
totalActiveWeight = totalActiveWeight - oldWeight + newWeight;
if (_activeIndexPlusOne[tokenId] == 0) {
_activeTokens.push(tokenId);
_activeIndexPlusOne[tokenId] = _activeTokens.length;
}
if (currentPlusOne == 0) {
emit Events.Activated(tokenId, msg.sender, tier, feeDue);
} else {
emit Events.ActivationUpgraded(tokenId, msg.sender, currentPlusOne - 1, tier, feeDue);
}
}
// ------------------------------------------------------------------
// NFT transfer hook
// ------------------------------------------------------------------
/// @notice Clears activation for `tokenId`. Only the NFT contract may call this, from its
/// ERC-721 transfer hook, so every true ownership change resets activation exactly once.
/// Idempotent: clearing an inactive token is a no-op (transfer hooks must never revert).
/// Deliberately NOT pausable — pausing it would let activation survive a transfer, and NFT
/// transfers must never depend on this contract's admin state.
function clearActivation(uint256 tokenId) external {
if (msg.sender != address(nft)) revert Unauthorized();
if (_tierPlusOne[tokenId] == 0) return;
totalActiveWeight -= activeWeight[tokenId];
delete activeWeight[tokenId];
delete _tierPlusOne[tokenId];
uint256 idxPlusOne = _activeIndexPlusOne[tokenId];
uint256 lastIdx = _activeTokens.length - 1;
uint256 idx = idxPlusOne - 1;
if (idx != lastIdx) {
uint256 movedToken = _activeTokens[lastIdx];
_activeTokens[idx] = movedToken;
_activeIndexPlusOne[movedToken] = idxPlusOne;
}
_activeTokens.pop();
delete _activeIndexPlusOne[tokenId];
emit Events.ActivationCleared(tokenId);
}
// ------------------------------------------------------------------
// Owner config
// ------------------------------------------------------------------
/// @notice Updates the activation fee split. Shares must sum to exactly 10000 bps; a
/// non-zero redirect share requires a non-zero receiver. This is the "optionally redirect
/// activation fees to StonkBroker wallets" switch — point redirectReceiver at a distributor
/// and shift bps into redirectBps.
function setFeeSplit(uint16 burnBps_, uint16 protocolBps_, uint16 redirectBps_, address redirectReceiver_)
external
onlyOwner
{
if (uint256(burnBps_) + protocolBps_ + redirectBps_ != BPS) revert InvalidFeeSplit();
if (redirectBps_ > 0 && redirectReceiver_ == address(0)) revert ZeroAddress();
burnBps = burnBps_;
protocolBps = protocolBps_;
redirectBps = redirectBps_;
redirectReceiver = redirectReceiver_;
emit Events.ActivationFeeSplitUpdated(burnBps_, protocolBps_, redirectBps_, redirectReceiver_);
}
/// @notice Emergency stop for new activations/upgrades (fees are burned and non-refundable,
/// so a wrong config must be haltable). Clearing stays live — see clearActivation.
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
// ------------------------------------------------------------------
// Views
// ------------------------------------------------------------------
/// @notice Current tier of `tokenId`, or (false, 0) when not activated.
function activationOf(uint256 tokenId) external view returns (bool activated, uint8 tier) {
uint8 plusOne = _tierPlusOne[tokenId];
return plusOne == 0 ? (false, 0) : (true, plusOne - 1);
}
/// @notice Fee owed to move `tokenId` to `tier` right now (full fee if inactive,
/// difference if upgrading). Reverts on non-upgrades, mirroring activate().
function quoteActivation(uint256 tokenId, uint8 tier) public view returns (uint256 feeDue) {
if (tier >= TIER_COUNT) revert InvalidTier();
uint8 currentPlusOne = _tierPlusOne[tokenId];
if (currentPlusOne == 0) return tiers[tier].fee;
uint8 currentTier = currentPlusOne - 1;
if (tier <= currentTier) revert TierNotUpgrade();
return tiers[tier].fee - tiers[currentTier].fee;
}
/// @notice Total fee a matching activateBatch() call would pull right now — the exact
/// amount to approve. Assumes distinct tokenIds (duplicates would revert in the batch
/// itself anyway, since the second occurrence is not an upgrade).
function quoteActivationBatch(uint256[] calldata tokenIds, uint8[] calldata tiersWanted)
external
view
returns (uint256 total)
{
uint256 n = tokenIds.length;
if (n == 0 || n != tiersWanted.length) revert InvalidConfig();
for (uint256 i = 0; i < n; i++) {
total += quoteActivation(tokenIds[i], tiersWanted[i]);
}
}
function activeCount() external view returns (uint256) {
return _activeTokens.length;
}
function activeTokenAt(uint256 index) external view returns (uint256) {
return _activeTokens[index];
}
// ------------------------------------------------------------------
// Internal
// ------------------------------------------------------------------
/// @dev Pulls the fee then routes it per the configured split: burn share is burned from
/// this contract's balance ($STONKBROKER is ERC20Burnable), protocol share to treasury,
/// redirect share to the optional receiver. Remainder-from-rounding lands in the burn
/// share (computed by subtraction) so no dust is stranded here.
function _collectFee(address payer, uint256 amount) internal {
if (amount == 0) return;
stonkBroker.safeTransferFrom(payer, address(this), amount);
uint256 protocolShare = (amount * protocolBps) / BPS;
uint256 redirectShare = (amount * redirectBps) / BPS;
uint256 burnShare = amount - protocolShare - redirectShare;
if (protocolShare > 0) stonkBroker.safeTransfer(protocolTreasury, protocolShare);
if (redirectShare > 0) stonkBroker.safeTransfer(redirectReceiver, redirectShare);
if (burnShare > 0) ERC20Burnable(address(stonkBroker)).burn(burnShare);
}
}
Chain explorer3127msChain node76ms