# Supply & Investor Limits

Source: https://docs.settlemint.com/docs/compliance-security/compliance/supply-investor-limits
TokenSupplyLimit and InvestorCount modules for time-based and rolling supply caps with currency conversion, plus unique holder limits with per-country granularity.



Install TokenSupplyLimit when the asset terms set a ceiling on total issuance. Install InvestorCount when the policy limits the number of unique token holders, globally or per country. The two modules are independent and can run together on the same token.

## Where these modules apply [#where-these-modules-apply]

Each module covers different on-chain operations.

| Concern          | TokenSupplyLimit                          | InvestorCount                         |
| ---------------- | ----------------------------------------- | ------------------------------------- |
| Minting          | Enforces cap (lifetime / fixed / rolling) | Tracks new holders                    |
| Transfers        | Not checked                               | Tracks new holders                    |
| Burns            | Not checked                               | Decrements count                      |
| Forced transfers | Not checked                               | Tracks new holders after the transfer |

## TokenSupplyLimit [#tokensupplylimit]

TokenSupplyLimit enforces the maximum token supply. Enable `useBasePrice` when the cap is denominated in EUR or USD rather than token units.

### Interface (capabilities) [#interface-capabilities]

| Capability                | Who can call                 | Inputs                                             | On-chain effect                                                                     | Emits | Notes                                                                  |
| ------------------------- | ---------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------- | ----- | ---------------------------------------------------------------------- |
| `setModuleParameters`     | Token admin (via compliance) | `maxSupply`, `limitType`, `period`, `useBasePrice` | Stores supply-limit config; `validateParameters` reverts if `maxSupply = 0`         | None  | Calling with `maxSupply = 0` reverts; the cap must be a positive value |
| `canTransfer` (mint path) | Compliance engine            | Sender, recipient, amount                          | Checks cumulative supply against limit (converts via price claim if `useBasePrice`) | None  | Only enforced on mints (`from == address(0)`)                          |

### Supply limit types [#supply-limit-types]

| Type            | Behavior                               | Use case                                        |
| --------------- | -------------------------------------- | ----------------------------------------------- |
| LIFETIME        | Total cap across the token's lifetime  | MiCA EUR 8M asset-referenced token limit        |
| FIXED\_PERIOD   | Cap resets at the start of each period | Quarterly fundraising caps                      |
| ROLLING\_PERIOD | Sliding window of last N days          | Continuous monitoring (rolling 12-month limits) |

### Base currency conversion [#base-currency-conversion]

When an admin enables `useBasePrice`, the module converts token amounts using on-chain price claims before checking the supply limit. The conversion supports EUR- or USD-denominated caps on tokens priced in native units.

### Key invariants [#key-invariants]

* Supply limit applies to minting operations only. The module does not check transfers or burns.
* ROLLING\_PERIOD uses a sliding window; older mints fall off as time passes.
* FIXED\_PERIOD resets at the start of each new period, regardless of previous minting.
* Calling `setModuleParameters` with `maxSupply = 0` reverts; the cap must be a positive value.

### Operational signals [#operational-signals]

The module emits no events. Monitor for `ComplianceCheckFailed` revert errors when minting exceeds the configured limit. You can detect a rejected mint by inspecting failed transactions for this revert.

### Failure modes & edge cases [#failure-modes--edge-cases]

* Missing or stale price feed when `useBasePrice` is active: minting reverts until a valid price claim exists.
* ROLLING\_PERIOD window boundary: mints near the window edge may succeed or fail depending on when older mints age out.

***

## InvestorCount [#investorcount]

InvestorCount restricts the number of unique token holders. The `topicFilter` determines which investors count toward the limit. Identity verification handles blocking; InvestorCount only tracks the headcount.

### Interface (capabilities) [#interface-capabilities-1]

| Capability            | Who can call                 | Inputs                                                                            | On-chain effect                                                 | Emits | Notes                                                                      |
| --------------------- | ---------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------- | ----- | -------------------------------------------------------------------------- |
| `setModuleParameters` | Token admin (via compliance) | `maxInvestors`, `countryCodes[]`, `countryLimits[]`, `topicFilter`, `global` flag | Stores investor-count config                                    | None  | `topicFilter` uses the same RPN expression system as identity verification |
| `canTransfer`         | Compliance engine            | Sender, recipient, amount                                                         | Checks if adding recipient would exceed global or country limit | None  | Skips burns (`to == address(0)`); applies to mints and transfers           |

### How it works [#how-it-works]

| Investor state                        | Identity module result     | InvestorCount result            | Transfer outcome |
| ------------------------------------- | -------------------------- | ------------------------------- | ---------------- |
| No KYC/AML claims                     | Blocked by identity module | N/A (never reaches count check) | Transfer blocked |
| Has qualifying claims, count \< limit | Allowed                    | Counted                         | Transfer allowed |
| Has qualifying claims, count = limit  | Allowed                    | Blocked (over limit)            | Transfer blocked |

### topicFilter and limits [#topicfilter-and-limits]

The `topicFilter` is a claim expression (same [RPN system](/docs/compliance-security/compliance/identity-verification) as SMARTIdentityVerification) that determines which investors count toward the limit. InvestorCount can enforce a global limit across all investors. It also supports per-country limits for jurisdiction-specific restrictions (e.g., max 50 Singapore residents).

### Key invariants [#key-invariants-1]

* `canTransfer` skips burns (`to == address(0)`) but applies to both mints and transfers.
* The active tracker depends on the `global` flag. With `global` enabled, tokens using the same InvestorCount module instance share one investor tracker. Use separate module instances when your policies need isolated counts. Otherwise, counts are token-specific.
* InvestorCount checks per-country limits independently of the global limit; both must pass when configured.
* `topicFilter` determines who counts toward InvestorCount. Identity verification handles blocking.
* InvestorCount does not count addresses without a registered identity. Use identity verification when your policy must block unverified recipients.

### Operational signals [#operational-signals-1]

The module emits no events. Monitor for `ComplianceCheckFailed` revert errors when investor count exceeds the configured limit. If you see transfers failing unexpectedly, check whether the global or per-country limit has been reached.

### Failure modes & edge cases [#failure-modes--edge-cases-1]

* Forced transfers bypass the pre-transfer investor-limit check but still update holder tracking after the transfer. A forced transfer to a new holder can leave the current investor count above the configured limit. Later movements can bring the count back within policy.
* Missing country code in the identity registry: InvestorCount counts the investor globally when a global limit is configured, but not toward a per-country limit.

## See also [#see-also]

These pages cover related modules and complementary controls.

* [Compliance Overview](/docs/compliance-security/compliance): module architecture and regulatory templates
* [Capital Raise Limit](/docs/compliance-security/compliance/capital-raise-limit): gross fiat fundraising caps during minting
* [Identity Verification](/docs/compliance-security/compliance/identity-verification): `topicFilter` uses the same RPN expression system
* [Transfer Approval](/docs/compliance-security/compliance/transfer-approval): pre-approval controls that complement investor count limits
* [Supply Cap & Collateral](/docs/compliance-security/compliance/supply-cap-collateral): hard circulating supply caps
