Custom Access Logic
Write custom smart contracts that implement IAccessControl to define who can trade or create markets.
OddMaki supports pluggable access control through the IAccessControl interface. Any contract that implements isAllowed(address) returns (bool) can serve as an access gate. Deploy one instance per gate — you can use different contracts for trading and creation.
Interface
interface IAccessControl {
function isAllowed(address user) external view returns (bool);
}The function receives the user's address and returns true for access or false for denial. If the call reverts, access is denied (the protocol uses try/catch). The venue operator always bypasses all access checks.
Unlike the pre-built contracts (which you can deploy via the SDK without writing Solidity), custom contracts give you full control over the logic.
Example: Token Gate
Gate access to holders of a specific ERC-20 token with a minimum balance:
contract CustomTokenGate is IAccessControl {
IERC20 public immutable token;
uint256 public immutable minBalance;
constructor(address _token, uint256 _minBalance) {
token = IERC20(_token);
minBalance = _minBalance;
}
function isAllowed(address user) external view override returns (bool) {
return token.balanceOf(user) >= minBalance;
}
}For simple ERC-20 gating, consider using client.accessControl.deployTokenGated() instead of deploying a custom contract.
Example: NFT Gate
Gate access to holders of a specific ERC-721 NFT:
contract CustomNFTGate is IAccessControl {
IERC721 public immutable nft;
constructor(address _nft) {
nft = IERC721(_nft);
}
function isAllowed(address user) external view override returns (bool) {
return nft.balanceOf(user) > 0;
}
}For simple NFT gating, consider using client.accessControl.deployNFTGated() instead.
Example: Whitelist
Maintain an allowlist of addresses managed by an admin:
contract CustomWhitelist is IAccessControl {
address public admin;
mapping(address => bool) public allowed;
constructor() {
admin = msg.sender;
}
function setAllowed(address user, bool status) external {
require(msg.sender == admin, "Only admin");
allowed[user] = status;
}
function isAllowed(address user) external view override returns (bool) {
return allowed[user];
}
}For simple whitelisting, consider using client.accessControl.deployWhitelist() instead — it includes batch add/remove and ownership transfer.
Example: Time-Window Gate
Gate access to a specific time window — logic that can't be achieved with the pre-built contracts:
contract TimeWindowGate is IAccessControl {
uint256 public immutable startTime;
uint256 public immutable endTime;
constructor(uint256 _startTime, uint256 _endTime) {
startTime = _startTime;
endTime = _endTime;
}
function isAllowed(address) external view override returns (bool) {
return block.timestamp >= startTime && block.timestamp <= endTime;
}
}Deploying and Connecting
- Deploy your access control contract to Base (using Foundry, Hardhat, etc.)
- Set it on your venue at creation or via update:
// At venue creation
const tx = await client.venue.createVenue({
// ...other params
tradingAccessControl: '0xYourCustomContract...',
creationAccessControl: '0xYourWhitelistContract...',
});
// Or update an existing venue
const tx = await client.venue.updateVenue({
venueId: 1n,
name: 'My Venue',
metadata: '',
tradingAccessControl: '0xYourCustomContract...',
creationAccessControl: '0xYourWhitelistContract...',
feeRecipient: '0xFeeRecipient...',
});
// Or set per-market for trading only
const tx = await client.accessControl.setMarketTradingAC({
marketId: 42n,
acContract: '0xYourCustomContract...',
});You can use different contracts for trading and market creation, or the same contract for both. Set either gate to the zero address (0x0000...0000) to make it permissionless.
What's Next
- Access Control → — overview and pre-built contracts
- SDK Reference → — full AccessControl module API
- Venue Admin → — update venue configuration