@fairmint/canton-node-sdk 0.0.225 → 0.0.227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairmint/canton-node-sdk",
3
- "version": "0.0.225",
3
+ "version": "0.0.227",
4
4
  "description": "Canton Node SDK",
5
5
  "keywords": [
6
6
  "canton",
@@ -10,6 +10,11 @@ DOCKERD_LOG_FILE="/tmp/localnet-dockerd.log"
10
10
  HOSTS_ENTRY="127.0.0.1 scan.localhost sv.localhost wallet.localhost"
11
11
  CURL_CONNECT_TIMEOUT=2
12
12
  CURL_MAX_TIME=5
13
+ LOCALNET_SPLICE_VERSION="${CANTON_LOCALNET_SPLICE_VERSION:-}"
14
+ LOCALNET_SCRIBE_VERSION="${CANTON_LOCALNET_SCRIBE_VERSION:-}"
15
+ LOCALNET_PROTOCOL_VERSION="${CANTON_LOCALNET_PROTOCOL_VERSION:-}"
16
+ VALIDATOR_READY_ATTEMPTS="${CANTON_LOCALNET_VALIDATOR_READY_ATTEMPTS:-90}"
17
+ SCAN_READY_ATTEMPTS="${CANTON_LOCALNET_SCAN_READY_ATTEMPTS:-90}"
13
18
 
14
19
  log() {
15
20
  printf '[localnet] %s\n' "$*"
@@ -34,6 +39,21 @@ require_command() {
34
39
  fi
35
40
  }
36
41
 
42
+ require_positive_integer() {
43
+ local name="$1"
44
+ local value="$2"
45
+
46
+ if [[ ! "${value}" =~ ^[1-9][0-9]*$ ]]; then
47
+ log "${name} must be a positive integer; received '${value}'."
48
+ exit 1
49
+ fi
50
+ }
51
+
52
+ validate_configuration() {
53
+ require_positive_integer "CANTON_LOCALNET_VALIDATOR_READY_ATTEMPTS" "${VALIDATOR_READY_ATTEMPTS}"
54
+ require_positive_integer "CANTON_LOCALNET_SCAN_READY_ATTEMPTS" "${SCAN_READY_ATTEMPTS}"
55
+ }
56
+
37
57
  ensure_sudo() {
38
58
  if ! sudo_noninteractive_available; then
39
59
  log "Passwordless sudo is required in this cloud environment."
@@ -147,6 +167,7 @@ run_quickstart_command() {
147
167
  local command="$1"
148
168
  local docker_shim_dir=""
149
169
  local quickstart_image_tag=""
170
+ local quickstart_path="${HOME}/.dpm/bin:${HOME}/.daml/bin:${PATH}"
150
171
  local status=0
151
172
 
152
173
  quickstart_image_tag="$(resolve_quickstart_image_tag)"
@@ -161,28 +182,20 @@ EOF
161
182
  log "Using sudo docker shim for quickstart command."
162
183
  fi
163
184
 
164
- set +e
165
185
  if [[ -n "${docker_shim_dir}" ]]; then
166
- (
167
- cd "${QUICKSTART_DIR}"
168
- MODULES_DIR="${QUICKSTART_DIR}/docker/modules" \
169
- LOCALNET_DIR="${QUICKSTART_DIR}/docker/modules/localnet" \
170
- IMAGE_TAG="${quickstart_image_tag}" \
171
- PATH="${docker_shim_dir}:${HOME}/.daml/bin:${PATH}" \
172
- bash -lc "${command}"
173
- )
174
- status=$?
175
- else
176
- (
177
- cd "${QUICKSTART_DIR}"
178
- MODULES_DIR="${QUICKSTART_DIR}/docker/modules" \
179
- LOCALNET_DIR="${QUICKSTART_DIR}/docker/modules/localnet" \
180
- IMAGE_TAG="${quickstart_image_tag}" \
181
- PATH="${HOME}/.daml/bin:${PATH}" \
182
- bash -lc "${command}"
183
- )
184
- status=$?
186
+ quickstart_path="${docker_shim_dir}:${quickstart_path}"
185
187
  fi
188
+
189
+ set +e
190
+ (
191
+ cd "${QUICKSTART_DIR}"
192
+ MODULES_DIR="${QUICKSTART_DIR}/docker/modules" \
193
+ LOCALNET_DIR="${QUICKSTART_DIR}/docker/modules/localnet" \
194
+ IMAGE_TAG="${quickstart_image_tag}" \
195
+ PATH="${quickstart_path}" \
196
+ bash -lc "${command}"
197
+ )
198
+ status=$?
186
199
  set -e
187
200
 
188
201
  if [[ -n "${docker_shim_dir}" ]]; then
@@ -199,19 +212,28 @@ run_quickstart_make() {
199
212
 
200
213
  run_infra_compose() {
201
214
  local compose_args="$1"
215
+ local keycloak_compose_file=""
216
+ local keycloak_env_file=""
217
+ local keycloak_profile=""
218
+
219
+ if [[ "$(current_auth_mode)" == "oauth2" ]]; then
220
+ keycloak_compose_file='-f "${MODULES_DIR}/keycloak/compose.yaml"'
221
+ keycloak_env_file='--env-file "${MODULES_DIR}/keycloak/compose.env"'
222
+ keycloak_profile='--profile keycloak'
223
+ fi
202
224
 
203
225
  run_quickstart_command "docker compose \
204
226
  -f \"\${LOCALNET_DIR}/compose.yaml\" \
205
- -f \"\${MODULES_DIR}/keycloak/compose.yaml\" \
227
+ ${keycloak_compose_file} \
206
228
  --env-file .env \
207
229
  --env-file .env.local \
208
230
  --env-file \"\${LOCALNET_DIR}/compose.env\" \
209
231
  --env-file \"\${LOCALNET_DIR}/env/common.env\" \
210
- --env-file \"\${MODULES_DIR}/keycloak/compose.env\" \
232
+ ${keycloak_env_file} \
211
233
  --profile app-provider \
212
234
  --profile app-user \
213
235
  --profile sv \
214
- --profile keycloak \
236
+ ${keycloak_profile} \
215
237
  ${compose_args}"
216
238
  }
217
239
 
@@ -308,25 +330,276 @@ ensure_hosts_entries() {
308
330
  fi
309
331
  }
310
332
 
311
- quickstart_setup() {
312
- if [[ ! -f "${QUICKSTART_DIR}/.env.local" ]]; then
313
- if quickstart_infra_only_enabled; then
314
- write_quickstart_env_local
315
- else
333
+ set_quickstart_env_value() {
334
+ local file="$1"
335
+ local key="$2"
336
+ local value="$3"
337
+
338
+ if [[ ! -f "${file}" ]]; then
339
+ return
340
+ fi
341
+
342
+ if grep -Eq "^${key}=" "${file}"; then
343
+ KEY="${key}" VALUE="${value}" perl -0pi -e '
344
+ my $key = $ENV{"KEY"};
345
+ my $value = $ENV{"VALUE"};
346
+ s/^\Q$key\E=.*/$key=$value/m;
347
+ ' "${file}"
348
+ else
349
+ printf '\n%s=%s\n' "${key}" "${value}" >>"${file}"
350
+ fi
351
+ }
352
+
353
+ patch_quickstart_canton_healthcheck() {
354
+ local healthcheck="${QUICKSTART_DIR}/docker/modules/localnet/docker/canton/health-check.sh"
355
+
356
+ if [[ ! -f "${healthcheck}" ]]; then
357
+ log "Canton quickstart healthcheck not found: ${healthcheck}"
358
+ exit 1
359
+ fi
360
+
361
+ cat >"${healthcheck}" <<'EOF'
362
+ #!/bin/bash
363
+ set -eou pipefail
364
+
365
+ http_check() {
366
+ local port="$1"
367
+ local path="$2"
368
+ local status=""
369
+
370
+ echo "Checking http://localhost:${port}${path}"
371
+ exec 3<>"/dev/tcp/127.0.0.1/${port}"
372
+ printf 'GET %s HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' "${path}" >&3
373
+ IFS=$'\r' read -r status <&3 || true
374
+ exec 3<&-
375
+ exec 3>&-
376
+
377
+ case "${status}" in
378
+ HTTP/*\ 2*) ;;
379
+ *)
380
+ echo "Unexpected status from ${port}${path}: ${status}"
381
+ return 1
382
+ ;;
383
+ esac
384
+ }
385
+
386
+ if [ "${APP_USER_PROFILE:-off}" = "on" ]; then
387
+ http_check "2${CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX}" "/health"
388
+ fi
389
+ if [ "${APP_PROVIDER_PROFILE:-off}" = "on" ]; then
390
+ http_check "3${CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX}" "/health"
391
+ fi
392
+ if [ "${SV_PROFILE:-off}" = "on" ]; then
393
+ http_check "4${CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX}" "/health"
394
+ fi
395
+ EOF
396
+ chmod +x "${healthcheck}"
397
+ }
398
+
399
+ patch_quickstart_splice_healthcheck() {
400
+ local healthcheck="${QUICKSTART_DIR}/docker/modules/localnet/docker/splice/health-check.sh"
401
+
402
+ if [[ ! -f "${healthcheck}" ]]; then
403
+ log "Splice quickstart healthcheck not found: ${healthcheck}"
404
+ exit 1
405
+ fi
406
+
407
+ cat >"${healthcheck}" <<'EOF'
408
+ #!/bin/bash
409
+ set -eou pipefail
410
+
411
+ http_check() {
412
+ local port="$1"
413
+ local path="$2"
414
+ local status=""
415
+
416
+ echo "Checking http://localhost:${port}${path}"
417
+ exec 3<>"/dev/tcp/127.0.0.1/${port}"
418
+ printf 'GET %s HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' "${path}" >&3
419
+ IFS=$'\r' read -r status <&3 || true
420
+ exec 3<&-
421
+ exec 3>&-
422
+
423
+ case "${status}" in
424
+ HTTP/*\ 2*) ;;
425
+ *)
426
+ echo "Unexpected status from ${port}${path}: ${status}"
427
+ return 1
428
+ ;;
429
+ esac
430
+ }
431
+
432
+ if [ "${APP_USER_PROFILE:-off}" = "on" ]; then
433
+ http_check "2${VALIDATOR_ADMIN_API_PORT_SUFFIX}" "/api/validator/readyz"
434
+ fi
435
+ if [ "${APP_PROVIDER_PROFILE:-off}" = "on" ]; then
436
+ http_check "3${VALIDATOR_ADMIN_API_PORT_SUFFIX}" "/api/validator/readyz"
437
+ fi
438
+ if [ "${SV_PROFILE:-off}" = "on" ]; then
439
+ http_check "4${VALIDATOR_ADMIN_API_PORT_SUFFIX}" "/api/validator/readyz"
440
+ http_check "5012" "/api/scan/readyz"
441
+ http_check "5014" "/api/sv/readyz"
442
+ fi
443
+ EOF
444
+ chmod +x "${healthcheck}"
445
+ }
446
+
447
+ configure_quickstart_localnet() {
448
+ local env_file=""
449
+ local canton_conf="${QUICKSTART_DIR}/docker/modules/localnet/conf/canton/app.conf"
450
+
451
+ for env_file in "${QUICKSTART_DIR}/.env" "${QUICKSTART_DIR}/.env.local"; do
452
+ if [[ -n "${LOCALNET_SPLICE_VERSION}" ]]; then
453
+ set_quickstart_env_value "${env_file}" "SPLICE_VERSION" "${LOCALNET_SPLICE_VERSION}"
454
+ fi
455
+ if [[ -n "${LOCALNET_SCRIBE_VERSION}" ]]; then
456
+ set_quickstart_env_value "${env_file}" "SCRIBE_VERSION" "${LOCALNET_SCRIBE_VERSION}"
457
+ fi
458
+ if [[ -n "${CANTON_LOCALNET_DAML_RUNTIME_VERSION:-}" ]]; then
459
+ set_quickstart_env_value "${env_file}" "DAML_RUNTIME_VERSION" "${CANTON_LOCALNET_DAML_RUNTIME_VERSION}"
460
+ fi
461
+ done
462
+
463
+ if [[ ! -f "${canton_conf}" ]]; then
464
+ log "Canton quickstart config not found: ${canton_conf}"
465
+ exit 1
466
+ fi
467
+
468
+ if [[ -n "${LOCALNET_PROTOCOL_VERSION}" ]]; then
469
+ PROTOCOL_VERSION="${LOCALNET_PROTOCOL_VERSION}" perl -0pi -e '
470
+ my $protocol_version = $ENV{"PROTOCOL_VERSION"};
471
+ s/initial-protocol-version = \d+/initial-protocol-version = $protocol_version/g;
472
+ s/(non-standard-config = yes\n)(?![[:space:]]*alpha-version-support = yes)/$1 alpha-version-support = yes\n/;
473
+ s/(initial-protocol-version = \d+\n)(?![[:space:]]*alpha-version-support = yes)/$1 alpha-version-support = yes\n/;
474
+ ' "${canton_conf}"
475
+ fi
476
+
477
+ patch_quickstart_canton_healthcheck
478
+ patch_quickstart_splice_healthcheck
479
+ }
480
+
481
+ requested_auth_mode() {
482
+ printf '%s' "${CANTON_LOCALNET_AUTH_MODE:-oauth2}"
483
+ }
484
+
485
+ quickstart_env_value() {
486
+ local key="$1"
487
+ local parsed_value=""
488
+
489
+ if [[ -f "${QUICKSTART_DIR}/.env.local" ]]; then
490
+ parsed_value="$(awk -F= -v key="${key}" '
491
+ $0 ~ /^[[:space:]]*#/ { next }
492
+ $1 == key {
493
+ value = substr($0, index($0, "=") + 1)
494
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
495
+ gsub(/^"|"$/, "", value)
496
+ gsub(/^'\''|'\''$/, "", value)
497
+ parsed = value
498
+ }
499
+ END {
500
+ if (parsed != "") {
501
+ print parsed
502
+ }
503
+ }
504
+ ' "${QUICKSTART_DIR}/.env.local")"
505
+ fi
506
+
507
+ printf '%s' "${parsed_value}"
508
+ }
509
+
510
+ configured_auth_mode() {
511
+ quickstart_env_value "AUTH_MODE"
512
+ }
513
+
514
+ quickstart_profile_config_complete() {
515
+ local key=""
516
+
517
+ for key in OBSERVABILITY_ENABLED AUTH_MODE PARTY_HINT TEST_MODE; do
518
+ if [[ -z "$(quickstart_env_value "${key}")" ]]; then
519
+ return 1
520
+ fi
521
+ done
522
+
523
+ return 0
524
+ }
525
+
526
+ verify_configured_auth_mode() {
527
+ local expected_auth_mode="$1"
528
+ local actual_auth_mode=""
529
+
530
+ actual_auth_mode="$(configured_auth_mode)"
531
+ if [[ "${actual_auth_mode}" != "${expected_auth_mode}" ]]; then
532
+ log "cn-quickstart setup did not configure AUTH_MODE=${expected_auth_mode}; found '${actual_auth_mode:-unset}'."
533
+ exit 1
534
+ fi
535
+ }
536
+
537
+ current_auth_mode() {
538
+ local configured=""
539
+
540
+ configured="$(configured_auth_mode)"
541
+ if [[ -n "${configured}" ]]; then
542
+ printf '%s' "${configured}"
543
+ return
544
+ fi
545
+
546
+ requested_auth_mode
547
+ }
548
+
549
+ run_quickstart_setup() {
550
+ local auth_mode=""
551
+
552
+ auth_mode="$(requested_auth_mode)"
553
+ case "${auth_mode}" in
554
+ shared-secret)
555
+ log "Running cn-quickstart setup (shared-secret mode)..."
556
+ (
557
+ cd "${QUICKSTART_DIR}"
558
+ printf 'Y\nn\n\n' | make setup || true
559
+ )
560
+ verify_configured_auth_mode "shared-secret"
561
+ ;;
562
+ oauth2)
316
563
  log "Running cn-quickstart setup (OAuth2 enabled)..."
317
564
  (
318
565
  cd "${QUICKSTART_DIR}"
319
566
  # Match CI behavior first, then fall back to prompt-based answers for newer setup flows.
320
567
  echo "2" | make setup || true
321
- if ! grep -Eq '^AUTH_MODE=oauth2$' "${QUICKSTART_DIR}/.env.local" 2>/dev/null; then
568
+ if [[ "$(configured_auth_mode)" != "oauth2" ]]; then
322
569
  printf 'y\ny\n\nn\n' | make setup
323
570
  fi
324
571
  )
325
- fi
572
+ verify_configured_auth_mode "oauth2"
573
+ ;;
574
+ *)
575
+ log "Unsupported CANTON_LOCALNET_AUTH_MODE: ${auth_mode}"
576
+ exit 1
577
+ ;;
578
+ esac
579
+ }
580
+
581
+ quickstart_setup() {
582
+ if [[ ! -f "${QUICKSTART_DIR}/.env.local" ]]; then
583
+ run_quickstart_setup
584
+ elif ! quickstart_profile_config_complete; then
585
+ log "Regenerating incomplete cn-quickstart config."
586
+ rm -f "${QUICKSTART_DIR}/.env.local"
587
+ run_quickstart_setup
588
+ elif [[ -n "${CANTON_LOCALNET_AUTH_MODE:-}" && "$(configured_auth_mode)" != "${CANTON_LOCALNET_AUTH_MODE}" ]]; then
589
+ log "Regenerating cn-quickstart config for CANTON_LOCALNET_AUTH_MODE=${CANTON_LOCALNET_AUTH_MODE}."
590
+ rm -f "${QUICKSTART_DIR}/.env.local"
591
+ run_quickstart_setup
326
592
  else
327
593
  log "Reusing existing ${QUICKSTART_DIR}/.env.local."
328
594
  fi
329
595
 
596
+ if [[ ! -f "${QUICKSTART_DIR}/.env.local" ]]; then
597
+ log "cn-quickstart setup failed: ${QUICKSTART_DIR}/.env.local was not created."
598
+ exit 1
599
+ fi
600
+
601
+ configure_quickstart_localnet
602
+
330
603
  if quickstart_infra_only_enabled; then
331
604
  log "Skipping Daml SDK install for infrastructure-only localnet."
332
605
  return
@@ -343,37 +616,6 @@ quickstart_setup() {
343
616
  fi
344
617
  }
345
618
 
346
- default_party_hint() {
347
- local clean_user=""
348
- local raw_user="${USER:-runner}"
349
-
350
- clean_user="$(printf '%s' "${raw_user}" | tr -cd '[:alpha:]')"
351
- if [[ -z "${clean_user}" ]]; then
352
- clean_user="runner"
353
- fi
354
-
355
- printf 'quickstart-%s-1' "${clean_user,,}"
356
- }
357
-
358
- write_quickstart_env_local() {
359
- local auth_mode="${CANTON_LOCALNET_AUTH_MODE:-oauth2}"
360
- local observability_enabled="${CANTON_LOCALNET_OBSERVABILITY_ENABLED:-false}"
361
- local party_hint="${CANTON_LOCALNET_PARTY_HINT:-}"
362
- local test_mode="${CANTON_LOCALNET_TEST_MODE:-off}"
363
-
364
- if [[ -z "${party_hint}" ]]; then
365
- party_hint="$(default_party_hint)"
366
- fi
367
-
368
- log "Writing non-interactive cn-quickstart localnet config."
369
- cat >"${QUICKSTART_DIR}/.env.local" <<EOF
370
- OBSERVABILITY_ENABLED=${observability_enabled}
371
- AUTH_MODE=${auth_mode}
372
- PARTY_HINT=${party_hint}
373
- TEST_MODE=${test_mode}
374
- EOF
375
- }
376
-
377
619
  quickstart_fast_start_enabled() {
378
620
  is_truthy "${CANTON_LOCALNET_FAST_START:-true}"
379
621
  }
@@ -448,20 +690,37 @@ stop_infra_only_localnet() {
448
690
  }
449
691
 
450
692
  wait_for_services() {
451
- log "Waiting for Keycloak..."
452
- for _ in $(seq 1 30); do
453
- if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
454
- break
693
+ local auth_mode=""
694
+ local code=""
695
+ local ledger_code=""
696
+
697
+ auth_mode="$(current_auth_mode)"
698
+
699
+ if [[ "${auth_mode}" == "oauth2" ]]; then
700
+ log "Waiting for Keycloak..."
701
+ for _ in $(seq 1 30); do
702
+ if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
703
+ break
704
+ fi
705
+ sleep 2
706
+ done
707
+ if ! curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
708
+ log "Keycloak did not become ready."
709
+ exit 1
455
710
  fi
456
- sleep 2
457
- done
458
- if ! curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
459
- log "Keycloak did not become ready."
460
- exit 1
711
+ else
712
+ log "Checking for Keycloak (optional in ${auth_mode} mode)..."
713
+ for _ in $(seq 1 5); do
714
+ if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
715
+ log "Keycloak is ready."
716
+ break
717
+ fi
718
+ sleep 2
719
+ done
461
720
  fi
462
721
 
463
722
  log "Waiting for Validator API..."
464
- for _ in $(seq 1 30); do
723
+ for _ in $(seq 1 "${VALIDATOR_READY_ATTEMPTS}"); do
465
724
  code="$(curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -sS -o /dev/null -w '%{http_code}' http://localhost:3903/api/validator/v0/wallet/user-status || true)"
466
725
  if [[ "${code}" == "200" || "${code}" == "401" ]]; then
467
726
  break
@@ -475,7 +734,7 @@ wait_for_services() {
475
734
  fi
476
735
 
477
736
  log "Waiting for Scan API..."
478
- for _ in $(seq 1 30); do
737
+ for _ in $(seq 1 "${SCAN_READY_ATTEMPTS}"); do
479
738
  if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://scan.localhost:4000/api/scan/v0/dso-party-id >/dev/null 2>&1; then
480
739
  break
481
740
  fi
@@ -486,6 +745,20 @@ wait_for_services() {
486
745
  exit 1
487
746
  fi
488
747
 
748
+ log "Waiting for Ledger JSON API..."
749
+ for _ in $(seq 1 60); do
750
+ ledger_code="$(curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -sS -o /dev/null -w '%{http_code}' http://localhost:3975/v2/version || true)"
751
+ if [[ "${ledger_code}" == "200" || "${ledger_code}" == "401" ]]; then
752
+ break
753
+ fi
754
+ sleep 2
755
+ done
756
+ ledger_code="$(curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -sS -o /dev/null -w '%{http_code}' http://localhost:3975/v2/version || true)"
757
+ if [[ "${ledger_code}" != "200" && "${ledger_code}" != "401" ]]; then
758
+ log "Ledger JSON API did not become ready (HTTP ${ledger_code})."
759
+ exit 1
760
+ fi
761
+
489
762
  log "All localnet services are ready."
490
763
  }
491
764
 
@@ -592,6 +865,7 @@ status_localnet() {
592
865
  keycloak_ok="no"
593
866
  validator_ok="no"
594
867
  scan_ok="no"
868
+ ledger_ok="no"
595
869
  if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
596
870
  keycloak_ok="yes"
597
871
  fi
@@ -602,10 +876,15 @@ status_localnet() {
602
876
  if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://scan.localhost:4000/api/scan/v0/dso-party-id >/dev/null 2>&1; then
603
877
  scan_ok="yes"
604
878
  fi
879
+ ledger_code="$(curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -sS -o /dev/null -w '%{http_code}' http://localhost:3975/v2/version || true)"
880
+ if [[ "${ledger_code}" == "200" || "${ledger_code}" == "401" ]]; then
881
+ ledger_ok="yes"
882
+ fi
605
883
 
606
884
  printf 'Keycloak ready: %s\n' "${keycloak_ok}"
607
885
  printf 'Validator ready: %s (HTTP %s)\n' "${validator_ok}" "${validator_code:-n/a}"
608
886
  printf 'Scan ready: %s\n' "${scan_ok}"
887
+ printf 'Ledger JSON API ready: %s (HTTP %s)\n' "${ledger_ok}" "${ledger_code:-n/a}"
609
888
  }
610
889
 
611
890
  show_localnet_logs() {
@@ -642,13 +921,20 @@ show_localnet_logs() {
642
921
  }
643
922
 
644
923
  run_smoke() {
924
+ local auth_mode=""
645
925
  local validator_code=""
926
+ local ledger_code=""
646
927
 
647
928
  log "Running localnet smoke checks..."
929
+ auth_mode="$(current_auth_mode)"
648
930
 
649
- if ! curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
931
+ if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
932
+ log "Keycloak is reachable."
933
+ elif [[ "${auth_mode}" == "oauth2" ]]; then
650
934
  log "Keycloak is not reachable."
651
935
  exit 1
936
+ else
937
+ log "Keycloak not detected (expected in ${auth_mode} mode)."
652
938
  fi
653
939
 
654
940
  validator_code="$(curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -sS -o /dev/null -w '%{http_code}' http://localhost:3903/api/validator/v0/wallet/user-status || true)"
@@ -662,6 +948,12 @@ run_smoke() {
662
948
  exit 1
663
949
  fi
664
950
 
951
+ ledger_code="$(curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -sS -o /dev/null -w '%{http_code}' http://localhost:3975/v2/version || true)"
952
+ if [[ "${ledger_code}" != "200" && "${ledger_code}" != "401" ]]; then
953
+ log "Ledger JSON API is not reachable (HTTP ${ledger_code})."
954
+ exit 1
955
+ fi
956
+
665
957
  log "Smoke checks passed."
666
958
  }
667
959
 
@@ -751,6 +1043,7 @@ Environment:
751
1043
  CANTON_LOCALNET_FAST_START=true|false Enable fast startup path (default: true)
752
1044
  CANTON_LOCALNET_FORCE_FULL_START=true|false Force full startup with rebuild
753
1045
  CANTON_LOCALNET_INFRA_ONLY=true|false Start only LocalNet + Keycloak infrastructure
1046
+ CANTON_LOCALNET_AUTH_MODE=oauth2|shared-secret
754
1047
  CANTON_LOCALNET_QUICKSTART_DIR=<path> Use an existing cn-quickstart/quickstart directory
755
1048
  USAGE
756
1049
  }
@@ -763,6 +1056,7 @@ main() {
763
1056
 
764
1057
  case "$1" in
765
1058
  setup)
1059
+ validate_configuration
766
1060
  ensure_docker_packages
767
1061
  start_docker_daemon
768
1062
  ensure_submodules
@@ -770,6 +1064,7 @@ main() {
770
1064
  quickstart_setup
771
1065
  ;;
772
1066
  start)
1067
+ validate_configuration
773
1068
  require_command curl
774
1069
  ensure_docker_packages
775
1070
  start_docker_daemon
@@ -796,6 +1091,7 @@ main() {
796
1091
  run_integration_tests
797
1092
  ;;
798
1093
  verify)
1094
+ validate_configuration
799
1095
  require_command curl
800
1096
  ensure_docker_packages
801
1097
  start_docker_daemon