> ## Documentation Index
> Fetch the complete documentation index at: https://docs.digitalasset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Allowlist

> As a registrar / token issuer, configure and manage allow-lists for my instruments.

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.

If a requirement is left empty, no credential verification is performed for that workflow.

## Configuring an Allowlist

Each credential requirement specifies:

* **Credential Issuer** – The party responsible for maintaining the allowlist.
* **Claim Property** – The permission being granted (for example `canHold` or `canMint`).
* **Claim Value** – The asset or permission scope (for example `USDX` or `BondX`).

When an action is requested, the Registry App verifies that a valid credential matching the configured requirements exists for the acting party.

## 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.

<Steps>
  <Step title="Create allowlist-entry credentials">
    <Tabs>
      <Tab title="UI user">
        1. In the Registry App, go to the **Credential** module.
        2. Click **Offer new credential**.
        3. Set the credential holder to **Use same party** so that the issuer also holds the credential.
        4. Set the subject to the party being added to the allowlist.
        5. Add the claim property and value required by the Instrument Configuration.
        6. Click **Offer Free credential**.

        Repeat these steps for each party that you want to add to the allowlist.
      </Tab>

      <Tab title="API user">
        Before running the workflow scripts, create `source.sh` and update its values for your environment:

        ```bash theme={null}
        #!/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"
        ```

        Now run the following script to create credentials for all configured subjects in one atomic transaction:

        ```bash theme={null}
        #!/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"
        ```

        The transaction response is saved to `response-step-1.json`.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Review active allowlist entries">
    <Tabs>
      <Tab title="UI 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.
      </Tab>

      <Tab title="API user">
        Retrieve the current ledger end

        ```bash theme={null}
        #!/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"
        ```

        Then, run the following:

        ```bash theme={null}
        #!/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"
        ```

        The matching active credentials are saved to `response-step-3.json`.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Remove parties from the allowlist">
    <Tabs>
      <Tab title="UI user">
        1. In the **Credential** module, find the active credential for the party being removed.
        2. Confirm that its claims match the allowlist entry that you intend to remove.
        3. Revoke or delete the credential.

        The party no longer satisfies the corresponding credential requirement after the credential is removed.
      </Tab>

      <Tab title="API user">
        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.

        ```bash theme={null}
        #!/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"
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>
