BuzzerTreasury
Title: BuzzerTreasury — two-founder, ten-wallet fee sink
Author: Buzzer
Receives everything Buzzer sends — ETH, any ERC-20, and NFTs (ERC-721 / ERC-1155). Ownership is split between two founders, A and B, each holding five wallets (ten slots total, fixed for the life of the contract). There is no owner, no guardian and no admin.
Fungibles (ETH & ERC-20)
withdraw(token) is callable by any one of the ten current wallets, at most once per 7 days per
token. It pays a fixed 10% of the contract's current balance of that token, split equally — 1%
to each of the ten current slot addresses. A slot that cannot receive is skipped (its share
stays), so one bad address never blocks the others. This bounds a single compromised wallet to
its own 1%/week until it is rotated out — it can neither drain more nor change the split.
NFTs
NFTs can't be split ten ways, so by the founders' choice any one of the ten wallets may transfer a held NFT out to any address, with no cooldown and no quorum. The contract implements the receiver hooks so NFT transfers into it never fail.
Recovery (per group, 3-of-4, 48h)
To change the address in a slot, three distinct members of that slot's own group — excluding the slot being changed — approve the same new address; after a 48h timelock (cancellable by any group member) it takes effect. Group A can only rotate A-slots and group B only B-slots: A cannot recover B and B cannot recover A.
State Variables
GROUP_SIZE
Wallets per founder group.
uint256 public constant GROUP_SIZE = 5
WALLETS
Total wallet slots. Slots 0-4 are group A; 5-9 are group B.
uint256 public constant WALLETS = 10
WITHDRAW_BPS
Share of the current balance paid per withdraw, in basis points (10%).
uint256 public constant WITHDRAW_BPS = 1000
WITHDRAW_INTERVAL
Minimum time between two withdrawals of the same token.
uint256 public constant WITHDRAW_INTERVAL = 7 days
ROTATION_APPROVALS
Distinct in-group approvals (excluding the target slot) required to rotate a wallet.
uint256 public constant ROTATION_APPROVALS = 3
ROTATION_TIMELOCK
Delay between the 3rd approval and a rotation taking effect.
uint256 public constant ROTATION_TIMELOCK = 48 hours
wallets
Current address in each slot. 0-4 = group A, 5-9 = group B.
address[WALLETS] public wallets
slotPlus1
Reverse index: slot + 1 for a current wallet, 0 for any other address.
mapping(address => uint256) internal slotPlus1
lastWithdrawAt
Last withdrawal time per token (address(0) = ETH). ETH and each ERC-20 have independent clocks.
mapping(address token => uint64) public lastWithdrawAt
rotations
Rotation proposal per slot.
mapping(uint256 slot => Rotation) public rotations
Functions
constructor
constructor(address[WALLETS] memory initial) ;
Parameters
| Name | Type | Description |
|---|---|---|
initial |
address[WALLETS] |
The ten founder wallets: indices 0-4 = group A, 5-9 = group B. All non-zero and distinct. |
receive
Accepts ETH from anyone (Buzzer fee pushes, sweeps, donations).
receive() external payable;
allWallets
All ten current wallet addresses (0-4 = group A, 5-9 = group B).
function allWallets() external view returns (address[WALLETS] memory);
slotOf
Slot of a (and whether it is currently a wallet at all).
function slotOf(address a) external view returns (uint256 slot, bool isWallet);
nextWithdrawal
Amount the next withdraw(token) would pay each wallet, and when it becomes possible.
function nextWithdrawal(address token) external view returns (uint256 perWallet, uint256 availableAt);
withdraw
Pay 10% of the current balance of token, split equally (1% each) across the ten wallets.
Callable by any current wallet, at most once per WITHDRAW_INTERVAL per token.
The clock is stamped before any transfer, so a re-entrant withdraw hits TooSoon — a
malicious recipient cannot draw more than its share. A wallet that cannot receive (reverting
receive, token blocklist, …) is skipped with PayoutSkipped; its 1% stays and rolls into
the next round's base. Integer dust (from the /10) also stays. ETH sends carry a 50k stipend.
function withdraw(address token) external;
Parameters
| Name | Type | Description |
|---|---|---|
token |
address |
ERC-20 to withdraw, or address(0) for ETH. |
withdrawMany
Withdraw several tokens in one transaction. Callable by any current wallet.
Convenience batch over withdraw: each token is distributed on its own 7-day clock. A token
that is still on cooldown, or has nothing to pay, is silently skipped — so a mix of ready and
not-ready tokens still pays out the ready ones instead of reverting the whole batch. Duplicate
entries are deduped naturally (the first stamps the clock; the rest hit the cooldown).
function withdrawMany(address[] calldata tokens) external;
Parameters
| Name | Type | Description |
|---|---|---|
tokens |
address[] |
ERC-20 addresses to withdraw; use address(0) for ETH. |
_due
True once token's 7-day cooldown has elapsed (also true on first use, since real timestamps
far exceed WITHDRAW_INTERVAL).
function _due(address token) internal view returns (bool);
_distribute
Pay 10% of token's current balance, split equally (1% each) across the ten wallets. Returns the
per-wallet amount, or 0 if there was nothing to pay (in which case the clock is NOT stamped, so an
empty token isn't put on cooldown). Callers must check _due first. The clock is stamped before
any transfer, so a re-entrant withdraw of the same token hits the cooldown and can't double-pay.
A wallet that cannot receive (reverting receive, token blocklist, …) is skipped with
PayoutSkipped; its 1% stays and rolls into the next round's base. Integer dust also stays.
function _distribute(address token) internal returns (uint256 per);
withdrawERC721
Transfer a held ERC-721 out to to. Callable by any current wallet; no cooldown, no quorum.
function withdrawERC721(address collection, uint256 tokenId, address to) external;
Parameters
| Name | Type | Description |
|---|---|---|
collection |
address |
ERC-721 contract. |
tokenId |
uint256 |
Token to send. |
to |
address |
Recipient. |
withdrawERC1155
Transfer a held ERC-1155 balance out to to. Callable by any current wallet; no cooldown, no quorum.
function withdrawERC1155(address collection, uint256 id, uint256 amount, address to) external;
approveRotation
Approve rotating slot to newAddr. Caller must be in the same group as slot and must not
be the target slot. The 3rd distinct approval queues the change behind a 48h timelock.
A proposal is identified by its newAddr; approving a different address for the same slot
supersedes the current proposal (approvals reset). The target slot is excluded, so a single
compromised wallet can never approve its own rotation.
function approveRotation(uint256 slot, address newAddr) external;
executeRotation
Execute a queued rotation once its 48h timelock has passed. Anyone may finalise.
function executeRotation(uint256 slot) external;
cancelRotation
Cancel a pending rotation for slot. Any member of that slot's group EXCEPT the slot being
changed may cancel (the target cannot veto its own eviction).
function cancelRotation(uint256 slot) external;
onERC721Received
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4);
onERC1155Received
function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4);
onERC1155BatchReceived
function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
external
pure
returns (bytes4);
supportsInterface
ERC-165: advertises ERC-165 + the ERC-721 and ERC-1155 receiver interfaces.
function supportsInterface(bytes4 id) external pure returns (bool);
_slotOf
Slot index of a current wallet; reverts NotWallet otherwise.
function _slotOf(address a) internal view returns (uint256);
_group
Group of a slot: 0 = A (slots 0-4), 1 = B (slots 5-9).
function _group(uint256 slot) internal pure returns (uint256);
_sendETH
ETH send with a fixed 50k stipend; returns success instead of reverting.
function _sendETH(address to, uint256 amount) internal returns (bool ok);
_balanceOf
balanceOf(this) via staticcall; reverts TransferFailed on a non-conforming token.
function _balanceOf(address token) internal view returns (uint256);
_tryTransfer
transfer that returns success instead of reverting; tolerates no-return tokens.
function _tryTransfer(address token, address to, uint256 amount) internal returns (bool);
Events
Withdrawn
A withdrawal round. total is what was actually delivered; perWallet the intended 1% share.
event Withdrawn(address indexed token, uint256 total, uint256 perWallet);
PayoutSkipped
A wallet could not receive its share; the amount stays in the treasury.
event PayoutSkipped(address indexed token, address indexed to, uint256 amount);
NFTWithdrawn
An NFT was transferred out by one of the wallets.
event NFTWithdrawn(address indexed collection, address indexed to, uint256 id, uint256 amount, address by);
RotationApproved
A group member approved a rotation of slot to newAddr.
event RotationApproved(uint256 indexed slot, address indexed newAddr, address indexed by, uint8 approvals);
RotationQueued
The 3rd approval queued a rotation; it executes at executeAfter.
event RotationQueued(uint256 indexed slot, address indexed newAddr, uint64 executeAfter);
RotationExecuted
A rotation took effect: slot now holds newAddr.
event RotationExecuted(uint256 indexed slot, address indexed oldAddr, address indexed newAddr);
RotationCancelled
A pending rotation was cancelled by a group member.
event RotationCancelled(uint256 indexed slot, address indexed by);
Errors
NotWallet
Caller is not one of the ten current wallets.
error NotWallet();
NotGroupMember
Caller is not in the target slot's group, or is the target slot itself.
error NotGroupMember();
TooSoon
Withdrawal interval or rotation timelock has not elapsed.
error TooSoon();
Zero
Zero address not allowed.
error Zero();
DuplicateWallet
Address is already one of the ten wallets.
error DuplicateWallet();
BadSlot
Slot index out of range (must be 0-9).
error BadSlot();
AlreadyApproved
This group member has already approved the current proposal.
error AlreadyApproved();
NoProposal
No active rotation proposal for this slot.
error NoProposal();
NothingToWithdraw
Per-wallet share would be zero.
error NothingToWithdraw();
TransferFailed
ERC-20 balance query or NFT transfer failed.
error TransferFailed();
Structs
Rotation
An in-flight wallet rotation for one slot.
struct Rotation {
address newAddr;
uint64 executeAfter;
uint16 approverMask;
uint8 approvals;
}
Properties
| Name | Type | Description |
|---|---|---|
newAddr |
address |
Proposed replacement address (0 = no proposal). |
executeAfter |
uint64 |
Earliest execution time; 0 until the 3rd approval queues it. |
approverMask |
uint16 |
Bitmask of approver slot indices (bit i set = slot i approved). |
approvals |
uint8 |
Distinct approvals so far. |