Registry Utility - Allowlist-Entry Credential API Example¶
This example shows how to create allowlist-entry Credential\s via the HTTP JSON API (v2), i.e., credentials where the issuer is also the holder.
The workflows covered here are for the Credential issuer to:
Create new allowlist-entry credentials in a batch (atomically).
Query the ledger end offset.
Read all currently-active allowlist-entry credentials of the given issuer with matching claims.
Revoke (archieve) the corresponding credentials.
Preparation¶
Prerequisites:
Access to a running Canton HTTP JSON API (v2).
Local tools:
bash,curl,jq, anduuidgen.
Add all the required information to the source.sh file:
1#!/usr/bin/env bash
2
3## =================================================================================================
4## Purpose: Configurations for this example, amend variables as needed.
5## Script: source.sh
6## =================================================================================================
7
8# Credential issuer details
9CREDENTIAL_ISSUER_TOKEN="eyJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6ImRhbWxfbGVkZ2VyX2FwaSIsImlhdCI6MTc2OTAxMTAzNSwiYXVkIjoiaHR0cHM6Ly91dGlsaXR5LmNhbnRvbi5uZXR3b3JrIiwic3ViIjoicmVnaXN0cmFyIn0.mm8wFTWR7y3DRcLC9xU1t9CUcsGStwf6mpBtuSvbvQA"
10CREDENTIAL_ISSUER_PARTY_ID="registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
11CREDENTIAL_ISSUER_USER_ID="registrar"
12
13# Subjects to create an allowlist-entry credential for.
14CREDENTIAL_SUBJECT_PARTY_IDS='[
15 "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
16 "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
17 "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
18]'
19
20# Description used for all created credentials
21CREDENTIAL_DESCRIPTION="Allowlist-Entry Credential"
22
23# Claims to attach to each created credential (JSON list).
24# The scripts will set the `subject` field of each claim to the current subject party.
25CREDENTIAL_CLAIMS_JSON='[
26 {"property":"IsHolderOf","value":"INST1"},
27 {"property":"IsHolderOf","value":"INST2"}
28]'
29
30# JSON API endpoint
31# Example (local): HTTP_JSON_API="http://localhost:8001/api/json-api"
32# Example (remote): HTTP_JSON_API="https://<your-host>/api/json-api"
33HTTP_JSON_API="http://localhost:8001/api/json-api"
34
35# Daml template IDs (package-name qualified)
36CREDENTIAL_TEMPLATE="#utility-credential-v0:Utility.Credential.V0.Credential:Credential"
The required information is:
Details of |
Description |
|---|---|
Credential issuer |
|
Subjects |
|
Claims |
|
Description |
|
JSON API base URL |
|
Step 1: Create Allowlist-Entry Credentials (in a Batch)¶
This step submits a single transaction that contains one CreateCommand per subject from
CREDENTIAL_SUBJECT_PARTY_IDS. The transaction is atomic, either all credentials are created, or
none are.
Run the following script:
1#!/usr/bin/env bash
2
3## =================================================================================================
4## How-to Tutorial: Manage allowlist-entry credentials
5## Step 1: Credential issuer createss allowlist-entry `Utility.Credential.V0.Credential:Credential`s
6## for multiple subjects in a single transaction.
7## Authorized by: credential issuer
8## Script: step-1-credential-issuer-creates-allowlist-entries.sh
9## =================================================================================================
10
11set -euo pipefail
12
13DATAFILE="source.sh"
14source "$DATAFILE"
15
16# Validate inputs
17if [[ -z "${CREDENTIAL_SUBJECT_PARTY_IDS:-}" ]]; then
18 echo "Error: CREDENTIAL_SUBJECT_PARTY_IDS is not set in source.sh"
19 exit 1
20fi
21if [[ -z "${CREDENTIAL_CLAIMS_JSON:-}" ]]; then
22 echo "Error: CREDENTIAL_CLAIMS_JSON is not set in source.sh"
23 exit 1
24fi
25echo "${CREDENTIAL_SUBJECT_PARTY_IDS}" | jq -e 'type == "array" and length > 0 and all(.[]; type == "string")' >/dev/null
26echo "${CREDENTIAL_CLAIMS_JSON}" | jq -e 'type == "array" and length > 0 and all(.[]; has("property") and has("value"))' >/dev/null
27
28# Random Credential ID prefix.
29ID_PREFIX="allowlist-entry: $(od -An -N4 -tu4 < /dev/urandom | tr -d ' ')"
30
31COMMANDS_JSON=$(jq -n \
32 --arg templateId "${CREDENTIAL_TEMPLATE}" \
33 --arg issuer "${CREDENTIAL_ISSUER_PARTY_ID}" \
34 --arg description "${CREDENTIAL_DESCRIPTION}" \
35 --arg holder "${CREDENTIAL_ISSUER_PARTY_ID}" \
36 --arg idPrefix "${ID_PREFIX}-" \
37 --argjson subjects "${CREDENTIAL_SUBJECT_PARTY_IDS}" \
38 --argjson claimPairs "${CREDENTIAL_CLAIMS_JSON}" \
39 '[
40 ($subjects | to_entries[]) as $entry
41 | ($entry.key + 1) as $n
42 | ($entry.value) as $subjectParty
43 | {
44 CreateCommand: {
45 templateId: $templateId,
46 createArguments: {
47 issuer: $issuer,
48 holder: $holder,
49 id: ($idPrefix + ($n | tostring)),
50 description: $description,
51 validFrom: null,
52 validUntil: null,
53 claims: (
54 $claimPairs
55 | map({subject: $subjectParty, property: .property, value: .value})
56 ),
57 observers: { map: [] }
58 }
59 }
60 }
61 ]')
62
63RESULT=$(
64 curl -s \
65 --url "${HTTP_JSON_API}/v2/commands/submit-and-wait-for-transaction" \
66 --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
67 --header "Content-Type: application/json" \
68 --request POST \
69 --data @- <<EOF
70{
71 "commands": {
72 "commands": ${COMMANDS_JSON},
73 "workflowId": "",
74 "userId": "${CREDENTIAL_ISSUER_USER_ID}",
75 "commandId": "$(uuidgen | tr -d '\n')",
76 "deduplicationPeriod": {
77 "DeduplicationDuration": {
78 "value": { "seconds": 30, "nanos": 0 }
79 }
80 },
81 "actAs": [
82 "${CREDENTIAL_ISSUER_PARTY_ID}"
83 ],
84 "readAs": [],
85 "submissionId": "$(uuidgen | tr -d '\n')",
86 "disclosedContracts": [],
87 "domainId": "",
88 "packageIdSelectionPreference": []
89 }
90}
91EOF
92)
93
94echo "--- Command response ---"
95echo "$RESULT" | jq
96
97OUTPUTFILE="response-step-1.json"
98echo "$RESULT" > "$OUTPUTFILE"
The result is the transaction response stored in response-step-1.json.
Example response:
1{
2 "transaction": {
3 "updateId": "12200dff8b12b6136ad9326352e25207992b33a9ab5c9b16d5edb1e7d693fcaeb32b",
4 "commandId": "5D5BA47A-8C47-4E2C-BD64-6539B25446F9",
5 "workflowId": "",
6 "effectiveAt": "2026-01-21T15:58:04.458345Z",
7 "events": [
8 {
9 "CreatedEvent": {
10 "offset": 198,
11 "nodeId": 0,
12 "contractId": "009d7ada69b1e26db27535a60a985d3902d602a4d684b37cd31859903e6d5f7a66ca121220d7d60b9fe0f67118aab24d3814b3807ab787fe9c49c103660625bee5d4f54886",
13 "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
14 "contractKey": null,
15 "createArgument": {
16 "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
17 "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
18 "id": "allowlist-entry: 290808241-1",
19 "description": "Allowlist-Entry Credential",
20 "validFrom": null,
21 "validUntil": null,
22 "claims": [
23 {
24 "subject": "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
25 "property": "IsHolderOf",
26 "value": "INST1"
27 },
28 {
29 "subject": "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
30 "property": "IsHolderOf",
31 "value": "INST2"
32 }
33 ],
34 "observers": {
35 "map": []
36 }
37 },
38 "createdEventBlob": "",
39 "interfaceViews": [],
40 "witnessParties": [
41 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
42 ],
43 "signatories": [
44 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
45 ],
46 "observers": [],
47 "createdAt": "2026-01-21T15:58:04.458345Z",
48 "packageName": "utility-credential-v0",
49 "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
50 "acsDelta": true
51 }
52 },
53 {
54 "CreatedEvent": {
55 "offset": 198,
56 "nodeId": 1,
57 "contractId": "00cafb02692ee25fd1ce37433e8f2c7e9d8fa4826cd36353e68b7b84b36aa94df1ca1212204b97fbecd571fc03180e36d0f8e643455e782ca6ba09f2214a740c4c6ff3c55e",
58 "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
59 "contractKey": null,
60 "createArgument": {
61 "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
62 "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
63 "id": "allowlist-entry: 290808241-2",
64 "description": "Allowlist-Entry Credential",
65 "validFrom": null,
66 "validUntil": null,
67 "claims": [
68 {
69 "subject": "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
70 "property": "IsHolderOf",
71 "value": "INST1"
72 },
73 {
74 "subject": "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
75 "property": "IsHolderOf",
76 "value": "INST2"
77 }
78 ],
79 "observers": {
80 "map": []
81 }
82 },
83 "createdEventBlob": "",
84 "interfaceViews": [],
85 "witnessParties": [
86 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
87 ],
88 "signatories": [
89 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
90 ],
91 "observers": [],
92 "createdAt": "2026-01-21T15:58:04.458345Z",
93 "packageName": "utility-credential-v0",
94 "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
95 "acsDelta": true
96 }
97 },
98 {
99 "CreatedEvent": {
100 "offset": 198,
101 "nodeId": 2,
102 "contractId": "0039dd342399e147a805d867b9675cfeaa333264a11ee66239c589ad6d22183b80ca12122009066bc94452281a107527c17f572aa2d976af73701d59eff0553ca039b6067c",
103 "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
104 "contractKey": null,
105 "createArgument": {
106 "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
107 "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
108 "id": "allowlist-entry: 290808241-3",
109 "description": "Allowlist-Entry Credential",
110 "validFrom": null,
111 "validUntil": null,
112 "claims": [
113 {
114 "subject": "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
115 "property": "IsHolderOf",
116 "value": "INST1"
117 },
118 {
119 "subject": "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
120 "property": "IsHolderOf",
121 "value": "INST2"
122 }
123 ],
124 "observers": {
125 "map": []
126 }
127 },
128 "createdEventBlob": "",
129 "interfaceViews": [],
130 "witnessParties": [
131 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
132 ],
133 "signatories": [
134 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
135 ],
136 "observers": [],
137 "createdAt": "2026-01-21T15:58:04.458345Z",
138 "packageName": "utility-credential-v0",
139 "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
140 "acsDelta": true
141 }
142 }
143 ],
144 "offset": 198,
145 "synchronizerId": "global-domain::1220840e7f637a4c596517230420f05d25e9914a364d62e2c01ea9a162fe90823fa1",
146 "traceContext": {
147 "traceparent": "00-8fd12fc7e9d2dddd48dc67d228362df5-3ecad24d429a391d-01",
148 "tracestate": null
149 },
150 "recordTime": "2026-01-21T15:58:04.485074Z",
151 "externalTransactionHash": null
152 }
153}
Step 2: Get Ledger Offset¶
Run the following script to obtain the ledger end offset:
1#!/usr/bin/env bash
2
3## =================================================================================================
4## How-to Tutorial: Manage allowlist-entry credentials
5## Step 2: Credential issuer gets ledger offset
6## Authorized by: credential issuer
7## Script: step-2-credential-issuer-gets-offset.sh
8## =================================================================================================
9
10DATAFILE="source.sh"
11source "$DATAFILE"
12
13OFFSET=$(curl -s \
14 --url "${HTTP_JSON_API}/v2/state/ledger-end" \
15 --header "Accept: application/json" \
16 --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}")
17
18echo "$OFFSET" | jq
19
20OUTPUTFILE="response-step-2.json"
21echo "$OFFSET" > "$OUTPUTFILE"
The result is the ledger end offset at this moment, stored in response-step-2.json.
Example response:
1{
2 "offset": 206
3}
Step 3: Read All Issued Allowlist-Entry Credentials¶
This step queries active contracts visible to the issuer for the credential template, and filters
the result to only those where createArgument.issuer == CREDENTIAL_ISSUER_PARTY_ID, and where
the claims match those specified in CREDENTIAL_CLAIMS_JSON (ignoring the subject).
This script expects the ledger end offset from step 2 to be present in response-step-2.json.
Run the following script:
1#!/usr/bin/env bash
2
3## =================================================================================================
4## How-to Tutorial: Manage allowlist-entry credentials
5## Step 3: Credential issuer retrieve active `Utility.Credential.V0.Credential:Credential` contracts
6## issued for itself, filtered by expected claim property/value pairs.
7## Authorized by: credential issuer
8## Script: step-3-credential-issuer-retrieves-issued-credentials.sh
9## =================================================================================================
10
11set -euo pipefail
12
13DATAFILE="source.sh"
14source "$DATAFILE"
15
16# Validate expected claim pairs config (property/value)
17if [[ -z "${CREDENTIAL_CLAIMS_JSON:-}" ]]; then
18 echo "Error: CREDENTIAL_CLAIMS_JSON is not set in source.sh" >&2
19 exit 1
20fi
21
22echo "${CREDENTIAL_CLAIMS_JSON}" | jq -e 'type == "array" and all(.[]; (has("property") and has("value")))' >/dev/null
23
24# Get offset from previous step
25if [[ -f "response-step-2.json" ]]; then
26 JSONCONTENT=$(cat "response-step-2.json")
27 OFFSET=$(echo "$JSONCONTENT" | jq -r ".offset")
28else
29 echo "Error: response-step-2.json not found"
30 exit 1
31fi
32
33RESULT=$(curl -s \
34 --url "${HTTP_JSON_API}/v2/state/active-contracts" \
35 --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
36 --header "Content-Type: application/json" \
37 --request POST \
38 --data @- <<EOF
39{
40 "verbose": false,
41 "activeAtOffset": "${OFFSET}",
42 "filter": {
43 "filtersByParty": {
44 "${CREDENTIAL_ISSUER_PARTY_ID}": {
45 "cumulative": [
46 {
47 "identifierFilter": {
48 "TemplateFilter": {
49 "value": {
50 "templateId": "${CREDENTIAL_TEMPLATE}",
51 "includeCreatedEventBlob": false
52 }
53 }
54 }
55 }
56 ]
57 }
58 }
59 }
60}
61EOF
62)
63
64# Keep only credentials whose createArgument.issuer matches CREDENTIAL_ISSUER_PARTY_ID and whose
65# claim property/value pairs match the configured CREDENTIAL_CLAIMS_JSON pairs (ignoring subject).
66FILTERED=$(echo "$RESULT" | jq \
67 --arg CREDENTIAL_ISSUER_PARTY_ID "$CREDENTIAL_ISSUER_PARTY_ID" \
68 --argjson expectedClaimPairs "${CREDENTIAL_CLAIMS_JSON}" \
69 '[
70 def normalizePairs($xs): ($xs | map({property: .property, value: .value}) | unique);
71 def matchesPairsExactly($claims; $expected):
72 (normalizePairs($expected)) as $e
73 | (normalizePairs($claims)) as $a
74 | ($a == $e);
75
76 .[]
77 | .contractEntry.JsActiveContract.createdEvent
78 | {
79 contractId: .contractId,
80 createdAt: .createdAt,
81 id: .createArgument.id,
82 issuer: .createArgument.issuer,
83 holder: .createArgument.holder,
84 description: .createArgument.description,
85 validFrom: .createArgument.validFrom,
86 validUntil: .createArgument.validUntil,
87 claims: .createArgument.claims
88 }
89 | select(.issuer == $CREDENTIAL_ISSUER_PARTY_ID)
90 | select(matchesPairsExactly(.claims; $expectedClaimPairs))
91 ]'
92)
93
94COUNT=$(echo "$FILTERED" | jq 'length')
95
96echo "--- Issued credentials of ${CREDENTIAL_ISSUER_PARTY_ID} as of offset ${OFFSET} (count=${COUNT}) ---"
97echo "$FILTERED" | jq
98
99OUTPUTFILE="response-step-3.json"
100echo "$FILTERED" > "$OUTPUTFILE"
The result is a JSON array written to response-step-3.json.
Example response:
1[
2 {
3 "contractId": "009d7ada69b1e26db27535a60a985d3902d602a4d684b37cd31859903e6d5f7a66ca121220d7d60b9fe0f67118aab24d3814b3807ab787fe9c49c103660625bee5d4f54886",
4 "createdAt": "2026-01-21T15:58:04.458345Z",
5 "id": "allowlist-entry: 290808241-1",
6 "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
7 "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
8 "description": "Allowlist-Entry Credential",
9 "validFrom": null,
10 "validUntil": null,
11 "claims": [
12 {
13 "subject": "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
14 "property": "IsHolderOf",
15 "value": "INST1"
16 },
17 {
18 "subject": "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
19 "property": "IsHolderOf",
20 "value": "INST2"
21 }
22 ]
23 },
24 {
25 "contractId": "00cafb02692ee25fd1ce37433e8f2c7e9d8fa4826cd36353e68b7b84b36aa94df1ca1212204b97fbecd571fc03180e36d0f8e643455e782ca6ba09f2214a740c4c6ff3c55e",
26 "createdAt": "2026-01-21T15:58:04.458345Z",
27 "id": "allowlist-entry: 290808241-2",
28 "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
29 "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
30 "description": "Allowlist-Entry Credential",
31 "validFrom": null,
32 "validUntil": null,
33 "claims": [
34 {
35 "subject": "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
36 "property": "IsHolderOf",
37 "value": "INST1"
38 },
39 {
40 "subject": "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
41 "property": "IsHolderOf",
42 "value": "INST2"
43 }
44 ]
45 },
46 {
47 "contractId": "0039dd342399e147a805d867b9675cfeaa333264a11ee66239c589ad6d22183b80ca12122009066bc94452281a107527c17f572aa2d976af73701d59eff0553ca039b6067c",
48 "createdAt": "2026-01-21T15:58:04.458345Z",
49 "id": "allowlist-entry: 290808241-3",
50 "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
51 "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
52 "description": "Allowlist-Entry Credential",
53 "validFrom": null,
54 "validUntil": null,
55 "claims": [
56 {
57 "subject": "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
58 "property": "IsHolderOf",
59 "value": "INST1"
60 },
61 {
62 "subject": "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
63 "property": "IsHolderOf",
64 "value": "INST2"
65 }
66 ]
67 }
68]
Step 4: Revoke Issued Allowlist-Entry Credentials¶
This step revokes or archives (consumes) the active credential contracts returned by step 3.
This script expects the filtered list from step 3 to be present in response-step-3.json.
Run the following script:
1#!/usr/bin/env bash
2
3## =================================================================================================
4## How-to Tutorial: Manage allowlist-entry credentials
5## Step 4: Credential issuer revokes the active `Utility.Credential.V0.Credential:Credential`s
6## contracts returned by step 3
7## Authorized by: credential issuer
8## Script: step-4-credential-issuer-revokes-issued-credentials.sh
9## =================================================================================================
10
11set -euo pipefail
12
13DATAFILE="source.sh"
14source "$DATAFILE"
15
16CONTRACTS_FILE="response-step-3.json"
17
18if [[ ! -f "$CONTRACTS_FILE" ]]; then
19 echo "Error: ${CONTRACTS_FILE} not found. Run step 3 first." >&2
20 exit 1
21fi
22
23# Extract contractIds from step 3 output.
24CONTRACT_IDS_JSON=$(jq -c '[.[].contractId] | map(select(type == "string" and length > 0))' "$CONTRACTS_FILE")
25COUNT=$(echo "$CONTRACT_IDS_JSON" | jq 'length')
26
27if [[ "$COUNT" -eq 0 ]]; then
28 echo "No contractIds found in ${CONTRACTS_FILE}. Nothing to archive." >&2
29 echo "[]" > "response-step-4.json"
30 exit 0
31fi
32
33COMMANDS_JSON=$(jq -n \
34 --arg templateId "${CREDENTIAL_TEMPLATE}" \
35 --argjson contractIds "${CONTRACT_IDS_JSON}" \
36 '[
37 $contractIds[]
38 | {
39 ExerciseCommand: {
40 templateId: $templateId,
41 contractId: .,
42 choice: "Archive",
43 choiceArgument: {}
44 }
45 }
46 ]'
47)
48
49RESULT=$(
50 curl -s \
51 --url "${HTTP_JSON_API}/v2/commands/submit-and-wait-for-transaction" \
52 --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
53 --header "Content-Type: application/json" \
54 --request POST \
55 --data @- <<EOF
56{
57 "commands": {
58 "commands": ${COMMANDS_JSON},
59 "workflowId": "",
60 "userId": "${CREDENTIAL_ISSUER_USER_ID}",
61 "commandId": "$(uuidgen | tr -d '\n')",
62 "deduplicationPeriod": {
63 "DeduplicationDuration": {
64 "value": { "seconds": 30, "nanos": 0 }
65 }
66 },
67 "actAs": [
68 "${CREDENTIAL_ISSUER_PARTY_ID}"
69 ],
70 "readAs": [],
71 "submissionId": "$(uuidgen | tr -d '\n')",
72 "disclosedContracts": [],
73 "domainId": "",
74 "packageIdSelectionPreference": []
75 }
76}
77EOF
78)
79
80echo "--- Command response (archived count=${COUNT}) ---"
81echo "$RESULT" | jq
82
83OUTPUTFILE="response-step-4.json"
84echo "$RESULT" > "$OUTPUTFILE"
The result is the transaction response stored in response-step-4.json.
Example response:
1{
2 "transaction": {
3 "updateId": "12201bc9ff362bf24503fde97f9e4401934f29ae4ef07361bcd2e67ae31d6d7e3427",
4 "commandId": "62A57B3A-B0D3-4962-9035-1B260AD302E8",
5 "workflowId": "",
6 "effectiveAt": "2026-01-21T15:58:43.555374Z",
7 "events": [
8 {
9 "ArchivedEvent": {
10 "offset": 207,
11 "nodeId": 0,
12 "contractId": "009d7ada69b1e26db27535a60a985d3902d602a4d684b37cd31859903e6d5f7a66ca121220d7d60b9fe0f67118aab24d3814b3807ab787fe9c49c103660625bee5d4f54886",
13 "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
14 "witnessParties": [
15 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
16 ],
17 "packageName": "utility-credential-v0",
18 "implementedInterfaces": []
19 }
20 },
21 {
22 "ArchivedEvent": {
23 "offset": 207,
24 "nodeId": 1,
25 "contractId": "00cafb02692ee25fd1ce37433e8f2c7e9d8fa4826cd36353e68b7b84b36aa94df1ca1212204b97fbecd571fc03180e36d0f8e643455e782ca6ba09f2214a740c4c6ff3c55e",
26 "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
27 "witnessParties": [
28 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
29 ],
30 "packageName": "utility-credential-v0",
31 "implementedInterfaces": []
32 }
33 },
34 {
35 "ArchivedEvent": {
36 "offset": 207,
37 "nodeId": 2,
38 "contractId": "0039dd342399e147a805d867b9675cfeaa333264a11ee66239c589ad6d22183b80ca12122009066bc94452281a107527c17f572aa2d976af73701d59eff0553ca039b6067c",
39 "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
40 "witnessParties": [
41 "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
42 ],
43 "packageName": "utility-credential-v0",
44 "implementedInterfaces": []
45 }
46 }
47 ],
48 "offset": 207,
49 "synchronizerId": "global-domain::1220840e7f637a4c596517230420f05d25e9914a364d62e2c01ea9a162fe90823fa1",
50 "traceContext": {
51 "traceparent": "00-8823f2d62154ad7f34c50fdb3c5b51b0-fc0881278522b625-01",
52 "tracestate": null
53 },
54 "recordTime": "2026-01-21T15:58:43.591203Z",
55 "externalTransactionHash": null
56 }
57}