IPcl.contractPeriodicVolume
contractPeriodicVolume(
address contractAddress,
address user,
bytes calldata selector,
string calldata asset,
uint64 resetPeriodSeconds,
bool resolveAgentOwners
) external view returns (PeriodicVolume[] memory statuses) Returns every contract-scoped periodic-volume counter matching the requested (selector, asset, resetPeriodSeconds) tuple for user on contractAddress. A single reset period can back several distinct maxAmount limits (for example, a wallet-tier cap layered under a per-function cap), so callers must fold across the returned array rather than assume a single record. An empty array means no contract policy currently matches the tuple. Pass an empty selector to read counters registered without a function-selector scope; pass resolveAgentOwners = true to aggregate volume across every agent owned by user.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
contractAddress | address | ✓ | The contract whose ContractPolicyConfig carries the periodic-volume rule. |
user | address | ✓ | The account whose accumulated volume should be read. |
selector | bytes | ✓ | 4-byte function selector the policy is bound to, encoded as bytes. Pass empty bytes ("") to match policies that were registered without a selector scope ("applies to all calls"). |
asset | string | ✓ | Asset identifier as declared in the PeriodicVolumePolicy.tokens array. Normalized server-side. |
resetPeriodSeconds | uint64 | ✓ | The period length in seconds identifying which counter to read. Only entries whose registered period equals this value are returned. |
resolveAgentOwners | bool | ✓ | When true, volume is aggregated across every agent whose owner is user; when false, only user's own accumulator is read. |
Returns
PeriodicVolume[] Array of PeriodicVolume { uint256 amount; uint256 maxAmount; uint64 resetPeriodSeconds; uint64 resetAt; } — one entry per contract-scoped limit that matches the tuple. Empty array means no matching policy is registered.
Errors
| Code | Name | Description |
|---|---|---|
InvalidSelector | InvalidSelector | Reverts when selector is non-empty but is not exactly 4 bytes. Empty bytes are valid and mean "any call to this contract". |
Examples
Read every contract-scoped daily cap layered on a transfer function
The response can contain multiple caps against the same (asset, period) pair — for example a base per-user daily cap alongside a stricter cap that only applies to a specific selector — so iterate and evaluate all of them before quoting headroom to end users.
import { createPublicClient, http, toHex, keccak256, toBytes } from "viem";
const PCL = "0x1000000000000000000000000000000000000005" as const;
const pclAbi = [{
name: "contractPeriodicVolume", type: "function", stateMutability: "view",
inputs: [
{ name: "contractAddress", type: "address" },
{ name: "user", type: "address" },
{ name: "selector", type: "bytes" },
{ name: "asset", type: "string" },
{ name: "resetPeriodSeconds", type: "uint64" },
{ name: "resolveAgentOwners", type: "bool" },
],
outputs: [{
type: "tuple[]",
components: [
{ name: "amount", type: "uint256" },
{ name: "maxAmount", type: "uint256" },
{ name: "resetPeriodSeconds", type: "uint64" },
{ name: "resetAt", type: "uint64" },
],
}],
}] as const;
const client = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });
// 4-byte selector for transfer(address,uint256).
const transferSelector = toHex(keccak256(toBytes("transfer(address,uint256)")).slice(0, 4));
// TODO: replace with the real contract + user addresses before production.
const statuses = await client.readContract({
address: PCL,
abi: pclAbi,
functionName: "contractPeriodicVolume",
args: [
"0x8f3aC2b1D9e74C05a6B18Fe27dC4913E5A0f7b62",
"0x2d1e4A98F0Cc78bC3d5FA47a6E1B927dE04B85b1",
transferSelector,
"aokrw",
86_400n,
false,
],
});
for (const s of statuses) {
console.log(`used ${s.amount} / ${s.maxAmount} aokrw, resets at ${s.resetAt}`);
} Empty selector — counters registered without a function scope
The empty-bytes selector reads the "applies to all calls" bucket. Combined with resolveAgentOwners = true, this returns the ceiling that the owner of any downstream agent sees.
// A PolicySet registered with selector = "" applies to every call on the target.
// Pass empty bytes to read that counter class.
const statuses = await client.readContract({
address: PCL,
abi: pclAbi,
functionName: "contractPeriodicVolume",
args: [
"0x8f3aC2b1D9e74C05a6B18Fe27dC4913E5A0f7b62",
"0x2d1e4A98F0Cc78bC3d5FA47a6E1B927dE04B85b1",
"0x",
"aokrw",
86_400n,
true, // include agent owners
],
});