Registry Utility - Transfer Preapproval API Example

This example shows how to create a TransferPreapproval contract via 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 details
 9RECEIVER_TOKEN="<PASTE_JWT_TOKEN_HERE>"
10RECEIVER_PARTY_ID="holder::1220e71be62943820d0f7ecc365fc498adcd25e1b1fd165f0ae9b65c343230f93579"
11RECEIVER_USER_ID="holder"
12
13# Operator details
14OPERATOR_PARTY_ID="operator::1220ae8c93e1f1263d0366cbc4c2a2fe587b5227e929ddd76528380c59deb58ada8f"
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 -s \
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="response-step-1.json"
99echo "$RESULT" > "$OUTPUTFILE"

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

Example response:

 1{
 2    "transaction": {
 3        "updateId": "1220d28660de73bc898696c75955f9ad3cf99ca7d317634a29749779e2c960d81cdc",
 4        "commandId": "C07EC6B2-25E0-46E5-B0A2-1AB04BF0BD31",
 5        "workflowId": "",
 6        "effectiveAt": "2026-02-05T15:25:53.665284Z",
 7        "events": [
 8            {
 9                "CreatedEvent": {
10                    "offset": 339,
11                    "nodeId": 0,
12                    "contractId": "00c2307ab2eeb1d348197729fcae1dbfac52201392bf054fc955f278007a6d01b2ca1212201d0efef35a061299b59ab211bf9b3ed8cb05af08c808c4f26d2c9907a47089b7",
13                    "templateId": "f33ca939728c8401e67af3ab1dfc1ad7dc00fb35862bf5e964c5cdb3d5b6857c:Utility.Registry.App.V0.Model.TransferPreapproval:TransferPreapproval",
14                    "contractKey": null,
15                    "createArgument": {
16                        "operator": "operator::1220ae8c93e1f1263d0366cbc4c2a2fe587b5227e929ddd76528380c59deb58ada8f",
17                        "receiver": "holder::1220e71be62943820d0f7ecc365fc498adcd25e1b1fd165f0ae9b65c343230f93579",
18                        "instrumentAdmin": "registrar::1220e71be62943820d0f7ecc365fc498adcd25e1b1fd165f0ae9b65c343230f93579",
19                        "instrumentAllowances": [
20                            {
21                                "id": "INST"
22                            }
23                        ]
24                    },
25                    "createdEventBlob": "",
26                    "interfaceViews": [],
27                    "witnessParties": [
28                        "holder::1220e71be62943820d0f7ecc365fc498adcd25e1b1fd165f0ae9b65c343230f93579"
29                    ],
30                    "signatories": [
31                        "holder::1220e71be62943820d0f7ecc365fc498adcd25e1b1fd165f0ae9b65c343230f93579"
32                    ],
33                    "observers": [
34                        "operator::1220ae8c93e1f1263d0366cbc4c2a2fe587b5227e929ddd76528380c59deb58ada8f"
35                    ],
36                    "createdAt": "2026-02-05T15:25:53.665284Z",
37                    "packageName": "utility-registry-app-v0",
38                    "representativePackageId": "f33ca939728c8401e67af3ab1dfc1ad7dc00fb35862bf5e964c5cdb3d5b6857c",
39                    "acsDelta": true
40                }
41            }
42        ],
43        "offset": 339,
44        "synchronizerId": "global-domain::12208750245c5bca762b46827b41716c2d945838858b6ef97b35fb76817db0044d20",
45        "traceContext": {
46            "traceparent": "00-81a6821119081c80a3082310c434c8b9-ba14d7e97aa83062-01",
47            "tracestate": null
48        },
49        "recordTime": "2026-02-05T15:25:53.690588Z",
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.