Daml
Allowlist
The Registry App uses credential-based allowlists to control which parties are permitted to perform specific actions for an asset.
This enables issuers to enforce KYC, AML, regulatory, or business-specific requirements while keeping the rules fully configurable for each instrument.
Common use cases include:
- Restricting who may hold a regulated security.
- Restricting who may request minting or redemption of a stablecoin.
- Limiting participation to onboarded investors.
- Delegating compliance decisions to an external compliance provider.
How Allowlisting Works
Allowlists are implemented using credential contracts. The Instrument Configuration defines the credential requirements for each workflow. When a user performs an action, the Smart Contract model verifies that the required credentials are present when the transaction is executed. This verification happens atomically. Credential requirements can be configured independently for:- Holder requirements – Who may hold, receive, or transfer an asset.
- Mint/Burn requirements – Who may request minting or redemption.
Configuring an Allowlist
Each credential requirement specifies:- Credential Issuer – The party responsible for maintaining the allowlist.
- Claim Property – The permission being granted (for example
canHoldorcanMint). - Claim Value – The asset or permission scope (for example
USDXorBondX).
Managing the Allowlist
Adding a party to an allowlist is equivalent to issuing a credential with the required claims. The recommended approach is for the credential issuer to also hold the credential. This allows the allowlist to be maintained unilaterally without requiring the allowlisted party to separately accept or manage credential contracts.1
Create allowlist-entry credentials
- UI user
- API user
- In the Registry App, go to the Credential module.
- Click Offer new credential.
- Set the credential holder to Use same party so that the issuer also holds the credential.
- Set the subject to the party being added to the allowlist.
- Add the claim property and value required by the Instrument Configuration.
- Click Offer Free credential.
Before running the workflow scripts, create Now run the following script to create credentials for all configured subjects in one atomic transaction:The transaction response is saved to
source.sh and update its values for your environment:#!/usr/bin/env bash
## =================================================================================================
## Purpose: Configurations for this example, amend variables as needed.
## Script: source.sh
## =================================================================================================
# Credential issuer details
CREDENTIAL_ISSUER_TOKEN="<PASTE_CREDENTIAL_ISSUER_JWT_HERE>"
CREDENTIAL_ISSUER_PARTY_ID="registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
CREDENTIAL_ISSUER_USER_ID="registrar"
# DA operator details
OPERATOR_PARTY_ID="operator::1220a6f417751797b91ab08423236f8c0d3c53f1ec62ed1afd4602816390b68be8f0"
# Subjects to create an allowlist-entry credential for.
CREDENTIAL_SUBJECT_PARTY_IDS='[
"holder1::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
"holder2::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
"holder3::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
]'
# Description used for all created credentials
CREDENTIAL_DESCRIPTION="Allowlist-Entry Credential"
# Claims to attach to each created credential (JSON list).
# The scripts will set the `subject` field of each claim to the current subject party.
CREDENTIAL_CLAIMS_JSON='[
{"property":"IsHolderOf","value":"INST1"},
{"property":"IsHolderOf","value":"INST2"}
]'
# JSON API endpoint
# Example (local): HTTP_JSON_API="http://localhost:8001/api/json-api"
# Example (remote): HTTP_JSON_API="https://<your-host>/api/json-api"
HTTP_JSON_API="http://localhost:8001/api/json-api"
# Daml template IDs (package-name qualified)
CREDENTIAL_TEMPLATE="#utility-credential-v0:Utility.Credential.V0.Credential:Credential"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DATAFILE="${SCRIPT_DIR}/source.sh"
source "$DATAFILE"
# Validate inputs
if [[ -z "${CREDENTIAL_SUBJECT_PARTY_IDS:-}" ]]; then
echo "Error: CREDENTIAL_SUBJECT_PARTY_IDS is not set in source.sh"
exit 1
fi
if [[ -z "${CREDENTIAL_CLAIMS_JSON:-}" ]]; then
echo "Error: CREDENTIAL_CLAIMS_JSON is not set in source.sh"
exit 1
fi
if [[ -z "${OPERATOR_PARTY_ID:-}" ]]; then
echo "Error: OPERATOR_PARTY_ID is not set in source.sh"
exit 1
fi
echo "${CREDENTIAL_SUBJECT_PARTY_IDS}" | jq -e 'type == "array" and length > 0 and all(.[]; type == "string")' >/dev/null
echo "${CREDENTIAL_CLAIMS_JSON}" | jq -e 'type == "array" and length > 0 and all(.[]; has("property") and has("value"))' >/dev/null
# Random Credential ID prefix.
ID_PREFIX="allowlist-entry: $(od -An -N4 -tu4 < /dev/urandom | tr -d ' ')"
COMMANDS_JSON=$(jq -n \
--arg templateId "${CREDENTIAL_TEMPLATE}" \
--arg issuer "${CREDENTIAL_ISSUER_PARTY_ID}" \
--arg description "${CREDENTIAL_DESCRIPTION}" \
--arg holder "${CREDENTIAL_ISSUER_PARTY_ID}" \
--arg operator "${OPERATOR_PARTY_ID}" \
--arg idPrefix "${ID_PREFIX}-" \
--argjson subjects "${CREDENTIAL_SUBJECT_PARTY_IDS}" \
--argjson claimPairs "${CREDENTIAL_CLAIMS_JSON}" \
'[
($subjects | to_entries[]) as $entry
| ($entry.key + 1) as $n
| ($entry.value) as $subjectParty
| {
CreateCommand: {
templateId: $templateId,
createArguments: {
issuer: $issuer,
holder: $holder,
id: ($idPrefix + ($n | tostring)),
description: $description,
validFrom: null,
validUntil: null,
claims: (
$claimPairs
| map({subject: $subjectParty, property: .property, value: .value})
),
observers: { map: [[$operator, {}]] }
}
}
}
]')
RESULT=$(
curl -sS --fail-with-body \
--url "${HTTP_JSON_API}/v2/commands/submit-and-wait-for-transaction" \
--header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
--header "Content-Type: application/json" \
--request POST \
--data @- <<EOF
{
"commands": {
"commands": ${COMMANDS_JSON},
"workflowId": "",
"userId": "${CREDENTIAL_ISSUER_USER_ID}",
"commandId": "$(uuidgen | tr -d '\n')",
"deduplicationPeriod": {
"DeduplicationDuration": {
"value": { "seconds": 30, "nanos": 0 }
}
},
"actAs": [
"${CREDENTIAL_ISSUER_PARTY_ID}"
],
"readAs": [],
"submissionId": "$(uuidgen | tr -d '\n')",
"disclosedContracts": [],
"domainId": "",
"packageIdSelectionPreference": []
}
}
EOF
)
echo "--- Command response ---"
echo "$RESULT" | jq
OUTPUTFILE="${SCRIPT_DIR}/response-step-1.json"
echo "$RESULT" > "$OUTPUTFILE"
response-step-1.json.2
Review active allowlist entries
- UI user
- API user
In the Credential module, review the active credentials held by the credential issuer. Confirm that each allowlisted party appears as the subject and that the claim property and value match the Instrument Configuration.
Retrieve the current ledger end.Then, run the following:The matching active credentials are saved to
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DATAFILE="${SCRIPT_DIR}/source.sh"
source "$DATAFILE"
OFFSET=$(curl -sS --fail-with-body \
--url "${HTTP_JSON_API}/v2/state/ledger-end" \
--header "Accept: application/json" \
--header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}")
echo "$OFFSET" | jq
OUTPUTFILE="${SCRIPT_DIR}/response-step-2.json"
echo "$OFFSET" > "$OUTPUTFILE"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DATAFILE="${SCRIPT_DIR}/source.sh"
source "$DATAFILE"
# Validate expected claim pairs config (property/value)
if [[ -z "${CREDENTIAL_CLAIMS_JSON:-}" ]]; then
echo "Error: CREDENTIAL_CLAIMS_JSON is not set in source.sh" >&2
exit 1
fi
echo "${CREDENTIAL_CLAIMS_JSON}" | jq -e 'type == "array" and all(.[]; (has("property") and has("value")))' >/dev/null
# Get offset from previous step
if [[ -f "${SCRIPT_DIR}/response-step-2.json" ]]; then
JSONCONTENT=$(cat "${SCRIPT_DIR}/response-step-2.json")
OFFSET=$(echo "$JSONCONTENT" | jq -r ".offset")
else
echo "Error: response-step-2.json not found"
exit 1
fi
RESULT=$(curl -sS --fail-with-body \
--url "${HTTP_JSON_API}/v2/state/active-contracts" \
--header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
--header "Content-Type: application/json" \
--request POST \
--data @- <<EOF
{
"verbose": false,
"activeAtOffset": "${OFFSET}",
"filter": {
"filtersByParty": {
"${CREDENTIAL_ISSUER_PARTY_ID}": {
"cumulative": [
{
"identifierFilter": {
"TemplateFilter": {
"value": {
"templateId": "${CREDENTIAL_TEMPLATE}",
"includeCreatedEventBlob": false
}
}
}
}
]
}
}
}
}
EOF
)
# Keep only credentials whose createArgument.issuer matches CREDENTIAL_ISSUER_PARTY_ID and whose
# claim property/value pairs match the configured CREDENTIAL_CLAIMS_JSON pairs (ignoring subject).
FILTERED=$(echo "$RESULT" | jq \
--arg CREDENTIAL_ISSUER_PARTY_ID "$CREDENTIAL_ISSUER_PARTY_ID" \
--argjson expectedClaimPairs "${CREDENTIAL_CLAIMS_JSON}" \
'[
def normalizePairs($xs): ($xs | map({property: .property, value: .value}) | unique);
def matchesPairsExactly($claims; $expected):
(normalizePairs($expected)) as $e
| (normalizePairs($claims)) as $a
| ($a == $e);
.[]
| .contractEntry.JsActiveContract.createdEvent
| {
contractId: .contractId,
createdAt: .createdAt,
id: .createArgument.id,
issuer: .createArgument.issuer,
holder: .createArgument.holder,
description: .createArgument.description,
validFrom: .createArgument.validFrom,
validUntil: .createArgument.validUntil,
claims: .createArgument.claims
}
| select(.issuer == $CREDENTIAL_ISSUER_PARTY_ID)
| select(matchesPairsExactly(.claims; $expectedClaimPairs))
]'
)
COUNT=$(echo "$FILTERED" | jq 'length')
echo "--- Issued credentials of ${CREDENTIAL_ISSUER_PARTY_ID} as of offset ${OFFSET} (count=${COUNT}) ---"
echo "$FILTERED" | jq
OUTPUTFILE="${SCRIPT_DIR}/response-step-3.json"
echo "$FILTERED" > "$OUTPUTFILE"
response-step-3.json.3
Remove parties from the allowlist
- UI user
- API user
- In the Credential module, find the active credential for the party being removed.
- Confirm that its claims match the allowlist entry that you intend to remove.
- Revoke or delete the credential.
In order to remove parties from the allowlist, the corresponding credentials must be archived.The following script archives all credentials returned in step 3. Apply the required filters to the
response-step-3.json file before running this script if you want to archive only a subset of the credentials.#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DATAFILE="${SCRIPT_DIR}/source.sh"
source "$DATAFILE"
CONTRACTS_FILE="${SCRIPT_DIR}/response-step-3.json"
if [[ ! -f "$CONTRACTS_FILE" ]]; then
echo "Error: ${CONTRACTS_FILE} not found. Run step 3 first." >&2
exit 1
fi
# Extract contractIds from step 3 output.
CONTRACT_IDS_JSON=$(jq -c '[.[].contractId] | map(select(type == "string" and length > 0))' "$CONTRACTS_FILE")
COUNT=$(echo "$CONTRACT_IDS_JSON" | jq 'length')
if [[ "$COUNT" -eq 0 ]]; then
echo "No contractIds found in ${CONTRACTS_FILE}. Nothing to archive." >&2
echo "[]" > "${SCRIPT_DIR}/response-step-4.json"
exit 0
fi
COMMANDS_JSON=$(jq -n \
--arg templateId "${CREDENTIAL_TEMPLATE}" \
--argjson contractIds "${CONTRACT_IDS_JSON}" \
'[
$contractIds[]
| {
ExerciseCommand: {
templateId: $templateId,
contractId: .,
choice: "Archive",
choiceArgument: {}
}
}
]'
)
RESULT=$(
curl -sS --fail-with-body \
--url "${HTTP_JSON_API}/v2/commands/submit-and-wait-for-transaction" \
--header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
--header "Content-Type: application/json" \
--request POST \
--data @- <<EOF
{
"commands": {
"commands": ${COMMANDS_JSON},
"workflowId": "",
"userId": "${CREDENTIAL_ISSUER_USER_ID}",
"commandId": "$(uuidgen | tr -d '\n')",
"deduplicationPeriod": {
"DeduplicationDuration": {
"value": { "seconds": 30, "nanos": 0 }
}
},
"actAs": [
"${CREDENTIAL_ISSUER_PARTY_ID}"
],
"readAs": [],
"submissionId": "$(uuidgen | tr -d '\n')",
"disclosedContracts": [],
"domainId": "",
"packageIdSelectionPreference": []
}
}
EOF
)
echo "--- Command response (archived count=${COUNT}) ---"
echo "$RESULT" | jq
OUTPUTFILE="${SCRIPT_DIR}/response-step-4.json"
echo "$RESULT" > "$OUTPUTFILE"
Was this page helpful?
YesNo
⌘I