Skip to main content

FeeSchedule

Inherits: Ownable2Step, IFeeSchedule

Title: FeeSchedule

Author: amser

Tier-based fee registry for the amser protocol.

This contract manages fee rates independently of PaymentProcessor, enabling fee policy changes without upgrading the execution engine. Architecture:

  • Fee tiers define (protocolFeeBps, keeperFeeBps) pairs.
  • Modules are assigned to tiers by admins (DAO-ready via ownership transfer).
  • Unassigned modules (tier 0) fall back to global defaults.
  • PaymentProcessor calls resolveFees(module) once per execution. Tier 0 is reserved as the "no tier assigned" sentinel. It cannot be configured as a tier — modules on tier 0 always use the global defaults. Each FeeTier has a configured boolean that tracks whether it has ever been set via setFeeTier(). This replaces the previous (0,0) sentinel pattern and allows zero-fee tiers for promotional or partner merchants. Security:
  • Only the owner (admin / DAO) can create tiers and assign modules.
  • Protocol fee rates are bounded by MAX_PROTOCOL_FEE_BPS (30%).
  • Keeper fee rates are bounded: keeperFeeBps <= 10000.
  • Deactivating a tier causes all modules on that tier to fall back to defaults.
  • This contract holds no funds and has no transfer authority.
  • RECOMMENDATION: Deploy behind a TimelockController so that fee increases have a notice period (e.g. 24–48 h), giving merchants time to react. Gas Profile (per resolveFees call):
  • Module with tier: 2 SLOADs (~4200 gas cold, ~200 gas warm)
  • Module without tier: 1 SLOAD + 2 SLOADs (~6300 gas cold, ~300 gas warm)

Note: security-contact: security@amser.io

State Variables

defaultProtocolFeeBps

Global default protocol fee (basis points, 0-10000)

uint16 public defaultProtocolFeeBps

defaultKeeperFeeBps

Global default keeper fee (basis points of protocol fee, 0-10000)

uint16 public defaultKeeperFeeBps

_tiers

Tier definitions: tierId => fee rates

Tier 0 is reserved as "no tier assigned" and cannot be configured.

mapping(uint8 => FeeTier) private _tiers

moduleTier

Module tier assignments: module address => tierId

0 means no tier assigned (falls back to global defaults).

mapping(address => uint8) public moduleTier

MAX_PROTOCOL_FEE_BPS

Maximum protocol fee: 30% (3000 basis points).

Caps the protocol's cut to prevent governance attacks that divert 100% of merchant payments. Keeper fee is uncapped (it's a share of the already-capped protocol fee).

uint16 public constant MAX_PROTOCOL_FEE_BPS = 3000

MAX_BATCH_TIER_SIZE

Maximum number of modules in a single setModuleTierBatch call.

uint256 public constant MAX_BATCH_TIER_SIZE = 100

Functions

constructor

Deploys the FeeSchedule with global default fee rates.

constructor(uint16 _defaultProtocolFeeBps, uint16 _defaultKeeperFeeBps) Ownable(msg.sender);

Parameters

NameTypeDescription
_defaultProtocolFeeBpsuint16Default protocol fee (basis points, 0-10000)
_defaultKeeperFeeBpsuint16Default keeper fee share (basis points of protocol fee, 0-10000)

resolveFees

Resolves the effective fee rates for a module.

Called by PaymentProcessor once per execution. Resolution order:

  1. Check if module has a tier assigned (moduleTier != 0)
  2. If tier is active, return tier rates
  3. Otherwise, return global defaults
function resolveFees(address module)
external
view
override
returns (uint16 protocolFeeBps, uint16 keeperFeeBps);

Parameters

NameTypeDescription
moduleaddressAddress of the module being executed

Returns

NameTypeDescription
protocolFeeBpsuint16Effective protocol fee (basis points)
keeperFeeBpsuint16Effective keeper fee (basis points)

setDefaultFees

Updates the global default fee rates.

These rates apply to any module that has no tier assigned or whose tier has been deactivated.

function setDefaultFees(uint16 _protocolFeeBps, uint16 _keeperFeeBps) external onlyOwner;

Parameters

NameTypeDescription
_protocolFeeBpsuint16New default protocol fee (basis points, 0-10000)
_keeperFeeBpsuint16New default keeper fee (basis points, 0-10000)

setFeeTier

Creates or updates a fee tier.

Tier 0 is reserved and cannot be configured. Creating a tier that already exists will update its rates.

function setFeeTier(
uint8 tierId,
uint16 _protocolFeeBps,
uint16 _keeperFeeBps
)
external
onlyOwner;

Parameters

NameTypeDescription
tierIduint8Tier identifier (1-255)
_protocolFeeBpsuint16Protocol fee for this tier (basis points, 0-10000)
_keeperFeeBpsuint16Keeper fee for this tier (basis points, 0-10000)

deactivateFeeTier

Deactivates a fee tier.

All modules assigned to this tier will fall back to global defaults until the tier is reactivated. Module assignments are preserved.

function deactivateFeeTier(uint8 tierId) external onlyOwner;

Parameters

NameTypeDescription
tierIduint8Tier identifier (1-255)

activateFeeTier

Reactivates a previously deactivated fee tier.

Modules still assigned to this tier will resume using tier rates.

function activateFeeTier(uint8 tierId) external onlyOwner;

Parameters

NameTypeDescription
tierIduint8Tier identifier (1-255)

setModuleTier

Assigns a module to a fee tier.

Setting tierId to 0 removes the tier assignment (reverts to defaults). Non-zero tierId must reference an active tier.

function setModuleTier(address module, uint8 tierId) external onlyOwner;

Parameters

NameTypeDescription
moduleaddressAddress of the module
tierIduint8Tier identifier (0 = remove assignment, 1-255 = assign to tier)

setModuleTierBatch

Batch-assigns multiple modules to a fee tier.

Convenience function for DAO proposals that assign many modules at once.

function setModuleTierBatch(address[] calldata modules, uint8 tierId) external onlyOwner;

Parameters

NameTypeDescription
modulesaddress[]Array of module addresses
tierIduint8Tier identifier (0 = remove assignment, 1-255 = assign to tier)

getFeeTier

Returns the full configuration of a fee tier.

function getFeeTier(uint8 tierId)
external
view
returns (uint16 protocolFeeBps, uint16 keeperFeeBps, bool active, bool configured);

Parameters

NameTypeDescription
tierIduint8Tier identifier

Returns

NameTypeDescription
protocolFeeBpsuint16Protocol fee (basis points)
keeperFeeBpsuint16Keeper fee (basis points)
activeboolWhether the tier is currently active
configuredboolWhether the tier has ever been set via setFeeTier

getModuleFees

Returns the effective fee rates for a module (same as resolveFees).

Convenience function with explicit naming for dashboard/SDK use.

function getModuleFees(address module)
external
view
returns (uint16 protocolFeeBps, uint16 keeperFeeBps, uint8 tierId, bool isUsingDefault);

Parameters

NameTypeDescription
moduleaddressModule address

Returns

NameTypeDescription
protocolFeeBpsuint16Effective protocol fee (basis points)
keeperFeeBpsuint16Effective keeper fee (basis points)
tierIduint8The tier the module is on (0 = default)
isUsingDefaultboolWhether the module is using default rates

Events

DefaultFeesUpdated

event DefaultFeesUpdated(uint16 protocolFeeBps, uint16 keeperFeeBps);

FeeTierCreated

event FeeTierCreated(uint8 indexed tierId, uint16 protocolFeeBps, uint16 keeperFeeBps);

FeeTierUpdated

event FeeTierUpdated(uint8 indexed tierId, uint16 protocolFeeBps, uint16 keeperFeeBps);

FeeTierDeactivated

event FeeTierDeactivated(uint8 indexed tierId);

FeeTierActivated

event FeeTierActivated(uint8 indexed tierId);

ModuleTierAssigned

event ModuleTierAssigned(address indexed module, uint8 indexed tierId);

Errors

InvalidFees

error InvalidFees();

InvalidTier

error InvalidTier();

TierNotActive

error TierNotActive();

InvalidModule

error InvalidModule();

BatchTooLarge

error BatchTooLarge();

Structs

FeeTier

Fee rates for a specific tier.

Packed into a single 32-byte storage slot. protocolFeeBps (16 bits) + keeperFeeBps (16 bits) + active (1 bit) + configured (1 bit) = 34 bits. configured = false means "fall through to global default" in activateFeeTier.

struct FeeTier {
uint16 protocolFeeBps; // Protocol's cut of each payment (0-10000)
uint16 keeperFeeBps; // Keeper's share of protocol fee (0-10000)
bool active; // Whether this tier is currently active
bool configured; // Whether this tier has ever been set via setFeeTier
}