Registry Utility - Retrieve Holdings API Example

This example shows how to retrieve holdings on CNU 0.12.x and later using the HTTP JSON API.

The example below retrieves all holdings of a user for a specific registrar, instrument, and minimum amount.

Prerequisites

  • A running validator node connected to one of DevNet, TestNet, or MainNet

  • The Utility DARs installed on your validator node

  • A valid business user token (<user-token>) obtained from the IAM of the validator node

  • A business user and associated party (<user-party>) created through the Validator API

  • The JSON API endpoint (<http-json-api>) of the participant node

  • curl and jq installed on your system

Preparation

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# Users's details
 9USER_TOKEN="<PASTE_USER_JWT_HERE>"
10USER_PARTY_ID="holder::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b"
11
12# Filtering criteria
13ADMIN_PARTY_ID="registrar::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b"
14INSTRUMENT_ID="INST"
15MIN_AMOUNT="1.0"
16
17# JSON API endpoint (pick one)
18# - Remote (TestNet/MainNet/other): HTTP_JSON_API="https://<your-host>/api/json-api"
19# - DevNet (example):               HTTP_JSON_API="https://utility.utility.cnu.devnet.da-int.net/api/json-api"
20# - Local (example):                HTTP_JSON_API="http://localhost:8001/api/json-api"
21HTTP_JSON_API="http://localhost:8001/api/json-api"
22
23# Token standard holding interface, may change when new versions of splice exists
24HOLDING_INTERFACE="#splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding"
25
26# Utility holding template, may change when a new version of the utility exists
27HOLDING_TEMPLATE="#utility-registry-holding-v0:Utility.Registry.Holding.V0.Holding:Holding"

The required information is:

Details of

Description

User

JWT, user ID, and party ID of the user

Registrar

Party ID of the registrar you want to query holdings for.

Instrument

The identifier of the specific instrument you want to query holdings for.

Minimum amount

The minimum amount for the holdings you want to query.

Retrieve Holdings

1. Obtain the Ledger End Offset

Run the following script to obtain the ledger end offset:

 1#!/usr/bin/env bash
 2
 3# obtain-ledger-offset.sh - Obtains the ledger end offset
 4
 5SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 6DATAFILE="${SCRIPT_DIR}/source.sh"
 7source "$DATAFILE"
 8
 9OFFSET=$(curl -sS --fail-with-body --request GET \
10    --url "${HTTP_JSON_API}/v2/state/ledger-end" \
11    --header "Accept: application/json" \
12    --header "Authorization: Bearer ${USER_TOKEN}")
13
14echo "$OFFSET" | jq
15
16OUTPUTFILE="${SCRIPT_DIR}/response-obtain-ledger-offset.json"
17echo "$OFFSET" > "$OUTPUTFILE"

The result is the ledger end offset at this moment, stored in response-obtain-ledger-offset.json. For example:

1{
2    "offset": 400
3}

2. Retrieve Utility Holdings

Run the following script to retrieve the Utility Holdings of the user, filtered by the specified instrument ID and minimum amount:

 1#!/usr/bin/env bash
 2
 3## =================================================================================================
 4## Purpose: Retrieves holdings of the user for a specific instrument and minimum amount
 5## Authorized by: User
 6## Script: retrieve-utility-holdings.sh
 7## =================================================================================================
 8
 9SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10DATAFILE="${SCRIPT_DIR}/source.sh"
11source "$DATAFILE"
12
13# Get offset from previous step
14if [[ -f "${SCRIPT_DIR}/response-obtain-ledger-offset.json" ]]; then
15  JSONCONTENT=$(cat "${SCRIPT_DIR}/response-obtain-ledger-offset.json")
16  OFFSET=$(echo "$JSONCONTENT" | jq -r ".offset")
17else
18  echo "Error: response-obtain-ledger-offset.json not found"
19  exit 1
20fi
21
22RESULT=$(curl -sS --fail-with-body \
23    --url "${HTTP_JSON_API}/v2/state/active-contracts" \
24    --header "Authorization: Bearer ${USER_TOKEN}" \
25    --header "Content-Type: application/json" \
26    --request POST \
27    --data @- <<EOF
28{
29    "verbose": false,
30    "activeAtOffset": "${OFFSET}",
31    "filter": {
32        "filtersByParty": {
33            "${USER_PARTY_ID}": {
34                "cumulative": [{
35                    "identifierFilter": {
36                        "TemplateFilter": {
37                            "value": {
38                                "templateId": "${HOLDING_TEMPLATE}",
39                                "includeCreatedEventBlob": false
40                            }
41                        }
42                    }
43                }]
44            }
45        }
46    }
47}
48EOF
49)
50
51# Filter holdings for a specific holder, instrument ID, admin, and minimum amount
52FILTERED=$(echo "$RESULT" | jq \
53  --arg USER_PARTY_ID "$USER_PARTY_ID" \
54  --arg INSTRUMENT_ID "$INSTRUMENT_ID" \
55  --arg ADMIN_PARTY_ID "$ADMIN_PARTY_ID" \
56  --arg MIN_AMOUNT "$MIN_AMOUNT" \
57  '[
58    .[]
59    | .contractEntry.JsActiveContract.createdEvent.createArgument
60    | select(
61        .registrar == $ADMIN_PARTY_ID and
62        .owner == $USER_PARTY_ID and
63        .instrument.id == $INSTRUMENT_ID and
64        .instrument.source == $ADMIN_PARTY_ID and
65        (.amount | tonumber) >= ($MIN_AMOUNT | tonumber)
66    )
67  ]'
68)
69
70echo "--- All utility holdings of ${USER_PARTY_ID} with amount>=${MIN_AMOUNT} as of offset ${OFFSET} ---"
71echo "$FILTERED" | jq
72
73OUTPUTFILE="${SCRIPT_DIR}/response-retrieve-utility-holdings.json"
74echo "$FILTERED" > "$OUTPUTFILE"

The result is the Holding Cids, stored in response-retrieve-utility-holdings.json. For example:

 1[
 2    {
 3        "operator": "operator::1220a6f417751797b91ab08423236f8c0d3c53f1ec62ed1afd4602816390b68be8f0",
 4        "provider": "provider::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b",
 5        "registrar": "registrar::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b",
 6        "owner": "holder::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b",
 7        "instrument": {
 8            "source": "registrar::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b",
 9            "id": "INST",
10            "scheme": "RegistrarInternalScheme"
11        },
12        "label": "",
13        "amount": "1.0000000000"
14    }
15]

3. Retrieve Canton Network Token Standard Holdings

Run the following script to retrieve the Canton Network Token Standard Holdings of the user, filtered by the specified instrument ID and minimum amount:

 1#!/usr/bin/env bash
 2
 3## =================================================================================================
 4## Purpose: Retrieves holdings of the user for a specific instrument and minimum amount
 5## Authorized by: User
 6## Script: retrieve-holdings.sh
 7## =================================================================================================
 8
 9SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10DATAFILE="${SCRIPT_DIR}/source.sh"
11source "$DATAFILE"
12
13# Get offset from previous step
14if [[ -f "${SCRIPT_DIR}/response-obtain-ledger-offset.json" ]]; then
15  JSONCONTENT=$(cat "${SCRIPT_DIR}/response-obtain-ledger-offset.json")
16  OFFSET=$(echo "$JSONCONTENT" | jq -r ".offset")
17else
18  echo "Error: response-obtain-ledger-offset.json not found"
19  exit 1
20fi
21
22RESULT=$(curl -sS --fail-with-body \
23    --url "${HTTP_JSON_API}/v2/state/active-contracts" \
24    --header "Authorization: Bearer ${USER_TOKEN}" \
25    --header "Content-Type: application/json" \
26    --request POST \
27    --data @- <<EOF
28{
29    "verbose": false,
30    "activeAtOffset": "${OFFSET}",
31    "filter": {
32        "filtersByParty": {
33            "${USER_PARTY_ID}": {
34                "cumulative": [{
35                    "identifierFilter": {
36                        "InterfaceFilter": {
37                            "value": {
38                                "interfaceId":"$HOLDING_INTERFACE",
39                                "includeInterfaceView": true,
40                                "includeCreatedEventBlob": false
41                            }
42                        }
43                    }
44                }]
45            }
46        }
47    }
48}
49EOF
50)
51
52# Filter holdings for a specific holder, instrument ID, admin, and minimum amount
53FILTERED=$(echo "$RESULT" | jq \
54  --arg USER_PARTY_ID "$USER_PARTY_ID" \
55  --arg INSTRUMENT_ID "$INSTRUMENT_ID" \
56  --arg ADMIN_PARTY_ID "$ADMIN_PARTY_ID" \
57  --arg MIN_AMOUNT "$MIN_AMOUNT" \
58  '[
59    .[]
60    | .contractEntry.JsActiveContract.createdEvent.interfaceViews[]
61    | select(
62        .viewValue.owner == $USER_PARTY_ID and
63        .viewValue.instrumentId.id == $INSTRUMENT_ID and
64        .viewValue.instrumentId.admin == $ADMIN_PARTY_ID and
65        (.viewValue.amount | tonumber) >= ($MIN_AMOUNT | tonumber)
66    )
67  ]'
68)
69
70echo "--- All interface holdings of ${USER_PARTY_ID} with amount>=${MIN_AMOUNT} as of offset ${OFFSET} ---"
71echo "$FILTERED" | jq
72
73OUTPUTFILE="${SCRIPT_DIR}/response-retrieve-holdings.json"
74echo "$FILTERED" > "$OUTPUTFILE"

The result is the Holding Cids, stored in response-retrieve-holdings.json. For example:

 1[
 2    {
 3        "interfaceId": "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b:Splice.Api.Token.HoldingV1:Holding",
 4        "viewStatus": {
 5            "code": 0,
 6            "message": "",
 7            "details": []
 8        },
 9        "viewValue": {
10            "owner": "holder::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b",
11            "instrumentId": {
12                "admin": "registrar::1220163a2070cd9cf831721c6a84e19b7727b9825636ea9b09f24229f45a341f7c4b",
13                "id": "INST"
14            },
15            "amount": "1.0000000000",
16            "lock": null,
17            "meta": {
18                "values": {
19                    "utility.digitalasset.com/holding-label": ""
20                }
21            }
22        }
23    }
24]

Note

In Canton 3, the JSON API no longer supports direct querying as in Canton 2. As a result, any filtering of holdings must be performed client-side after retrieving the data. For advanced querying and filtering, it is recommended to use the PQS.