Skip to main content

Compliance modules

Reference for what each compliance module permits or refuses, its parameters and units, and where you configure it.

Nineteen compliance modules enforce the platform's transfer and mint rules and track burns. This page gives one entry per module: what it refuses, the parameters and units that decide it, and how its refusal reads. For installation, Global controls covers the organisation-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. A recipient on the compliance bypass list skips the modules that judge a recipient on a second-generation engine and every module on a first-generation one. A custodian forced transfer and a wallet recovery skip every module, and the token's Governance role can turn off or replace its rules, so review who holds Custodian, Identity manager and Governance.

Rules reach a token four ways:

  • A global control applies to every bound token of the organisation's system from “Global controls”.
  • A policy template's controls become a token's own rules when an asset is issued from that template.
  • The compliance steps in Issue asset let the issuer fill in the values the policy leaves open.
  • A holder of the token's Governance role adds, edits or turns off rules afterwards on the token's “Compliance” tab.

A token-level rule adds to any global control that already covers the same ground and cannot switch it off. See compliance policy for how the configuration surfaces and the underlying registry relate.

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. Errors and the API call the first generation V1 and the second V2. The entries below describe the current generation. A version note calls out where the older one behaves differently.

Keep stateful rules counting

Wallet count, Identity count, token supply limit, issuance volume limit, capital raise limit, time lock and transfer approval keep state in post-movement hooks. This holds in both generations. Transfer approval consumes approvals after a transfer.

Disabling one of these rules stops its state updates. Re-enabling it does not replay missed movements: counts, quotas, locks or remaining approvals may no longer reflect what happened while it was disabled.

New scopes default to ACCOUNTING_ONLY (“Track transfers outside this scope”). Movements outside the scope skip validation but still update the active rule's state. Clearing that option explicitly selects SKIP_ALL, which skips both validation and accounting outside the scope. The Console warns when you clear that option on a stateful rule. On a token or in global controls, “Turn off” on a rule asks only for wallet verification, which says the rule “stops checking transfers” but not that its state stops updating. Before you turn off a rule, check whether it is one of the stateful rules this section names. “Disable control…” on a first-generation token's control shows that state warning first.

To suspend a restriction while keeping state current, keep the rule active and use an ACCOUNTING_ONLY scope that excludes the movements you want to allow, or configure it to enforce nothing. For Wallet count and Identity count, set the maximum to 0 and remove every country cap. API clients should likewise keep stateful bindings active and set executionMode to ACCOUNTING_ONLY for out-of-scope accounting. Disabling a binding stops all its hooks.

Where a token's compliance rules come from.Four sources feed a token's compliance evaluation. Global controls apply platform-wide and reach every bound token. At issuance, a policy template's controls become the token's rules, with Issue asset's compliance steps filling in the values the policy leaves open. The token's Compliance tab adds or edits rules after issuance. Together those three form the token rules. When a transfer, mint, or burn executes, every enabled rule checks the operation. If all rules pass, the operation executes. If one rule refuses, the operation fails and names the rule.Where a token's compliance rules come from.Four sources feed a token's compliance evaluation. Global controls apply platform-wide and reach every bound token. At issuance, a policy template's controls become the token's rules, with Issue asset's compliance steps filling in the values the policy leaves open. The token's Compliance tab adds or edits rules after issuance. Together those three form the token rules. When a transfer, mint, or burn executes, every enabled rule checks the operation. If all rules pass, the operation executes. If one rule refuses, the operation fails and names the rule.

Active rules evaluate movements within their scope, and a failure reverts the transaction.

Check a change before you confirm it

When you add or remove a rule on a token's Compliance tab, the Console asks the platform whether it would accept the change before it asks for your PIN. A refusal shows in the verification dialog with its reason and fix, for example that the token already has a rule of that type or that the rule type is not registered in the system. The confirm button stays disabled and the reason repeats as text, so you learn it before the PIN. The check stores nothing and signs nothing. The platform checks again when you confirm, because the token can change in between. If the check cannot answer, the dialog does not block you and the write returns its own refusal.

Choose the module by what it keys on

The Console's category filter groups the catalogue 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 claims
“Restrictions”
Country allow list, Country block list, Block addresses
Where a party is registered, or the raw wallet address
“Transfer controls”
Transfer approval, Own custody gateway
A named authority approving first, or the holder's own custody gateway
“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, Lock-up
How long tokens have been held, or whether an unlock date has passed
“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 limit the number of holders rather than units, and minimum holding constrains each position's size rather than the total.

Configure modules while issuing an asset

When an asset is issued from an instrument template with a policy, each module the policy marks “Make configurable in Issue asset” gets its own step under “Compliance details” in Issue asset. A control the policy requires without configuring it gets a step too. A module the policy fixes for issuers gets no step, and its policy values are installed as they are.

Each step carries the module's name and its parameters, described in each module's entry on this page, pre-filled with the policy's values. A module the policy applies more than once gets one step per instance, marked “Inherited from template (N rules)” and “Rule 1 of N”. The “Applies to” line under the form shows each rule's scope, which comes from the policy and cannot be changed during issuance. Each step has its own address, so a reload returns to the same module.

The steps offer no way to add a module the policy does not bring. Only Write the policy can add a step, for a rule beyond the ones the policy brings. When the country rules contradict each other, the step and the summary show “Contradictory country rules”. A capital raise limit needs the system's price resolver add-on: the platform fills in the resolver and the price topic when it creates the token, and refuses the creation when the system has none.

Write the policy in words

A compliance officer can write the asset's investor policy as text and let the platform compile it into rules from this catalogue. “Write the policy” sits at the top of the rules on a token's “Compliance” tab and at the top of each compliance step in Issue asset. It shows while the Console assistant and its drafts capability are on for the organisation.

You write the policy in English or German, at most 2,000 characters, for example “EU professional investors only, no US persons, minimum holding 1,000 units.” “Read the policy” maps each part of the text to one module and reads every value, such as a country, a verification, an amount, a period or a date, from your own words. The assistant picks the module for each part and never writes a value. The read-back lists each clause, the rule it became and the words its values came from. A part no module expresses is listed with the reason: no module limits the share one holder may own, for example, and a lock-up date that has passed is not read. A policy in another language reads back that it cannot be read and asks for English or German.

On a token, “Check today's holders” runs the read-back rules against the 50 current holders with the largest balances and names the holders each country, verification or minimum holding rule would fail. A wallet with a valid escrow claim is exempt from the minimum holding, as it is on the installed rule. A holder count rule or a supply cap is compared with the current count or supply. A time lock or lock-up applies to future transfers only, so it fails no current holder. The check changes nothing, and it answers unavailable while the platform has not indexed the token yet.

Applying never installs a rule by itself. On a token, “Open N rules” opens the add-rule sheet for each rule in turn with its values filled in, and you confirm each one with wallet verification. Skip passes over one rule and Stop ends the run. In Issue asset, “Fill N rules into the design” writes the values into the compliance steps, and nothing deploys until you create the asset. The platform stores the policy text with the asset and links each rule to its words, and the “Compliance” tab shows them under “Policy” as the record of where each rule came from. A rule shows “Not added yet. Save the rule to apply it.” under its words until an active rule with the same values is installed on the token. A rule of the same kind with other values does not count. The Platform API offers the policy reading, the stored policy and the holder check as their own routes.

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 to refuse wallets your sanctions process names, or compromised wallets. You maintain the list, and DALP does not supply or update one. A wallet restriction freezes a wallet only on the tokens it holds when you run it, so add the address here to cover tokens the wallet acquires later.

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 5,000,000 in base currency per 365 days. Whether a cap satisfies a regulator's limit is for your legal team to decide. 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 add-on on the system. You set only the limit, and the platform fills in the resolver and the price topic. Without the add-on, asset creation fails and adding the rule is refused with DALP-199482. The “Rules” table reads the rule back in the units you set, for example “At most 5,000,000 in base currency raised per 365 days”.

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 claim is a signed attestation on one identity fact, such as KYC status or accreditation, and a trusted issuer 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. At most 3,650.
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. An address you remove here, or every address when you disable or remove the module, stops counting as trusted unless the registry trusts it on its own.

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.

Use this list for sanctions. You enumerate the identities, and the holder of a listed identity has no key to edit the list. A wallet recovery gives the holder a new identity, so add that identity to the list after any recovery of a listed holder. A rule that excludes holders of a sanctions claim does not give that protection, because a holder can remove a claim from their own identity.

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 module editor and in global controls, the Console does not save an empty expression, so an existing module rule cannot be cleared to admit everyone. The token's Holder rule editor does accept an empty rule, which leaves only the registration floor. When it refuses, the failure is a dedicated recipient-not-verified error, DALP-4266, which does not name the claims the receiver is missing.

Excluding holders of a claim

A NOT branch in the expression excludes the holders of a claim topic, as in “verified and not sanctioned”. A holder manages their own identity, so a holder who can delete that claim passes the rule meant to catch them. The platform therefore checks every save that puts a claim topic under NOT, in an identity verification rule on a token or in global controls, and in a token's compliance expression. The topic's “Use in exclusions” setting decides the result, and Verification topics says how to set it.

  • A topic set to “Not allowed” refuses the save, and the message names the topic.
  • A topic set to “Allowed with acknowledgement” refuses the save until the request carries holderRemovableNot with accepted set to true, sent from a session that is not impersonated. A topic with no stored setting is graded the same way.
  • A topic set to “Allowed” saves with no extra step.

The API answers a refused save with 422. The Holder rule editor on a token's Compliance tab shows the reason and a checkbox that sends the acknowledgement with the next save. The identity verification module editor and global controls have no such control, so a save there that excludes a topic set to “Allowed with acknowledgement”, or a topic with no stored setting, is refused until you narrow the rule. The expression builder marks a topic that needs the acknowledgement with a “Holder-removable” badge and a refused topic with “Cannot be used to exclude”. A refused save of a token's compliance expression names three alternatives a holder cannot defeat: an identity block list, a country block list, or a positive claim for the group you admit. A holder who deletes a positive claim loses access instead of gaining it.

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, and 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.

Lifetime counting counts only the identities that receive the token after the rule is added, so a cap set on a token that already has holders leaves those holders out of the count. The Console shows the current holder count and how to set the cap for a total limit, as it does for wallet count. Current-holder counting installs only on a token that has no holders yet.

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.

A receiver on the compliance bypass list takes no slot, even when it carries an identity and a country. The bypass list holds platform contracts such as XvP settlements and token sales, so anyone who can create one of them cannot use it to fill the cap. A custodian forced transfer can exceed the cap and still counts a receiver that is not on the bypass list. A wallet counted before it joined the bypass list keeps its slot until it releases the usual way, so under lifetime counting it stays counted. The rule runs only on a second-generation engine, because it asks the engine which receivers are on the bypass list.

When a current-holder cap is full, a holder cannot move a whole position to an identity that holds none of the token yet, even though the count would not grow: the rule checks the transfer before the sender leaves. A wallet of the holder's own identity still receives it. 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). This module belongs to the first generation: a first-generation token or policy template takes it, and so do global controls, but a second-generation token refuses it.

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: 1 to 36,500, or 1 to 730 for a rolling window. Locked once the module is enabled.
Rolling window
Rolling window slides day by day, and a fixed window resets capacity at the end of each period. Locked once the module is enabled.

Lock-up

Blocks transfers until an unlock date, in two forms. The class lock covers the whole token: before its unlock date, the module refuses every transfer, whoever sends it. The holder lock covers one holder: it sets an amount and an unlock date, and before that date the module refuses a transfer that would leave the sending wallet with less than the locked amount. A holder with 1,000 tokens and 600 locked can move 400. A transfer of 401 is refused. Both locks lift at their date with no operator action. Forced transfers, wallet recovery and burns bypass both. This module exists in the second generation only.

The module never refuses a mint. It also never sees a burn, a redemption, a conversion that burns the principal, a custodian forced transfer, or a lost-wallet recovery, because the token does not consult compliance for them. An issuer who needs the lock to cover conversions keeps conversion disabled until the unlock date.

Parameter
Description
Class unlock date (UTC)
When the class lock ends, as a date and time in UTC. Leave it empty for no class lock. You can extend, shorten, or lift it later. The API takes unlockTime in Unix seconds, with 0 for no class lock.

A class lock also stops the contracts that move tokens for the issuer. An issuer treasury, or a token-sale, XvP settlement, or trading-venue contract that must deliver or refund during a class lock needs a sender scope exemption. On a token with a burn or redemption fee, the fee collector needs a receiver scope exemption.

Holder locks

The token's governance role sets and clears holder locks from the token's holders list, with “Set lock-up” and “Clear lock-up” on a holder's row, or through the Platform API. “Set lock-up” shows while the token has an active lock-up rule, and “Clear lock-up” only for a holder with a stored lock. The unlock date must be later than the chain's latest block. A lock follows the holder's identity, not one wallet: every wallet of that identity keeps at least the locked amount. Moving tokens between wallets of the same identity is allowed, so consolidate first, then send the free part. Lost-wallet recovery registers the new wallet under a new identity, so the lock does not carry over. Set it again for the recovered wallet.

Request
Body
Effect
PUT /api/v2/tokens/{assetId}/compliance-modules/{instanceAddress}/holder-locks
Holder wallet, amount in base units, unlock time.
Sets or replaces the holder's lock.
DELETE /api/v2/tokens/{assetId}/compliance-modules/{instanceAddress}/holder-locks
Holder wallet.
Clears the holder's lock.

Tranche locks, which release a holding in stages, are not offered.

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, because the fee leg is far below any sensible minimum and would revert the whole transfer otherwise. Exempt only terminal, issuer-controlled wallets. A receiver on the system bypass list skips the receiver floor, while the sender's residual is still checked. 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.
Exempt claims
A wallet whose identity holds one of these claims skips its own minimum, while the other side of the transfer is still checked. A new rule starts with “Escrow account” selected, so escrows can hold and pay out amounts below the minimum. At most 5.

Own custody gateway

Keeps a custody receipt inside its holder's own custody gateway. A holder can send receipts only to their own gateway or that gateway's escrow. The gateway can book receipts into its escrow, return them to any wallet of the identity it serves, or burn them, and the escrow can pay them back to its gateway or to a wallet of that identity. A mint passes only to a wallet whose identity has a gateway at the configured factory. Every other movement is refused, and the rule also runs for a receiver on the system bypass list. Install it unscoped on custody receipts, not on general assets: a scoped rule skips the transfers outside its scope, and the custody gateway refuses to enable a coin whose receipt has no active rule bound to its factory. This module exists in the second generation only.

Parameter
Description
Custody gateway factory
Address of the custody gateway factory whose gateways this receipt is bound to. The Console fills in the organisation's custody gateway add-on.

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 and burns always pass. A transfer needs enough unlocked balance. A new rule on a token starts with escrow payouts exempted: its scope exempts senders holding the escrow claim and keeps tracking their movements. Remove that exemption from the rule's scope if escrows must respect the hold period.

Parameter
Description
Hold period (seconds)
Minimum holding period in seconds before tokens can be transferred: 1 second to 10 years. Locked once the module is enabled.

For a token with a V2 time lock, holder balances show how much of each holder's available balance the lock still holds back. The token's holder list and the investor's holdings return locked, transferable (what can move now) and nextUnlockAt (when more of the balance unlocks), and the Console shows them as Locked and Next unlock. A balance the holder received before the module was installed never unlocks, so it counts as locked.

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 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, and 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. Mints and burns pass. An approval binds the sender identity and the receiver identity, and the approval mode decides how its amount is enforced. Only an identity named as an approval authority can approve or revoke, and an approval stops counting if its approver is later removed from the authorities.

Parameter
Description
Approval authorities
Identity addresses allowed to grant approvals. At least one is required.
Approval expiry (seconds)
How long an approval stays valid: a whole number of seconds from 1 to 31,536,000 (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, and binds each approval to an exact amount. V2 replaces the one-time-use switch with the approval mode above. It also drops the claim-based exemption in favour 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. An approval authority sets that exemption with a direct call to the rule's contract. The Console and the Platform API do not offer it.

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. A receiver on the compliance bypass list is never counted, as for identity count.

Parameter
Description
Counting policy
Current holders, the default, or lifetime. Chosen at installation, and 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.

Lifetime counting counts only the wallets that receive the token after the rule is added. The holders a token already has are not counted until they next receive it, so a cap of 3 on a token with 3 holders still admits 3 more wallets. To keep such a token at N holders or fewer, set the cap to N minus its current holders. The Console shows the current holder count and this advice before you confirm, and the rule reads “At most 3 new wallets” instead of “At most 3 wallets”. Current-holder counting installs only on a token that has no holders yet, so on a token with holders the Console disables it and starts on lifetime.

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 contract error 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 “Transfer pre-check” tab 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 DALP-4036 when no approval exists, DALP-4035 when an existing approval's validity window has passed, and DALP-4034 when the approval was limited to a single use that has been spent. It fails with DALP-4423 for a transfer above the amount an approval has left, DALP-4118 when the sender or receiver has no registered identity, and DALP-1093 when the approval's approver is no longer an approval authority. Identity verification fails with a recipient-not-verified error, DALP-4266, which does not name 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
Lock-up
COMPLIANCE_CHECK_REASON_LOCKED_UNTIL
Minimum holding
COMPLIANCE_CHECK_REASON_RECIPIENT_BELOW_MINIMUM_HOLDING
Minimum holding
COMPLIANCE_CHECK_REASON_SENDER_BELOW_MINIMUM_HOLDING
Own custody gateway
COMPLIANCE_CHECK_REASON_NOT_OWN_CUSTODY_GATEWAY

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
The rule holds no price resolver address
COMPLIANCE_CHECK_REASON_NO_PRICE_RESOLVER
The resolver returns a zero or negative price
COMPLIANCE_CHECK_REASON_NO_VALID_PRICE
The resolver's price is stale
COMPLIANCE_CHECK_REASON_STALE_PRICE

A price feed the resolver cannot find fails with the price resolver's own error, which carries no reason code.

Token supply limit stays on the plain-text V1 error, because 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, open the token's “Transfer pre-check” tab. It shows for users who can freeze addresses on the asset and for users who hold System manager, Identity manager, Verification issuer or Compliance manager. Enter a “Sender”, a “Recipient” and an “Amount”, then select “Run check”. The tab runs the same compliance evaluation against that proposed transfer without submitting anything, and groups the blockers it finds by who can resolve them. The blocker detail follows the roles of the wallet that signs your actions. Use the check to confirm the condition has changed.

Troubleshooting

A module you expect is not offered

The Console filters the catalogue 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, the time lock's hold period, the transfer approval 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.

Adding the capital raise limit, or creating an asset with it, fails

The module converts mint amounts to fiat through the price resolver addon. While the system lacks that addon, asset creation fails and adding the rule to a token or as a global control is refused with DALP-199482. Install the addon first, then try again.

A refusal names a rule no token-level module explains

Check the rows marked “Global” in the “Rules” table of the token's “Compliance” tab. Global controls apply to the token automatically, refuse operations the same way its own rules do, and show “Set in Compliance › Global controls” in the Setting column: change them in global controls, 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. A scope edit works the same way: it decides which later movements the rule checks and does not revisit existing holders.

Was this page helpful?