---
title: "Read holdings"
description: "Query Canton token holdings through CIP-0103 ledgerApi using a ledger-end snapshot and the Splice Holding interface filter."
url: "https://sigilry.org/guides/read-holdings"
---

CIP-0103 has no dedicated holdings method. Use `ledgerApi` to read the connected party’s active contracts, filtered by the Splice token Holding interface. Send’s gateway authenticates the read as the connected user; the party filter does not grant access to another party’s private contracts.

Connect first and confirm the [active network](/guides/send-connect-testnet/). The request sequence is:

1. Read `/v2/state/ledger-end` to select a snapshot offset.
2. POST `/v2/state/active-contracts` with that offset and an `InterfaceFilter` for `#splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding`.
3. Read the returned created events and Holding interface views.

## Query a snapshot

```ts
import type { SpliceProvider } from "@sigilry/dapp";
import "@sigilry/dapp/browser-globals";

async function readHoldings(provider: SpliceProvider) {
  const account = await provider.request({ method: "getPrimaryAccount" });
  const ledgerEnd = await provider.request({
    method: "ledgerApi",
    params: { requestMethod: "get", resource: "/v2/state/ledger-end" },
  });

  // The proxy parses JSON numbers; reject offsets it cannot represent exactly.
  if (Array.isArray(ledgerEnd)) throw new Error("Expected a ledger-end object");
  const offset = ledgerEnd.offset;
  if (typeof offset !== "number" || !Number.isSafeInteger(offset) || offset < 0) {
    throw new Error("Ledger end must be a non-negative safe integer");
  }
  const activeAtOffset = offset;

  const response = await provider.request({
    method: "ledgerApi",
    params: {
      requestMethod: "post",
      resource: "/v2/state/active-contracts",
      body: {
        activeAtOffset,
        eventFormat: {
          verbose: true,
          filtersByParty: {
            [account.partyId]: {
              cumulative: [
                {
                  identifierFilter: {
                    InterfaceFilter: {
                      value: {
                        interfaceId:
                          "#splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding",
                        includeInterfaceView: true,
                        includeCreatedEventBlob: true,
                      },
                    },
                  },
                },
              ],
            },
          },
        },
      },
    },
  });

  // The active-contracts endpoint returns an array, rather than a ledger-end object.
  if (!Array.isArray(response)) throw new Error("Expected an active-contracts array");
  const current = await provider.request({ method: "getPrimaryAccount" });
  if (current.partyId !== account.partyId || current.networkId !== account.networkId) {
    throw new Error("Account or network changed; query a fresh snapshot");
  }
  return response;
}

if (!window.canton) throw new Error("Connect a Canton wallet first");
const holdings = await readHoldings(window.canton);
console.log(holdings);
```

`activeAtOffset` is sent as a JSON number after the safe-integer check. Send’s proxy parses the ledger-end response with `JSON.parse` and serializes request bodies with `JSON.stringify`; offsets above `Number.MAX_SAFE_INTEGER` (`2^53 - 1`) cannot pass through this proxy without potential precision loss. This example rejects them. Quoting the offset does not recover precision already lost in the response.

The JSON API uses `InterfaceFilter: { value: ... }` (including capital letters), rather than the protobuf client’s `case: "interfaceFilter"` representation. The package-name reference beginning with `#` selects the installed Holding interface without hard-coding a package hash. The interface must be available on the participant.

`eventFormat` supplies the party filter and verbosity. In this `eventFormat` example, top-level `verbose` must be omitted, even though the pinned OpenAPI schema lists it as required; also omit the deprecated top-level `filter`. Set verbosity inside `eventFormat`, as above. Setting top-level `verbose: true` with `eventFormat` is rejected. See the [pinned Canton JSON API schema](https://github.com/sigilry/sigilry/blob/main/packages/canton-json-api/api-specs/openapi.yaml) for the request and response shapes.

## Interpret the response

The result is an array of contract entries. Active entries have `contractEntry.JsActiveContract.createdEvent`, including `contractId` and `interfaceViews`. Inspect each interface view’s `viewStatus` (`code` and `message`) before reading `viewValue`; an unavailable or failed view is not a zero balance. Incomplete assignment/unassignment entries can occur during reassignment and need separate handling.

Holdings are contracts, not an aggregated balance response. Group amounts by instrument and owner from the Holding views. Keep decimal amounts lossless rather than converting them to JavaScript floating-point numbers. Locked holdings and the application’s spending rules can make the spendable amount differ from the total held amount.

An empty result can mean no visible contracts implement this interface at the snapshot. Check the party, network, offset, and interface availability before treating it as the user’s complete portfolio. This query covers implementations of the token Holding interface; it does not enumerate every asset template on Canton.

## Reads and submissions

Send’s `ledgerApi` proxy allows ledger reads on an allowlist. Use `prepareExecute` (or Sigilry’s `prepareExecuteAndWait`) for command submission and wallet approval. Do not submit commands through `ledgerApi`.

The HTTP active-contracts endpoint is intended for small snapshots and can reject large results with `413 Content Too Large`. After the initial snapshot, use `POST /v2/updates` to query subsequent changes over a bounded offset range. It returns a finite list in a blocking request and has the same `413 Content Too Large` limit. `ledgerApi` cannot open the JSON API websocket. Re-read ledger end for a fresh snapshot after a pruning or reassignment error.
