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

Type: 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
  ],
});
ESC
Type to search