PERIODIC_VOLUME_POLICY
Cumulative transaction-volume limits per denom over a reset period (24h, 30d, etc.).
Tracks cumulative transaction volume per sender per denom over a configured reset period and rejects transactions that would push the running total above the limit. Distinct from VOLUME_POLICY, which checks each transaction independently. Used for daily / monthly transfer caps and Travel-Rule–style thresholds.
Solidity struct + ABI
From
IPcl.sol:struct UnitPeriodicVolumePolicy {
uint256 maxAmount; // maximum total amount per reset period
uint64 resetPeriodSeconds; // period length in seconds (e.g. 86400 for 24h)
}
struct PeriodicVolumePolicy {
string[] tokens; // denom names, e.g. "aokrw"
UnitPeriodicVolumePolicy[] limits; // parallel array — limits[i] applies to tokens[i]
} ABI tuple shorthand:
Encode for
(string[] tokens, (uint256 maxAmount, uint64 resetPeriodSeconds)[] limits).Encode for
PolicySet.policy:UnitPeriodicVolumePolicy[] memory limits = new UnitPeriodicVolumePolicy[](1);
limits[0] = UnitPeriodicVolumePolicy({
maxAmount: 1_000_000 ether, // 1,000,000 OKRW per period (in aokrw)
resetPeriodSeconds: 86_400 // 24h
});
string[] memory toks = new string[](1);
toks[0] = "aokrw";
PeriodicVolumePolicy memory pvp = PeriodicVolumePolicy({
tokens: toks,
limits: limits
});
bytes memory policyBytes = abi.encode(pvp); Evaluation
For each token transferred, PCL maintains a
PeriodicVolume accumulator per (scope, sender, denom):struct PeriodicVolume {
uint256 amount; // accumulated amount in the current period
uint256 maxAmount; // policy limit
uint64 resetPeriodSeconds; // period length in seconds
uint64 resetAt; // unix timestamp at which the current period ends
} Flow:
1. If
2. If
3. Otherwise admit and add
Accumulators are per-sender — sending to many different recipients does not split the counter. Read accumulators with
1. If
block.timestamp >= resetAt → reset amount = 0, advance resetAt.2. If
amount + value > maxAmount → reject (ExceededPeriodicVolume).3. Otherwise admit and add
value to amount.Accumulators are per-sender — sending to many different recipients does not split the counter. Read accumulators with
globalPeriodicVolume(user, asset) or contractPeriodicVolume(contract, user, selector, asset).ReasonCode on rejection
ExceededPeriodicVolume(uint256 maxLimit, uint256 value, uint256 resetAt) — wallet UX should show "daily/monthly limit reached; resets at <resetAt>". The resetAt field is a unix timestamp, render it in the user's locale.Typical usage
- Travel-Rule-style thresholds (e.g.
maxAmount: 1,000,000 OKRW per 24hfor un-attested users). - Anti-fraud / pace limits (e.g. cap a hot wallet at 10 M OKRW per 30d to catch compromise faster).
- Combined with
EAS_POLICY: the EAS-conditional variantOKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICYlets you have different limits for attested vs un-attested users — the more common production setup.