Daily transfer limits are a standard fraud and risk control in finance. Stateful aggregation policies enforce these limits at the wallet layer without a separate rate limiting system. Once a wallet reaches its rolling cap, Privy rejects signing requests until the window resets. This recipe walks through enforcing a 24-hour USDC transfer cap on a server wallet. The same pattern applies to user withdrawal limits, hot wallet circuit breakers, or per-account spend controls. This approach has two parts:Documentation Index
Fetch the complete documentation index at: https://docs.privy.io/llms.txt
Use this file to discover all available pages before exploring further.
- Aggregation: Tracks the running sum of USDC
transferamounts frometh_signTransactionrequests over a rolling time window - Policy: References the aggregation and rejects signing if the running total would exceed the cap
Create a spend-tracking aggregation
Create an aggregation that sums the Save the returned
amount field from ERC-20 transfer calldata over a rolling 24-hour window. Scope it to the USDC contract so only USDC transfers count toward the cap.Aggregation creation is via the REST API. The Node SDK aggregations interface does not yet expose a create method.aggregationId. The next step uses it in the policy condition.Each Privy app supports a maximum of 10 aggregations. If your app provisions many wallets,
consider using
group_by to partition a single aggregation by wallet address rather than creating
one aggregation per wallet.Create a policy with an aggregation cap
Create a policy with an Save
eth_signTransaction rule that allows USDC transfers only while the 24-hour rolling sum stays at or below the cap. Set field_source: 'reference' and prefix the aggregation ID with aggregation. to reference it.policy.id. The wallet creation step and the spending-cap update step both need it.Create a wallet with the policy
Create a server wallet and attach the policy using The policy is now active on the wallet. Each
policy_ids:eth_signTransaction request checks the rule before signing.Sign and broadcast a USDC transfer
Use The policy check is forward-looking: the engine checks whether the running total plus the current request amount would exceed the cap. For example, a wallet that has spent 90 USDC is blocked from a 15 USDC transfer, even though the cap is 100 USDC.
eth_signTransaction to sign the transaction. This is where Privy checks the policy and updates the aggregation. After signing, send the raw transaction via any RPC node.Common pitfalls
Aggregation conditions and policy conditions are separate. The aggregation’sconditions array controls what gets tracked. The policy rule’s conditions control what gets allowed or denied. If these diverge, the cap can reset without warning. For example, if the aggregation tracks USDC transfers to one contract but the policy rule covers all USDC transfers, spend to other contracts is excluded from the cap. Keep both condition sets in sync.
eth_sendTransaction spend is invisible to aggregations. Aggregations only track eth_signTransaction and eth_signUserOperation requests. If the wallet also handles eth_sendTransaction calls, that spend does not count toward the cap. This recipe uses eth_signTransaction throughout to track all outflow.
group_by extraction failures deny the request. When group_by is set and Privy cannot extract the grouping field from a transaction, the policy denies the request rather than falling back to a global bucket. This can happen when the calldata does not match the expected function signature or the ABI is absent. When the group key source is optional or variable, omit group_by and use per-wallet aggregations instead.
ABI mismatch silently passes. If the transaction calldata does not decode against the aggregation metric’s ABI, the extracted value defaults to 0. The transaction passes the policy check as if nothing was sent. An incorrect ABI means Privy never enforces the cap for those transactions. Verify ABI decoding before relying on the cap in production.
Reverted transactions still count. The aggregation updates when signed, not when confirmed on-chain. If a signed transaction reverts on-chain, the spend still counts. Gas failures and reverts do not cancel the aggregation increment. Plan for gas and slippage.
The rolling window is continuous, not clock-based. A 24-hour window means the last 86,400 seconds from the exact moment of the request. There is no midnight reset. A wallet that sends 100 USDC at 11:59 PM is blocked until 11:59 PM the following day, not until midnight.
Patch behavior
Privy applies policy rule updates in place. The policy ID does not change, and wallets do not need updates after a rule change. Privy recommends updating each rule with_updateRule rather than replacing the full policy with _update. Updating each rule avoids race conditions from concurrent changes. See updating policy rules for more detail.
If the policy has an owner_id, each update needs the owner’s signature. See authorization signatures for details.

