Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
- Contract name:
- Allowed
- Optimization enabled
- false
- Compiler version
- v0.8.24+commit.e11b9ed9
- EVM Version
- Verified at
- 2026-03-13T02:47:22.240218Z
Constructor Arguments
src/allowed/Allowed.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;
import {IAllowedAdmin} from "./IAllowedAdmin.sol";
import {
ListAlreadyRegistered,
ListNotRegistered,
OnlyListOwner,
ListOwnerIsZeroAddress,
OwnableInvalidOwner
} from "./Constants.sol";
/**
* @title Allowed
* @notice Multi-list registry with hash-based namespacing.
* Public good contract — anyone can register a list with keccak256(namespace).
* Each list has its own owner with 2-step ownership transfer.
*
* Design:
* - List IDs are bytes32 hashes (e.g., keccak256("Name of List"))
* - Registration is open — anyone can register any hash with a specified owner
* - Each list owner can add/remove addresses from their list only
* - 2-step ownership transfer pattern (initiate → accept) for safety
* - No global admin — fully decentralized per-list management
*
* Use cases:
* - Token distribution allowed lists
* - Access control lists for DeFi protocols
* - Airdrop eligibility tracking
* - Any scenario requiring isolated, owner-managed address sets
*/
contract Allowed is IAllowedAdmin {
// ──────────────────────────────────────────────
// Storage
// ──────────────────────────────────────────────
/// @dev listId => account => isListed
mapping(bytes32 => mapping(address => bool)) private _listed;
/// @dev listId => owner address
mapping(bytes32 => address) private _listOwners;
/// @dev listId => pending owner address (for 2-step transfer)
mapping(bytes32 => address) private _pendingListOwners;
// ──────────────────────────────────────────────
// Constructor
// ──────────────────────────────────────────────
/// @dev Empty constructor — all lists are registered post-deploy
constructor() {}
// ──────────────────────────────────────────────
// Registration
// ──────────────────────────────────────────────
/**
* @notice Registers a new list with the specified owner.
* Anyone can register any hash. First-come, first-served.
* @param listId The keccak256 hash identifying this list
* @param owner The address that will own and manage this list
*/
function registerList(bytes32 listId, address owner) external {
if (_listOwners[listId] != address(0)) {
revert ListAlreadyRegistered(listId);
}
if (owner == address(0)) {
revert ListOwnerIsZeroAddress();
}
_listOwners[listId] = owner;
emit ListRegistered({listId: listId, owner: owner});
}
// ──────────────────────────────────────────────
// List queries
// ──────────────────────────────────────────────
/**
* @notice Returns whether the given address is listed in the specified list.
* @param listId The list to check
* @param account The address to check
* @return True if listed, false otherwise
*/
function isListed(bytes32 listId, address account) external view returns (bool) {
return _listed[listId][account];
}
/**
* @notice Returns the owner of the specified list.
* @param listId The list to query
* @return The owner address (zero if not registered)
*/
function getListOwner(bytes32 listId) external view returns (address) {
return _listOwners[listId];
}
/**
* @notice Returns the pending owner of the specified list (2-step transfer).
* @param listId The list to query
* @return The pending owner address (zero if no pending transfer)
*/
function getPendingListOwner(bytes32 listId) external view returns (address) {
return _pendingListOwners[listId];
}
// ──────────────────────────────────────────────
// List management (owner only)
// ──────────────────────────────────────────────
/**
* @notice Sets the listed status for a given address. Owner only.
* @param listId The list to modify
* @param account The address to update
* @param status The new listed status
*/
function setListed(bytes32 listId, address account, bool status) external {
_checkCallerIsOwner(listId);
_listed[listId][account] = status;
emit ListUpdated({listId: listId, account: account, status: status});
}
/**
* @notice Sets the listed status for multiple addresses at once. Owner only.
* @param listId The list to modify
* @param accounts Array of addresses to update
* @param status Listed status to apply to all addresses
* @dev For large lists, may need to be called multiple times in batches to avoid gas limits.
* Gas cost: ~26,000 per address. At 30M gas limit, max ~1,150 addresses per transaction.
* Recommended batch size: 300-500 addresses per transaction.
*/
function setListedBatch(bytes32 listId, address[] calldata accounts, bool status) external {
_checkCallerIsOwner(listId);
uint256 length = accounts.length;
for (uint256 i; i < length;) {
_listed[listId][accounts[i]] = status;
emit ListUpdated({listId: listId, account: accounts[i], status: status});
unchecked { ++i; }
}
}
// ──────────────────────────────────────────────
// Ownership management (2-step transfer)
// ──────────────────────────────────────────────
/**
* @notice Initiates ownership transfer for a list. Current owner only.
* The new owner must call `acceptListOwnership` to complete the transfer.
* @param listId The list to transfer
* @param newOwner The address to transfer ownership to
*/
function transferListOwnership(bytes32 listId, address newOwner) external {
_checkCallerIsOwner(listId);
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_pendingListOwners[listId] = newOwner;
emit ListOwnershipTransferStarted({listId: listId, previousOwner: msg.sender, newOwner: newOwner});
}
/**
* @notice Completes ownership transfer by accepting as the pending owner.
* @param listId The list to accept ownership of
*/
function acceptListOwnership(bytes32 listId) external {
address pendingOwner = _pendingListOwners[listId];
if (msg.sender != pendingOwner) {
revert OwnableInvalidOwner(msg.sender);
}
address previousOwner = _listOwners[listId];
delete _pendingListOwners[listId];
_listOwners[listId] = msg.sender;
emit ListOwnershipTransferred({listId: listId, previousOwner: previousOwner, newOwner: msg.sender});
}
/**
* @notice Cancels a pending ownership transfer. Current owner only.
* @param listId The list to cancel transfer for
*/
function cancelListOwnershipTransfer(bytes32 listId) external {
_checkCallerIsOwner(listId);
delete _pendingListOwners[listId];
}
// ──────────────────────────────────────────────
// Internal helpers
// ──────────────────────────────────────────────
/**
* @dev Reverts if msg.sender is not the owner of the list.
*/
function _checkCallerIsOwner(bytes32 listId) private view {
address owner = _listOwners[listId];
if (owner == address(0)) {
revert ListNotRegistered(listId);
}
if (msg.sender != owner) {
revert OnlyListOwner(listId, msg.sender);
}
}
}
src/allowed/IAllowed.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;
/**
* @title IAllowed
* @notice Client interface for the Allowed multi-list registry.
* Consumers (like ProveX) should reference this interface to query list status.
* This interface contains only read operations — no admin functions.
*
* Design rationale:
* - Minimal surface area for client contracts
* - No exposure to admin operations (ownership, list management)
* - Clients cannot accidentally call admin functions
* - Clear separation of concerns: query vs. management
*/
interface IAllowed {
/**
* @notice Returns whether the given address is listed in the specified list.
* @param listId The list to check
* @param account The address to check
* @return True if listed, false otherwise
*/
function isListed(bytes32 listId, address account) external view returns (bool);
/**
* @notice Returns the owner of the specified list.
* @param listId The list to query
* @return The owner address (zero if not registered)
*/
function getListOwner(bytes32 listId) external view returns (address);
/**
* @notice Returns the pending owner of the specified list (2-step transfer).
* @param listId The list to query
* @return The pending owner address (zero if no pending transfer)
*/
function getPendingListOwner(bytes32 listId) external view returns (address);
}
src/allowed/IAllowedAdmin.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;
import {IAllowed} from "./IAllowed.sol";
/**
* @title IAllowedAdmin
* @notice Complete admin interface for the Allowed multi-list registry.
* Extends IAllowed with registration, list management, and ownership operations.
* List owners and operators should reference this interface.
*
* Design rationale:
* - Extends IAllowed (clients get read-only, admins get full control)
* - Exposes all registry operations (registration, list updates, ownership)
* - Single source of truth for the complete Allowed API
* - Enforces interface compliance on the Allowed implementation
*/
interface IAllowedAdmin is IAllowed {
// ──────────────────────────────────────────────
// Events
// ──────────────────────────────────────────────
/// @dev Emitted when a new list is registered.
event ListRegistered(bytes32 indexed listId, address indexed owner);
/// @dev Emitted when list ownership transfer is initiated (2-step).
event ListOwnershipTransferStarted(bytes32 indexed listId, address indexed previousOwner, address indexed newOwner);
/// @dev Emitted when list ownership transfer is completed.
event ListOwnershipTransferred(bytes32 indexed listId, address indexed previousOwner, address indexed newOwner);
/// @dev Emitted when a list entry is updated.
event ListUpdated(bytes32 indexed listId, address indexed account, bool status);
// ──────────────────────────────────────────────
// Registration
// ──────────────────────────────────────────────
/**
* @notice Registers a new list with the specified owner.
* Anyone can register any hash. First-come, first-served.
* @param listId The keccak256 hash identifying this list
* @param owner The address that will own and manage this list
*/
function registerList(bytes32 listId, address owner) external;
// ──────────────────────────────────────────────
// List management (owner only)
// ──────────────────────────────────────────────
/**
* @notice Sets the listed status for a given address. Owner only.
* @param listId The list to modify
* @param account The address to update
* @param status The new listed status
*/
function setListed(bytes32 listId, address account, bool status) external;
/**
* @notice Sets the listed status for multiple addresses at once. Owner only.
* @param listId The list to modify
* @param accounts Array of addresses to update
* @param status Listed status to apply to all addresses
*/
function setListedBatch(bytes32 listId, address[] calldata accounts, bool status) external;
// ──────────────────────────────────────────────
// Ownership management (2-step transfer)
// ──────────────────────────────────────────────
/**
* @notice Initiates ownership transfer for a list. Current owner only.
* The new owner must call `acceptListOwnership` to complete the transfer.
* @param listId The list to transfer
* @param newOwner The address to transfer ownership to
*/
function transferListOwnership(bytes32 listId, address newOwner) external;
/**
* @notice Completes ownership transfer by accepting as the pending owner.
* @param listId The list to accept ownership of
*/
function acceptListOwnership(bytes32 listId) external;
/**
* @notice Cancels a pending ownership transfer. Current owner only.
* @param listId The list to cancel transfer for
*/
function cancelListOwnershipTransfer(bytes32 listId) external;
}
src/allowed/Constants.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;
// ──────────────────────────────────────────────
// Custom errors — Allowed registry only
// ──────────────────────────────────────────────
/// @dev List with this ID is already registered.
error ListAlreadyRegistered(bytes32 listId);
/// @dev List with this ID is not registered.
error ListNotRegistered(bytes32 listId);
/// @dev Caller is not the owner of this list.
error OnlyListOwner(bytes32 listId, address caller);
/// @dev List owner address is zero.
error ListOwnerIsZeroAddress();
/// @dev Invalid owner address for ownership transfer.
error OwnableInvalidOwner(address owner);
Compiler Settings
{"viaIR":true,"remappings":["forge-std/=lib/forge-std/src/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/"],"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode"]}},"optimizer":{"runs":200,"enabled":true},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"libraries":{},"evmVersion":"cancun"}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[]},{"type":"error","name":"ListAlreadyRegistered","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"}]},{"type":"error","name":"ListNotRegistered","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"}]},{"type":"error","name":"ListOwnerIsZeroAddress","inputs":[]},{"type":"error","name":"OnlyListOwner","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"},{"type":"address","name":"caller","internalType":"address"}]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"type":"address","name":"owner","internalType":"address"}]},{"type":"event","name":"ListOwnershipTransferStarted","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32","indexed":true},{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"ListOwnershipTransferred","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32","indexed":true},{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"ListRegistered","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32","indexed":true},{"type":"address","name":"owner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"ListUpdated","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32","indexed":true},{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"bool","name":"status","internalType":"bool","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"acceptListOwnership","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"cancelListOwnershipTransfer","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"getListOwner","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"getPendingListOwner","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"isListed","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"registerList","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"},{"type":"address","name":"owner","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setListed","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"},{"type":"address","name":"account","internalType":"address"},{"type":"bool","name":"status","internalType":"bool"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setListedBatch","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"},{"type":"address[]","name":"accounts","internalType":"address[]"},{"type":"bool","name":"status","internalType":"bool"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferListOwnership","inputs":[{"type":"bytes32","name":"listId","internalType":"bytes32"},{"type":"address","name":"newOwner","internalType":"address"}]}]
Contract Creation Code
0x60808060405234610016576105b0908161001b8239f35b5f80fdfe60406080815260049081361015610014575f80fd5b5f3560e01c80630c55a58c1461048957806324879bdc1461045a57806329227260146103cb578063432451bb146103425780635cee8c69146102ff57806370bbc61c146102525780638d2cb9381461015a578063bcb907ff146100b15763c18a73211461007f575f80fd5b346100ad5760203660031901126100ad57602091355f526002825260018060a01b03815f2054169051908152f35b5f80fd5b50346100ad57806003193601126100ad578135906100cd6104c0565b5f8381526001602052829020546001600160a01b039190821661014357169283156101355750815f5260016020525f20826001600160601b0360a01b8254161790557faa7b418a1b880af41df3685bd1f32b509f3186ac4ca6ab95c4b922a30d0b2fd65f80a3005b90516339619eb760e01b8152fd5b8251634a99cccd60e11b8152808601859052602490fd5b50346100ad5760603660031901126100ad578135906024359167ffffffffffffffff938484116100ad57366023850112156100ad578301359384116100ad576024830192602436918660051b0101116100ad576101b56104d6565b936101bf8261051d565b841515935f5b8281106101ce57005b600190845f5260205f8152857fe06b67235de399be357719c701038d0e4b97839bb2add8576c835e684c63bd5a885f20858060a01b039081610219610214888c8c6104e5565b610509565b165f5284526102368c8b5f209060ff801983541691151516179055565b610244610214868a8a6104e5565b169289518b8152a3016101c5565b50346100ad5760203660031901126100ad5781355f81815260026020528290205490926001600160a01b0391821633036102e85750825f526001602052815f205416906002602052805f20906001600160601b0360a01b9182815416905560016020525f2090339082541617905533917fdd39129cb9b7dd599250295414a3358ff23bbdd042f94c752078660daea70fd15f80a4005b602490835190631e4fbdf760e01b82523390820152fd5b50346100ad57806003193601126100ad5760209161031b6104c0565b90355f525f8352815f209060018060a01b03165f52825260ff815f20541690519015158152f35b5090346100ad5760603660031901126100ad57357fe06b67235de399be357719c701038d0e4b97839bb2add8576c835e684c63bd5a60206103816104c0565b9361038a6104d6565b906103948561051d565b5f8581528084528181206001600160a01b0397909716808252968452819020805460ff191660ff84151516179055519015158152a3005b50346100ad57806003193601126100ad578135906103e76104c0565b6103f08361051d565b6001600160a01b03169283156104455750815f5260026020525f20826001600160601b0360a01b82541617905533907fc924d854d2ee1ca8704e17e607219243ce1340f8b88735ca9a8fa73d56e681e15f80a4005b5f6024925191631e4fbdf760e01b8352820152fd5b50346100ad5760203660031901126100ad57602091355f526001825260018060a01b03815f2054169051908152f35b5090346100ad5760203660031901126100ad57356104a68161051d565b5f90815260026020522080546001600160a01b0319169055005b602435906001600160a01b03821682036100ad57565b6044359081151582036100ad57565b91908110156104f55760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160a01b03811681036100ad5790565b5f818152600160205260409020546001600160a01b031680156105615733036105435750565b60449060405190630e7bb12160e01b82526004820152336024820152fd5b604051638b22cf9960e01b815260048101839052602490fdfea26469706673582212200b34c67dba21c6390122b83fd51bcd8672ca1a5072b749a5bdb8f925a0363d5864736f6c63430008180033
Deployed ByteCode
0x60406080815260049081361015610014575f80fd5b5f3560e01c80630c55a58c1461048957806324879bdc1461045a57806329227260146103cb578063432451bb146103425780635cee8c69146102ff57806370bbc61c146102525780638d2cb9381461015a578063bcb907ff146100b15763c18a73211461007f575f80fd5b346100ad5760203660031901126100ad57602091355f526002825260018060a01b03815f2054169051908152f35b5f80fd5b50346100ad57806003193601126100ad578135906100cd6104c0565b5f8381526001602052829020546001600160a01b039190821661014357169283156101355750815f5260016020525f20826001600160601b0360a01b8254161790557faa7b418a1b880af41df3685bd1f32b509f3186ac4ca6ab95c4b922a30d0b2fd65f80a3005b90516339619eb760e01b8152fd5b8251634a99cccd60e11b8152808601859052602490fd5b50346100ad5760603660031901126100ad578135906024359167ffffffffffffffff938484116100ad57366023850112156100ad578301359384116100ad576024830192602436918660051b0101116100ad576101b56104d6565b936101bf8261051d565b841515935f5b8281106101ce57005b600190845f5260205f8152857fe06b67235de399be357719c701038d0e4b97839bb2add8576c835e684c63bd5a885f20858060a01b039081610219610214888c8c6104e5565b610509565b165f5284526102368c8b5f209060ff801983541691151516179055565b610244610214868a8a6104e5565b169289518b8152a3016101c5565b50346100ad5760203660031901126100ad5781355f81815260026020528290205490926001600160a01b0391821633036102e85750825f526001602052815f205416906002602052805f20906001600160601b0360a01b9182815416905560016020525f2090339082541617905533917fdd39129cb9b7dd599250295414a3358ff23bbdd042f94c752078660daea70fd15f80a4005b602490835190631e4fbdf760e01b82523390820152fd5b50346100ad57806003193601126100ad5760209161031b6104c0565b90355f525f8352815f209060018060a01b03165f52825260ff815f20541690519015158152f35b5090346100ad5760603660031901126100ad57357fe06b67235de399be357719c701038d0e4b97839bb2add8576c835e684c63bd5a60206103816104c0565b9361038a6104d6565b906103948561051d565b5f8581528084528181206001600160a01b0397909716808252968452819020805460ff191660ff84151516179055519015158152a3005b50346100ad57806003193601126100ad578135906103e76104c0565b6103f08361051d565b6001600160a01b03169283156104455750815f5260026020525f20826001600160601b0360a01b82541617905533907fc924d854d2ee1ca8704e17e607219243ce1340f8b88735ca9a8fa73d56e681e15f80a4005b5f6024925191631e4fbdf760e01b8352820152fd5b50346100ad5760203660031901126100ad57602091355f526001825260018060a01b03815f2054169051908152f35b5090346100ad5760203660031901126100ad57356104a68161051d565b5f90815260026020522080546001600160a01b0319169055005b602435906001600160a01b03821682036100ad57565b6044359081151582036100ad57565b91908110156104f55760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160a01b03811681036100ad5790565b5f818152600160205260409020546001600160a01b031680156105615733036105435750565b60449060405190630e7bb12160e01b82526004820152336024820152fd5b604051638b22cf9960e01b815260048101839052602490fdfea26469706673582212200b34c67dba21c6390122b83fd51bcd8672ca1a5072b749a5bdb8f925a0363d5864736f6c63430008180033