Reference · Validator access

Seaport Sandbox Validator Access

The Seaport Devnet validator is a hosted Canton 3.x participant on the Canton Devnet global synchronizer. Every idea in this archive ships with these endpoints baked in — no signup, no local participant, no synchronizer setup. Tokens are issued by an OIDC provider and last 8 hours; the validator accepts them as Bearer tokens on HTTP and as the daml.ws.auth / jwt.token.<token> subprotocols on websockets.

Endpoints

Ledger JSON API v2 (REST)
https://ledger-api.validator.devnet.sandbox.fivenorth.io/
Ledger JSON API v2 (WS)
wss://ledger-api.validator.devnet.sandbox.fivenorth.io/
OIDC token URL
https://auth.sandbox.fivenorth.io/application/o/token/
OIDC audience
validator-devnet-m2m
OIDC scope
daml_ledger_api
Runtime client_id
validator-devnet-m2m

The runtime client_secret is provisioned for the workspace as the CANTON_DEVNET_OIDC_RUNTIME_CLIENT_SECRET environment variable. It is shared across the hackathon and only valid against this devnet — never use it in production.

1 · Exchange credentials for a Bearer token

curl -X POST 'https://auth.sandbox.fivenorth.io/application/o/token/' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=validator-devnet-m2m' \
  -d 'client_secret=$CANTON_DEVNET_OIDC_RUNTIME_CLIENT_SECRET' \
  -d 'audience=validator-devnet-m2m' \
  -d 'scope=daml_ledger_api'

Returns { access_token, expires_in: 28800, token_type: "Bearer" }. Cache in memory; refresh before expires_in elapses.

2 · Call the JSON Ledger API v2

curl 'https://ledger-api.validator.devnet.sandbox.fivenorth.io/v2/state/active-contracts?pageSize=25' \
  -X POST \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{ "filter": { "filtersByParty": {} }, "verbose": false }'

Common paths: /v2/dars (upload DAR), /v2/parties (allocate party), /v2/users (create user), /v2/users/{id}/rights (grant CanActAs), /v2/commands/submit-and-wait-for-transaction (exercise a Daml choice and get the updateId + events).

3 · Stream live updates over WebSocket

const ws = new WebSocket(
  "wss://ledger-api.validator.devnet.sandbox.fivenorth.io/v2/updates/flats",
  ["daml.ws.auth", "jwt.token." + accessToken]
);

The validator authenticates websocket connections via subprotocols, not headers. Pass daml.ws.auth and a second subprotocol of the form jwt.token.<ACCESS_TOKEN>.

Four invariants

  1. Two token subjects, two purposes. A bootstrap client uploads DARs, allocates parties, and grants rights. A runtime client signs every command and ledger read.
  2. Bootstrap must be idempotent. Treat 409 / already-exists as success; persist the logical-name → fully-qualified party-id map.
  3. userId === token.sub. On Devnet the validator enforces that the userId in every command equals the runtime token's sub claim. Decode the JWT to derive it.
  4. DAR upgrade = rename, not bump. Bumping a package version on a breaking schema change fails with KNOWN_PACKAGE_VERSION. For hackathon iteration, bump the package name instead.

Full Canton documentation: https://docs.canton.network/llms-full.txt