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
}