Compliance modules
Reference for what each compliance module permits or refuses, its parameters and units, and where you configure it.
Seventeen compliance modules enforce the platform's transfer, mint, and burn rules, and this page is their catalog: one entry per module, stating what it refuses, the parameters and units that decide it, and how its refusal reads. Reach for it when you already know a module by name, perhaps because it sits on a token's "Compliance" tab or a failed operation named it. It does not teach the installation workflow: Global controls covers the platform-wide surface and Policy templates covers the reusable token-scoped one, and the Platform API manages the same modules programmatically.
A compliance module is one rule bound to a token. Ordinary transfers and mints evaluate active rules within scope. Burns notify stateful modules after the supply change rather than running the same recipient checks. A reverting hook still reverts the transaction. Recipient bypasses and privileged recovery paths need separate review. Rules reach a token four ways. A global control applies platform-wide from "Global controls". A policy template's controls become a token's own rules when an asset is issued from that template. The asset designer's compliance step sets them during issuance, and the token's "Compliance" tab adds or edits them afterwards. A token-level rule adds to any global control that already covers the same ground; it never replaces the global control. See compliance policyA Compliance Policy lets a compliance team write eligibility and holding rules once, while a platform floor reaches every current-factory token.Read the concept for how the configuration surfaces and the underlying registry relate.
Where a module runs in the second generation, the Console marks it with a "V2" badge; a first-generation module carries no badge of its own. A second-generation module is installed as its own instance per token, refuses with a machine-readable reason code, and carries its configuration on one or more rules, each with a scope that decides which senders and receivers it checks. A DALP asset is a second-generation token and takes only second-generation modules: the Platform API refuses a first-generation module on one, when the asset is created and when a module is installed later, and names the module that replaces it, for example collateral-v2 for collateral. Other asset types keep accepting first-generation modules. The entries below describe the current generation; a version note calls out where the older one behaves differently.
Active rules evaluate movements within their scope. A failure reverts the transaction; simulation can identify blockers when the transaction returns only a generic rejection.
Choose the module by what it keys on
The Console's category filter groups the catalog by what each rule reads before it decides, and that is also how to choose one. Start from the fact your requirement keys on, then pick the module in that row.
| Console category | Modules | The rule keys on |
|---|---|---|
| "Eligibility" | Identity verification, Identity allow list, Identity block list, Claim expiry | Who the receiver is: their registered identity and its claimsAn On-Chain Identity lets a subject's verification serve every asset that accepts it, so a screened wallet skips re-onboarding for the next regulated asset.Read the concept |
| "Restrictions" | Country allow list, Country block list, Block addresses | Where a party is registered, or the raw wallet address |
| "Transfer controls" | Transfer approval | A named authority approving first |
| "Issuance & supply" | Supply cap, Token supply limit, Capital raise limit, Wallet count, Identity count, Issuance volume limit, Minimum holding | How much exists: supply, issuance, raised capital, holder count, position size |
| "Time-based rules" | Time lock | How long tokens have been held |
| "Settlement & collateral" | Collateral requirement | Proof of collateral behind the supply |
Within the eligibility row, the choice is population shape. Identity verification admits anyone whose identity carries the right claims, so it fits an open population qualified by credential. The identity allow list admits only a closed set you enumerate, which fits a private placement. The identity block list excludes named identities from an otherwise open population, and claim expiry keeps an admitted population's credentials current over time.
Within the supply row, the choice is what the limit counts. The supply cap is a hard ceiling on units in existence. The issuance volume limit is a quota on units issued per window, and the capital raise limit is a quota on fiat value raised per window. Wallet count and identity count count holders rather than units, and minimum holding constrains each position's size rather than the total.
Block addresses
Refuses an ordinary transfer or mint when a checked sender or receiver address is on the module's block list. Burns do not run this pre-check. It reads the raw wallet address, so it works before any identity is registered; use it for sanctioned or compromised wallets.
| Parameter | Description |
|---|---|
| Blocked addresses | Wallet addresses the module refuses on either side of a transfer. |
Capital raise limit
Caps the gross fiat value raised through issuance in a fixed or rolling window, for example an MAS Singapore SGD 5M / 12-month limit or a MiCA Title II small-offer exemption. It converts each mint's token amount to fiat through the installed price resolver at mint time, and it fails closed if the price is missing, zero, negative, or stale. Burns do not release capacity, because the module measures gross capital raised, not net outstanding supply; for the same reason it cannot satisfy a requirement keyed to average outstanding value. Configuration is immutable once applied, so changing a parameter means removing the module and adding a new instance. It needs the price resolver addon installed on the system; asset creation fails without that addon.
| Parameter | Description |
|---|---|
| Maximum raised (fiat) | Fiat amount, in whole base-currency units, that may be raised in each window. |
| Period length (days) | Tracking window in days, 1 to 730. |
| Rolling window | A continuous rolling window (sum over the last N days) instead of discrete fixed periods. |
Claim expiry
Refuses a mint or a transfer when the receiver's claim on any required topic has expired, or was never issued. A claimAn On-Chain Identity lets a subject's verification serve every asset that accepts it, so a screened wallet skips re-onboarding for the next regulated asset.Read the concept is a signed attestation on one identity fact, such as KYC status or accreditation; a trusted issuerAn On-Chain Identity lets a subject's verification serve every asset that accepts it, so a screened wallet skips re-onboarding for the next regulated asset.Read the concept is the party the receiver's identity registry trusts to sign it. It checks every topic you select against the receiver's registered identity and accepts only a live claim from a trusted issuer. The sender side is never checked, so a holder whose claim lapsed can still exit the position. Stack it with identity verification: the verification expression decides who is eligible at all, and this module decides whether their credentials are still current. The Console warns when a required topic is only one alternative branch of the token's verification expression, because this rule then refuses receivers the expression would accept. This module exists in the second generation only.
| Parameter | Description |
|---|---|
| Topics that must stay current | Claim topics checked on every receiver. At least one, at most 16. |
| Grace period (days) | Days past the expiry date a claim is still accepted, to absorb a re-issuance backlog. 0 requires claims to be current. |
| Require every claim to carry an expiry date | Claims issued before expiry dates were recorded carry none and count as valid by default. Turn this on to reject them, once every holder has a re-issued claim. |
Collateral requirement
Refuses a mint unless the token's own on-chain identity carries a live collateral claim, from a trusted issuer, on the configured topic, for at least the required ratio of the resulting total supply. It reads the highest-value valid entry it finds; an expired one does not count.
| Parameter | Description |
|---|---|
| Proof topic ID | The claim topic that represents collateral proof. |
| Collateral ratio | Basis points; 10000 = 100%, 20000 = 200%. 0 disables the check. |
| Trusted issuers | Additional claim issuer addresses, added to the issuers the identity registry already trusts. |
Country allow list
Resolves the receiver's registered identity and country, then refuses the operation unless that country is on the allow list. Use it when the permitted jurisdictions are enumerable, such as an offering registered in a fixed set of countries.
| Parameter | Description |
|---|---|
| Allowed countries | ISO 3166-1 numeric country codes. |
Country block list
Resolves the receiver's registered identity and country, then refuses the operation if that country is on the block list. A receiver with no registered identity, or one whose country is not on file, is also refused; only a receiver with a known country outside the block list passes. Use it for sanctions and embargo lists, where everywhere is permitted except the named jurisdictions.
| Parameter | Description |
|---|---|
| Blocked countries | ISO 3166-1 numeric country codes. |
Identity allow list
Requires the receiver to hold a registered identity whose identity-contract address is on the allow list. That list holds identity-contract addresses, not wallet addresses.
A token holds one identity allow list, because a mint must pass every installed rule and a second list would not admit what the first refuses. On a token's Compliance tab, "Add rule" opens the list the token already has for editing and does not install another.
| Parameter | Description |
|---|---|
| Allowed identities | Identity-contract addresses. |
Identity block list
Refuses the operation only when the receiver holds a registered identity whose identity-contract address is on the block list. A receiver with no registered identity passes through, the opposite fail mode from the identity allow list.
| Parameter | Description |
|---|---|
| Blocked identities | Identity-contract addresses. |
Identity verification
Refuses a mint or a transfer unless the receiver's registered identity satisfies the token's verification expression. The expression combines verification topics, such as KYC, AML, or accreditation, with AND, OR, and NOT operators, and can group conditions; a claim counts only when a trusted issuer attested it. This is the standard eligibility gate for a regulated instrument: instead of enumerating approved identities one by one, it admits anyone whose identity carries the right claims. Its single parameter is the verification expression itself, built in the Console's expression builder. While you design an asset or a compliance template, you can leave the expression empty, which means no claim is required. On a live token's Compliance tab and in global controls, the Console will not save an empty expression, so an existing rule cannot be cleared to admit everyone. When it refuses, the failure is a dedicated recipient-not-verified error naming the claims the receiver is missing.
Identity count
Limits the number of distinct registered identities holding the token and refuses an operation that would add a new identity past a cap. Two wallets registered to one identity take one slot. A receiver without a registered identity is refused, because it has no identity to count.
| Parameter | Description |
|---|---|
| Counting policy | Current holders, the default, or lifetime. Chosen at installation; it cannot be changed afterwards. |
| Maximum investors | Total cap on counted identities. 0 means no total cap. |
| Country limits | Optional cap per country, applied to the country of the wallet the identity first entered with. |
Current-holder counting releases an identity once none of its wallets holds the token. Lifetime counting keeps an identity counted after it exits. Moving a whole position between two wallets of one identity keeps it counted under its original country, so the move cannot slip past a country cap.
When a current-holder cap is full, a holder cannot move a whole position to a new wallet or identity, even though the count would not grow: the rule checks the transfer before the sender leaves. Transfer to an existing holder, or burn first.
A current-holder rule can only be installed on a token that has no holders yet, because the rule counts from its first mint. On a token with holders, choose lifetime counting. Global controls cannot use current-holder counting, since a platform-wide rule cannot confirm that every token it governs starts empty.
Current-holder counts are only as complete as the transfers the rule sees. A rule that is disabled and re-enabled, or a scope that skips accounting for transfers it does not check, misses the movements in between. Scope current-holder rules to track the operations they skip.
Issuance volume limit
Caps how many token units may be issued in a fixed or rolling window. Use it for dilution control, vesting schedules, tranche releases, or operational throttling. A burn releases capacity, attributed to the most recent issuance in the window first (LIFO).
| Parameter | Description |
|---|---|
| Maximum issuance per period | Token units that may be issued within one period. You can raise or tighten this later. |
| Period length (days) | Length of the issuance window in days, greater than 0. Locked once the module is enabled. |
| Rolling window | Rolling window slides day by day; a fixed window resets capacity at the end of each period. Locked once the module is enabled. |
Minimum holding
Keeps every non-zero position at or above a floor. It refuses a mint or a transfer that would leave the receiver holding less than the minimum, and refuses a transfer that would leave the sender with a residual balance below it. A full exit passes this module's position-floor check: a holder may go to zero, they may not sell down to a stub. Use it for board-lot rules, minimum subscription amounts, and cap-table hygiene. If the token charges transfer fees, exempt the fee collector's wallet; the fee leg is far below any sensible minimum and would revert the whole transfer otherwise. Exempt only terminal, issuer-controlled wallets. This module exists in the second generation only.
| Parameter | Description |
|---|---|
| Minimum holding | Smallest position any holder may keep after an operation, in whole token units. |
| Exempt wallets | Wallets this rule ignores on both sides of a transfer. At most 20. |
Supply cap
Refuses a mint that would push total token supply past the cap. It checks nothing else: this module does not reject transfers or burns, and the module keeps no history of its own, so a burn frees room under the cap immediately. Use it when the constraint is a hard ceiling on units in existence; use the issuance volume limit or the capital raise limit when the constraint is a quota per period.
| Parameter | Description |
|---|---|
| Maximum supply | The maximum total supply allowed, in whole token units. |
Time lock
Requires tokens to age for a minimum hold period before they can move. It tracks acquisitions in first-in-first-out order per holder, so the oldest tokens unlock first. Mints always pass; a transfer or a burn needs enough unlocked balance.
| Parameter | Description |
|---|---|
| Hold period (seconds) | Minimum holding period in seconds before tokens can be transferred. |
V1 and V2 differ here. V1 stored its configuration per hook call and let certain identities carry an exemption from the hold period, based on their claims. V2 stores the hold period once in the module itself and drops the exemption field entirely; a token-specific exemption now comes from the rule's own scope instead of the module's configuration.
Token supply limit
Caps total token supply against a lifetime, fixed-period, or rolling-period limit. Only mints count against the limit; a burn reduces tracked supply, never below zero. This module exists in the first generation only. For a new deployment, three narrower modules cover the same ground with one fixed rule each. The supply cap handles a hard ceiling, the issuance volume limit a periodic token-unit quota, and the capital raise limit a periodic fiat quota.
| Parameter | Description |
|---|---|
| Maximum supply | The maximum supply allowed. In whole token units, unless "Use base price" is on, in which case this is a base-currency amount instead. |
| Period length (days) | Tracking period in days. 0 means a lifetime cap; greater than 0 means a periodic cap. |
| Rolling window | Use a rolling window instead of fixed periods. Only applies when the period length is greater than 0. |
| Use base price | Convert token amounts to a base currency with price claims before comparing to the limit. |
| Global (issuer-wide) | Track supply across every token for the issuer, instead of per token. |
Transfer approval
Requires an approval from a designated authority before a transfer executes. Both the sender and the receiver need a registered identity.
| Parameter | Description |
|---|---|
| Approval authorities | Identity addresses allowed to grant approvals. At least one is required. |
| Approval expiry (seconds) | How long an approval stays valid, 1 to 31,536,000 seconds (365 days). |
| Approval mode | How an approved amount is enforced: an exact amount, up to once, or up to a total. Locked once the module is enabled. |
V1 and V2 differ here. The earlier generation has a one-time-use switch and lets certain identities carry an exemption from the approval requirement, based on their claims. It also skips mints and burns entirely, binding each approval to a specific sender identity, receiver identity, and amount. V2 replaces the one-time-use switch with the approval mode above. It also drops the claim-based exemption in favor of an exemption for a designated fee recipient, needed for fee-splitting transfers. If the token charges transfer fees, avoid exact amount: the fee leg's net amount never equals the approved value, so the transfer reverts. Use up to once or up to a total instead, and exempt the fee leg's destination address as the fee recipient.
Wallet count
Limits the number of distinct wallets holding the token and refuses an operation that would add a new wallet past a cap. Each wallet counts on its own, even when several belong to one identity; use identity count to count identities. A wallet does not need a registered identity to be counted.
| Parameter | Description |
|---|---|
| Counting policy | Current holders, the default, or lifetime. Chosen at installation; it cannot be changed afterwards. |
| Maximum investors | Total cap on counted wallets. 0 means no total cap. |
| Country limits | Optional cap per country, applied to the wallet's registered country when it was first counted. |
Wallet count is the current generation of the rule earlier releases called investor count, and an existing investor count rule becomes a wallet count rule without losing its count. Such a rule keeps lifetime counting. It cannot add country limits, because the wallets it counted before the upgrade carry no country; the Console explains this when you try, and the maximum keeps applying. Installation and completeness follow the same rules as identity count.
The first generation behaves differently. It tracks a global cap and per-country caps in the same configuration, filters which investors count with a claim expression, and drops an investor from the count once a balance reaches zero.
When a module refuses
A module refusal reverts the operation. Detailed errors identify the module; a generic transfer or mint rejection may need simulation to locate the blocker.
A V2 module fails with a compliance error, DALP-1120, that carries the refusing module instance, its type, a machine-readable reason code, and a human-readable reason. A V1 module fails with a compliance error, DALP-1009, that carries the plain-text reason alone, so you read the text to find the rule; V1 does not separate the module's identity from its message the way V2 does. The error code reference documents both error envelopes in full.
A plain transfer or mint can instead fail before any module's detail is available: DALP-1096 for a transfer, DALP-1110 for a mint. Both carry no reason code and name no module, whether the receiver lacks a verified identity or a compliance rule blocked the operation. Use the "Pre-check" tab, described below, to find the specific rule before you retry.
Two modules refuse through their own dedicated errors rather than a reason code. Transfer approval fails with one error when no approval exists yet, another when an existing approval's validity window has passed, and a third when the approval was limited to a single use that has already been spent. Identity verification fails with a recipient-not-verified error naming the claims the receiver is missing.
Most reason codes map to exactly one module; the count modules share theirs.
| Module | Reason code |
|---|---|
| Block addresses | COMPLIANCE_CHECK_REASON_ADDRESS_BLOCKED |
| Claim expiry | COMPLIANCE_CHECK_REASON_CLAIM_EXPIRED |
| Claim expiry | COMPLIANCE_CHECK_REASON_CLAIM_MISSING |
| Country block list | COMPLIANCE_CHECK_REASON_COUNTRY_BLOCKED |
| Country allow list | COMPLIANCE_CHECK_REASON_COUNTRY_NOT_ALLOWED |
| Issuance volume limit | COMPLIANCE_CHECK_REASON_EXCEEDS_QUOTA |
| Capital raise limit | COMPLIANCE_CHECK_REASON_EXCEEDS_RAISE_LIMIT |
| Supply cap | COMPLIANCE_CHECK_REASON_EXCEEDS_SUPPLY_CAP |
| Wallet count | COMPLIANCE_CHECK_REASON_EXCEEDS_COUNTRY_LIMIT |
| Identity count | COMPLIANCE_CHECK_REASON_EXCEEDS_COUNTRY_LIMIT |
| Wallet count | COMPLIANCE_CHECK_REASON_EXCEEDS_TOTAL_LIMIT |
| Identity count | COMPLIANCE_CHECK_REASON_EXCEEDS_TOTAL_LIMIT |
| Identity block list | COMPLIANCE_CHECK_REASON_IDENTITY_BLOCKED |
| Identity allow list | COMPLIANCE_CHECK_REASON_IDENTITY_NOT_ALLOWED |
| Collateral requirement | COMPLIANCE_CHECK_REASON_INSUFFICIENT_COLLATERAL |
| Time lock | COMPLIANCE_CHECK_REASON_INSUFFICIENT_UNLOCKED |
| Minimum holding | COMPLIANCE_CHECK_REASON_RECIPIENT_BELOW_MINIMUM_HOLDING |
| Minimum holding | COMPLIANCE_CHECK_REASON_SENDER_BELOW_MINIMUM_HOLDING |
One reason code is shared by four modules, for a receiver with no registered identity.
| Module | Reason code |
|---|---|
| Country allow list, Country block list, Identity allow list, Identity count | COMPLIANCE_CHECK_REASON_IDENTITY_UNKNOWN |
Capital raise limit fails closed on a bad price too, with its own code per cause.
| Cause | Reason code |
|---|---|
| No price resolver installed | COMPLIANCE_CHECK_REASON_NO_PRICE_RESOLVER |
| The installed resolver has no valid price | COMPLIANCE_CHECK_REASON_NO_VALID_PRICE |
| The installed resolver's price is stale | COMPLIANCE_CHECK_REASON_STALE_PRICE |
Token supply limit stays on the plain-text V1 error; it has no reason code of its own. Time lock can also fail with two internal bookkeeping codes, when its stored batches cannot be updated safely or a legacy token's batches are unbound.
| Cause | Reason code |
|---|---|
| The module's stored batches could not be updated safely during removal. | COMPLIANCE_CHECK_REASON_TOKEN_REMOVAL_FAILED |
| A legacy token's time-lock batches are not bound to this compliance engine. | COMPLIANCE_CHECK_REASON_UNSAFE_LEGACY_TOKEN |
Treat both as a platform condition to escalate, not a rule refusing a party.
A refusal reflects the rule and the on-chain state current at the moment the operation ran, not a transient fault. Retrying the same operation unchanged fails again for the same reason. Retry only after the condition behind the reason code changes. A list refusal clears when the country or identity is registered on, or removed from, the relevant list. An expired-claim refusal clears when the claim is re-issued. A limit refusal clears when the configuration is edited, the tracking window rolls over, or the amount is adjusted to clear a position floor. A burn frees room only under a module that credits burns back to its capacity; the capital raise limit never does, so a burn cannot clear that particular refusal.
Before retrying, the token's "Pre-check" tab runs the same compliance evaluation against a proposed sender, receiver, and amount without submitting anything, and reports the blockers it finds. Use it to confirm the condition has actually changed.
Troubleshooting
A module you expect is not offered
The Console filters the catalog to modules compatible with the surface's module-set generation and hides modules already enabled there. A second-generation module does not appear on a first-generation policy template, and a module the token already carries is not offered twice.
A configured field cannot be changed
Some fields lock once the module is enabled: the issuance volume limit's period length and window mode, and every parameter of the capital raise limit. The Console marks the locked field; to use a different value, remove the module and install a new instance. The new instance starts with no memory of what the old one had tracked.
Asset creation fails after adding the capital raise limit
The module converts mint amounts to fiat through the price resolver addon, and asset creation fails while the system lacks that addon. Install the addon first, then create the asset.
A refusal names a rule no token-level module explains
Check the "Inherited from global" section of the token's "Compliance" tab. Global controls apply to the token automatically, refuse operations the same way its own rules do, and can only be changed from "Platform settings", not from the token.
A country sits on both the allow list and the block list
No holder in that country can then satisfy both rules, so an ordinary transfer or mint to that receiver fails when both rules apply. If every allowed country is also blocked, no country satisfies both rules. Check for overlap between the country allow list and the country block list before deploying both on the same token.
Frequently asked questions
Does burning tokens free up capacity under a limit?
It depends on what the module counts. The supply cap reads live total supply, so a burn frees room immediately. Token supply limit reduces its tracked supply on a burn, and the issuance volume limit credits a burn against the most recent issuance in the window. The capital raise limit never credits burns, because it measures gross capital raised: once value has been raised in a window, redeeming tokens does not un-raise it.
Can one control apply different limits to different holders?
Yes, on second-generation modules. A V2 control carries one or more rules, and each rule has its own configuration and its own scope: senders and receivers included or excluded by claim topics and by countries. A per-country investor cap, for example, is a separately scoped rule beside the global one. A scope can also track operations it does not check, so reporting still sees transfers the rule deliberately skips.
What happens to existing holders when a stricter rule is enabled?
Nothing, until they transact. Applicable checks and hooks run during execution; enabling a rule does not claw back balances or revisit completed operations. A holder who no longer qualifies keeps their position but is refused as a receiver the next time tokens move toward them. The Console does warn that changing the scope on an active rule may trigger re-validation of existing holders, so treat scope edits with more care than parameter edits.
Compliance providers
Connect an external verification service to claim topics and control what pausing, revoking, and reactivating each connection changes.
Reporting
See which reporting duty needs attention next, review filing attempts, check the published version each duty files under, and see who performed an operation.