Registry Utility - Transfer Preapproval API Example

This example shows how to create a TransferPreapproval contract on CNU 0.12.x and later using the HTTP JSON API.

Preparation

Prerequisites:

  • Access to a running Canton HTTP JSON API.

  • Local tools: bash, curl, jq, and uuidgen.

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# Receiver's details
 9RECEIVER_TOKEN="<PASTE_RECEIVER_JWT_HERE>"
10RECEIVER_PARTY_ID="holder::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b"
11RECEIVER_USER_ID="holder"
12
13# Operator details
14OPERATOR_PARTY_ID="operator::1220a6f417751797b91ab08423236f8c0d3c53f1ec62ed1afd4602816390b68be8f0"
15# You can retrieve the operator party ID from `http://<host>/api/utilities/v0/operator`, e.g.
16# - Local:  `curl http://localhost:8080/api/utilities/v0/operator`
17# - DevNet: `curl https://api.utilities.digitalasset-dev.com/api/utilities/v0/operator`
18
19# Instrument admin (or registrar) details whose instruments are being preapproved.
20INSTRUMENT_ADMIN_PARTY_ID="registrar::1220e71be62943820d0f7ecc365fc498adcd25e1b1fd165f0ae9b65c343230f93579"
21
22# Instrument ids to preapprove (an empty list means all instruments are preapproved)
23INSTRUMENT_IDS='[
24	"INST"
25]'
26
27# JSON API endpoint
28# Example (local): HTTP_JSON_API="http://localhost:8001/api/json-api"
29# Example (remote): HTTP_JSON_API="https://<your-host>/api/json-api"
30HTTP_JSON_API="http://localhost:8001/api/json-api"
31
32# Daml template IDs (package-name qualified)
33TRANSFER_PREAPPROVAL_TEMPLATE="#utility-registry-app-v0:Utility.Registry.App.V0.Model.TransferPreapproval:TransferPreapproval"

The required information is:

Details of

Description

Receiver (authorizing user)

RECEIVER_TOKEN, RECEIVER_USER_ID, RECEIVER_PARTY_ID

Operator

OPERATOR_PARTY_ID

Instrument admin

INSTRUMENT_ADMIN_PARTY_ID: party whose instruments are being preapproved

Instrument IDs

INSTRUMENT_IDS: JSON array of instrument IDs (strings). Use [] for a blanket preapproval.

JSON API base URL

HTTP_JSON_API: HTTP JSON API base URL

Note

You can retrieve the OPERATOR_PARTY_ID via http://<your-host>/api/utilities/v0/operator, e.g.

  • Local: http://localhost:8080/api/utilities/v0/operator

  • DevNet: https://api.utilities.digitalasset-dev.com/api/utilities/v0/operator

Create Transfer Preapproval

This step submits a single transaction containing a single CreateCommand.

Run the following script:

 1#!/usr/bin/env bash
 2
 3## =================================================================================================
 4## How-to Tutorial: Create transfer preapproval
 5## Authorized by: receiver
 6## Script: create-transfer-preapproval.sh
 7## =================================================================================================
 8
 9set -euo pipefail
10
11SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
12source "${SCRIPT_DIR}/source.sh"
13
14OUTPUT_DIR="${SCRIPT_DIR}/../response"
15mkdir -p "$OUTPUT_DIR"
16
17# Validate inputs
18if [[ -z "${HTTP_JSON_API:-}" ]]; then
19    echo "Error: HTTP_JSON_API is not set in source.sh"
20    exit 1
21fi
22if [[ -z "${TRANSFER_PREAPPROVAL_TEMPLATE:-}" ]]; then
23    echo "Error: TRANSFER_PREAPPROVAL_TEMPLATE is not set in source.sh"
24    exit 1
25fi
26if [[ -z "${OPERATOR_PARTY_ID:-}" ]]; then
27    echo "Error: OPERATOR_PARTY_ID is not set in source.sh"
28    exit 1
29fi
30if [[ -z "${INSTRUMENT_ADMIN_PARTY_ID:-}" ]]; then
31    echo "Error: INSTRUMENT_ADMIN_PARTY_ID is not set in source.sh"
32    exit 1
33fi
34if [[ -z "${RECEIVER_TOKEN:-}" || -z "${RECEIVER_USER_ID:-}" || -z "${RECEIVER_PARTY_ID:-}" ]]; then
35    echo "Error: RECEIVER_TOKEN/RECEIVER_USER_ID/RECEIVER_PARTY_ID must be set in source.sh"
36    exit 1
37fi
38if [[ -z "${INSTRUMENT_IDS:-}" ]]; then
39    echo "Error: INSTRUMENT_IDS is not set in source.sh"
40    exit 1
41fi
42echo "${INSTRUMENT_IDS}" | jq -e 'type == "array" and all(.[]; type == "string")' >/dev/null
43
44COMMANDS_JSON=$(jq -n \
45    --arg templateId "${TRANSFER_PREAPPROVAL_TEMPLATE}" \
46    --arg operator "${OPERATOR_PARTY_ID}" \
47    --arg receiver "${RECEIVER_PARTY_ID}" \
48    --arg instrumentAdmin "${INSTRUMENT_ADMIN_PARTY_ID}" \
49    --argjson instrumentIds "${INSTRUMENT_IDS}" \
50    '[
51        {
52            CreateCommand: {
53                templateId: $templateId,
54                createArguments: {
55                    operator: $operator,
56                    receiver: $receiver,
57                    instrumentAdmin: $instrumentAdmin,
58                    instrumentAllowances: ($instrumentIds | map({id: .}))
59                }
60            }
61        }
62    ]')
63
64RESULT=$(
65    curl -sS --fail-with-body \
66        --url "${HTTP_JSON_API}/v2/commands/submit-and-wait-for-transaction" \
67        --header "Authorization: Bearer ${RECEIVER_TOKEN}" \
68        --header "Content-Type: application/json" \
69        --request POST \
70        --data @- <<EOF
71{
72    "commands": {
73        "commands": ${COMMANDS_JSON},
74        "workflowId": "",
75        "userId": "${RECEIVER_USER_ID}",
76        "commandId": "$(uuidgen | tr -d '\n')",
77        "deduplicationPeriod": {
78            "DeduplicationDuration": {
79                "value": { "seconds": 30, "nanos": 0 }
80            }
81        },
82        "actAs": [
83            "${RECEIVER_PARTY_ID}"
84        ],
85        "readAs": [],
86        "submissionId": "$(uuidgen | tr -d '\n')",
87        "disclosedContracts": [],
88        "domainId": "",
89        "packageIdSelectionPreference": []
90    }
91}
92EOF
93)
94
95echo "--- Command response ---"
96echo "$RESULT" | jq
97
98OUTPUTFILE="${SCRIPT_DIR}/response.json"
99echo "$RESULT" > "$OUTPUTFILE"

The result is the transaction response stored in response/response.json.

Example response:

 1{
 2    "transaction": {
 3        "updateId": "1220d5d4cf0fb1f802a7e0592d0f37eef3da8b3b21a1d052d31d905c19500ffe5c76",
 4        "commandId": "103655EB-9CE7-4873-816C-C6C85BA21C1E",
 5        "workflowId": "",
 6        "effectiveAt": "2026-03-20T14:54:18.687951Z",
 7        "events": [
 8            {
 9                "CreatedEvent": {
10                    "offset": 458,
11                    "nodeId": 0,
12                    "contractId": "00d2a3d4ebfc3d78d9fbc270223743b6d3d41d81f969720f49e2ef155e88818d50ca121220ae2b55cc41cb8419137edda70cba958fce73d7ed76ca30dbc86d53f7fe171f2d",
13                    "templateId": "7a75ef6e69f69395a4e60919e228528bb8f3881150ccfde3f31bcc73864b18ab:Utility.Registry.App.V0.Model.TransferPreapproval:TransferPreapproval",
14                    "contractKey": null,
15                    "createArgument": {
16                        "operator": "operator::1220a6f417751797b91ab08423236f8c0d3c53f1ec62ed1afd4602816390b68be8f0",
17                        "receiver": "holder::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b",
18                        "instrumentAdmin": "registrar::1220e71be62943820d0f7ecc365fc498adcd25e1b1fd165f0ae9b65c343230f93579",
19                        "instrumentAllowances": [
20                            {
21                                "id": "INST"
22                            }
23                        ]
24                    },
25                    "createdEventBlob": "",
26                    "interfaceViews": [],
27                    "witnessParties": [
28                        "holder::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b"
29                    ],
30                    "signatories": [
31                        "holder::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b"
32                    ],
33                    "observers": [
34                        "operator::1220a6f417751797b91ab08423236f8c0d3c53f1ec62ed1afd4602816390b68be8f0"
35                    ],
36                    "createdAt": "2026-03-20T14:54:18.687951Z",
37                    "packageName": "utility-registry-app-v0",
38                    "representativePackageId": "7a75ef6e69f69395a4e60919e228528bb8f3881150ccfde3f31bcc73864b18ab",
39                    "acsDelta": true
40                }
41            }
42        ],
43        "offset": 458,
44        "synchronizerId": "global-domain::122090f9b95363b7ca407a1aacab116b997d4e6f326cc0576cfed6f45f979d8c60ee",
45        "traceContext": {
46            "traceparent": "00-cb90ece6d1b7f938ba131487c10e7577-5fa36607d41db120-01",
47            "tracestate": null
48        },
49        "recordTime": "2026-03-20T14:54:19.215073Z",
50        "externalTransactionHash": null
51    }
52}

Once a transfer preapproval is established for a receiver and instrument, all incoming transfers become direct. If you are following the Registry Utility - Transfer API Example, you may skip the second step; the transfer is automatically effectuated immediately upon completion of the first step.