@fairmint/canton-node-sdk 0.0.224 → 0.0.226

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.
@@ -1,7 +1,22 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- PACKAGE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
4
+ resolve_script_dir() {
5
+ local source="$1"
6
+ local dir=""
7
+
8
+ while [[ -L "${source}" ]]; do
9
+ dir="$(cd -P "$(dirname "${source}")" && pwd)"
10
+ source="$(readlink "${source}")"
11
+ if [[ "${source}" != /* ]]; then
12
+ source="${dir}/${source}"
13
+ fi
14
+ done
15
+
16
+ cd -P "$(dirname "${source}")" && pwd
17
+ }
18
+
19
+ PACKAGE_ROOT="$(cd "$(resolve_script_dir "${BASH_SOURCE[0]}")/.." && pwd)"
5
20
  LOCALNET_SCRIPT="${PACKAGE_ROOT}/scripts/localnet-cloud.sh"
6
21
  DEFAULT_QUICKSTART_REPO="https://github.com/digital-asset/cn-quickstart.git"
7
22
  DEFAULT_QUICKSTART_REF="46201cd27ee16c99f78ab97b7e6513c56f28e004"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairmint/canton-node-sdk",
3
- "version": "0.0.224",
3
+ "version": "0.0.226",
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
@@ -308,25 +321,276 @@ ensure_hosts_entries() {
308
321
  fi
309
322
  }
310
323
 
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
324
+ set_quickstart_env_value() {
325
+ local file="$1"
326
+ local key="$2"
327
+ local value="$3"
328
+
329
+ if [[ ! -f "${file}" ]]; then
330
+ return
331
+ fi
332
+
333
+ if grep -Eq "^${key}=" "${file}"; then
334
+ KEY="${key}" VALUE="${value}" perl -0pi -e '
335
+ my $key = $ENV{"KEY"};
336
+ my $value = $ENV{"VALUE"};
337
+ s/^\Q$key\E=.*/$key=$value/m;
338
+ ' "${file}"
339
+ else
340
+ printf '\n%s=%s\n' "${key}" "${value}" >>"${file}"
341
+ fi
342
+ }
343
+
344
+ patch_quickstart_canton_healthcheck() {
345
+ local healthcheck="${QUICKSTART_DIR}/docker/modules/localnet/docker/canton/health-check.sh"
346
+
347
+ if [[ ! -f "${healthcheck}" ]]; then
348
+ log "Canton quickstart healthcheck not found: ${healthcheck}"
349
+ exit 1
350
+ fi
351
+
352
+ cat >"${healthcheck}" <<'EOF'
353
+ #!/bin/bash
354
+ set -eou pipefail
355
+
356
+ http_check() {
357
+ local port="$1"
358
+ local path="$2"
359
+ local status=""
360
+
361
+ echo "Checking http://localhost:${port}${path}"
362
+ exec 3<>"/dev/tcp/127.0.0.1/${port}"
363
+ printf 'GET %s HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' "${path}" >&3
364
+ IFS=$'\r' read -r status <&3 || true
365
+ exec 3<&-
366
+ exec 3>&-
367
+
368
+ case "${status}" in
369
+ HTTP/*\ 2*) ;;
370
+ *)
371
+ echo "Unexpected status from ${port}${path}: ${status}"
372
+ return 1
373
+ ;;
374
+ esac
375
+ }
376
+
377
+ if [ "${APP_USER_PROFILE:-off}" = "on" ]; then
378
+ http_check "2${CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX}" "/health"
379
+ fi
380
+ if [ "${APP_PROVIDER_PROFILE:-off}" = "on" ]; then
381
+ http_check "3${CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX}" "/health"
382
+ fi
383
+ if [ "${SV_PROFILE:-off}" = "on" ]; then
384
+ http_check "4${CANTON_HTTP_HEALTHCHECK_PORT_SUFFIX}" "/health"
385
+ fi
386
+ EOF
387
+ chmod +x "${healthcheck}"
388
+ }
389
+
390
+ patch_quickstart_splice_healthcheck() {
391
+ local healthcheck="${QUICKSTART_DIR}/docker/modules/localnet/docker/splice/health-check.sh"
392
+
393
+ if [[ ! -f "${healthcheck}" ]]; then
394
+ log "Splice quickstart healthcheck not found: ${healthcheck}"
395
+ exit 1
396
+ fi
397
+
398
+ cat >"${healthcheck}" <<'EOF'
399
+ #!/bin/bash
400
+ set -eou pipefail
401
+
402
+ http_check() {
403
+ local port="$1"
404
+ local path="$2"
405
+ local status=""
406
+
407
+ echo "Checking http://localhost:${port}${path}"
408
+ exec 3<>"/dev/tcp/127.0.0.1/${port}"
409
+ printf 'GET %s HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' "${path}" >&3
410
+ IFS=$'\r' read -r status <&3 || true
411
+ exec 3<&-
412
+ exec 3>&-
413
+
414
+ case "${status}" in
415
+ HTTP/*\ 2*) ;;
416
+ *)
417
+ echo "Unexpected status from ${port}${path}: ${status}"
418
+ return 1
419
+ ;;
420
+ esac
421
+ }
422
+
423
+ if [ "${APP_USER_PROFILE:-off}" = "on" ]; then
424
+ http_check "2${VALIDATOR_ADMIN_API_PORT_SUFFIX}" "/api/validator/readyz"
425
+ fi
426
+ if [ "${APP_PROVIDER_PROFILE:-off}" = "on" ]; then
427
+ http_check "3${VALIDATOR_ADMIN_API_PORT_SUFFIX}" "/api/validator/readyz"
428
+ fi
429
+ if [ "${SV_PROFILE:-off}" = "on" ]; then
430
+ http_check "4${VALIDATOR_ADMIN_API_PORT_SUFFIX}" "/api/validator/readyz"
431
+ http_check "5012" "/api/scan/readyz"
432
+ http_check "5014" "/api/sv/readyz"
433
+ fi
434
+ EOF
435
+ chmod +x "${healthcheck}"
436
+ }
437
+
438
+ configure_quickstart_localnet() {
439
+ local env_file=""
440
+ local canton_conf="${QUICKSTART_DIR}/docker/modules/localnet/conf/canton/app.conf"
441
+
442
+ for env_file in "${QUICKSTART_DIR}/.env" "${QUICKSTART_DIR}/.env.local"; do
443
+ if [[ -n "${LOCALNET_SPLICE_VERSION}" ]]; then
444
+ set_quickstart_env_value "${env_file}" "SPLICE_VERSION" "${LOCALNET_SPLICE_VERSION}"
445
+ fi
446
+ if [[ -n "${LOCALNET_SCRIBE_VERSION}" ]]; then
447
+ set_quickstart_env_value "${env_file}" "SCRIBE_VERSION" "${LOCALNET_SCRIBE_VERSION}"
448
+ fi
449
+ if [[ -n "${CANTON_LOCALNET_DAML_RUNTIME_VERSION:-}" ]]; then
450
+ set_quickstart_env_value "${env_file}" "DAML_RUNTIME_VERSION" "${CANTON_LOCALNET_DAML_RUNTIME_VERSION}"
451
+ fi
452
+ done
453
+
454
+ if [[ ! -f "${canton_conf}" ]]; then
455
+ log "Canton quickstart config not found: ${canton_conf}"
456
+ exit 1
457
+ fi
458
+
459
+ if [[ -n "${LOCALNET_PROTOCOL_VERSION}" ]]; then
460
+ PROTOCOL_VERSION="${LOCALNET_PROTOCOL_VERSION}" perl -0pi -e '
461
+ my $protocol_version = $ENV{"PROTOCOL_VERSION"};
462
+ s/initial-protocol-version = \d+/initial-protocol-version = $protocol_version/g;
463
+ s/(non-standard-config = yes\n)(?![[:space:]]*alpha-version-support = yes)/$1 alpha-version-support = yes\n/;
464
+ s/(initial-protocol-version = \d+\n)(?![[:space:]]*alpha-version-support = yes)/$1 alpha-version-support = yes\n/;
465
+ ' "${canton_conf}"
466
+ fi
467
+
468
+ patch_quickstart_canton_healthcheck
469
+ patch_quickstart_splice_healthcheck
470
+ }
471
+
472
+ requested_auth_mode() {
473
+ printf '%s' "${CANTON_LOCALNET_AUTH_MODE:-oauth2}"
474
+ }
475
+
476
+ quickstart_env_value() {
477
+ local key="$1"
478
+ local parsed_value=""
479
+
480
+ if [[ -f "${QUICKSTART_DIR}/.env.local" ]]; then
481
+ parsed_value="$(awk -F= -v key="${key}" '
482
+ $0 ~ /^[[:space:]]*#/ { next }
483
+ $1 == key {
484
+ value = substr($0, index($0, "=") + 1)
485
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
486
+ gsub(/^"|"$/, "", value)
487
+ gsub(/^'\''|'\''$/, "", value)
488
+ parsed = value
489
+ }
490
+ END {
491
+ if (parsed != "") {
492
+ print parsed
493
+ }
494
+ }
495
+ ' "${QUICKSTART_DIR}/.env.local")"
496
+ fi
497
+
498
+ printf '%s' "${parsed_value}"
499
+ }
500
+
501
+ configured_auth_mode() {
502
+ quickstart_env_value "AUTH_MODE"
503
+ }
504
+
505
+ quickstart_profile_config_complete() {
506
+ local key=""
507
+
508
+ for key in OBSERVABILITY_ENABLED AUTH_MODE PARTY_HINT TEST_MODE; do
509
+ if [[ -z "$(quickstart_env_value "${key}")" ]]; then
510
+ return 1
511
+ fi
512
+ done
513
+
514
+ return 0
515
+ }
516
+
517
+ verify_configured_auth_mode() {
518
+ local expected_auth_mode="$1"
519
+ local actual_auth_mode=""
520
+
521
+ actual_auth_mode="$(configured_auth_mode)"
522
+ if [[ "${actual_auth_mode}" != "${expected_auth_mode}" ]]; then
523
+ log "cn-quickstart setup did not configure AUTH_MODE=${expected_auth_mode}; found '${actual_auth_mode:-unset}'."
524
+ exit 1
525
+ fi
526
+ }
527
+
528
+ current_auth_mode() {
529
+ local configured=""
530
+
531
+ configured="$(configured_auth_mode)"
532
+ if [[ -n "${configured}" ]]; then
533
+ printf '%s' "${configured}"
534
+ return
535
+ fi
536
+
537
+ requested_auth_mode
538
+ }
539
+
540
+ run_quickstart_setup() {
541
+ local auth_mode=""
542
+
543
+ auth_mode="$(requested_auth_mode)"
544
+ case "${auth_mode}" in
545
+ shared-secret)
546
+ log "Running cn-quickstart setup (shared-secret mode)..."
547
+ (
548
+ cd "${QUICKSTART_DIR}"
549
+ printf 'Y\nn\n\n' | make setup || true
550
+ )
551
+ verify_configured_auth_mode "shared-secret"
552
+ ;;
553
+ oauth2)
316
554
  log "Running cn-quickstart setup (OAuth2 enabled)..."
317
555
  (
318
556
  cd "${QUICKSTART_DIR}"
319
557
  # Match CI behavior first, then fall back to prompt-based answers for newer setup flows.
320
558
  echo "2" | make setup || true
321
- if ! grep -Eq '^AUTH_MODE=oauth2$' "${QUICKSTART_DIR}/.env.local" 2>/dev/null; then
559
+ if [[ "$(configured_auth_mode)" != "oauth2" ]]; then
322
560
  printf 'y\ny\n\nn\n' | make setup
323
561
  fi
324
562
  )
325
- fi
563
+ verify_configured_auth_mode "oauth2"
564
+ ;;
565
+ *)
566
+ log "Unsupported CANTON_LOCALNET_AUTH_MODE: ${auth_mode}"
567
+ exit 1
568
+ ;;
569
+ esac
570
+ }
571
+
572
+ quickstart_setup() {
573
+ if [[ ! -f "${QUICKSTART_DIR}/.env.local" ]]; then
574
+ run_quickstart_setup
575
+ elif ! quickstart_profile_config_complete; then
576
+ log "Regenerating incomplete cn-quickstart config."
577
+ rm -f "${QUICKSTART_DIR}/.env.local"
578
+ run_quickstart_setup
579
+ elif [[ -n "${CANTON_LOCALNET_AUTH_MODE:-}" && "$(configured_auth_mode)" != "${CANTON_LOCALNET_AUTH_MODE}" ]]; then
580
+ log "Regenerating cn-quickstart config for CANTON_LOCALNET_AUTH_MODE=${CANTON_LOCALNET_AUTH_MODE}."
581
+ rm -f "${QUICKSTART_DIR}/.env.local"
582
+ run_quickstart_setup
326
583
  else
327
584
  log "Reusing existing ${QUICKSTART_DIR}/.env.local."
328
585
  fi
329
586
 
587
+ if [[ ! -f "${QUICKSTART_DIR}/.env.local" ]]; then
588
+ log "cn-quickstart setup failed: ${QUICKSTART_DIR}/.env.local was not created."
589
+ exit 1
590
+ fi
591
+
592
+ configure_quickstart_localnet
593
+
330
594
  if quickstart_infra_only_enabled; then
331
595
  log "Skipping Daml SDK install for infrastructure-only localnet."
332
596
  return
@@ -343,37 +607,6 @@ quickstart_setup() {
343
607
  fi
344
608
  }
345
609
 
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
610
  quickstart_fast_start_enabled() {
378
611
  is_truthy "${CANTON_LOCALNET_FAST_START:-true}"
379
612
  }
@@ -448,20 +681,37 @@ stop_infra_only_localnet() {
448
681
  }
449
682
 
450
683
  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
684
+ local auth_mode=""
685
+ local code=""
686
+ local ledger_code=""
687
+
688
+ auth_mode="$(current_auth_mode)"
689
+
690
+ if [[ "${auth_mode}" == "oauth2" ]]; then
691
+ log "Waiting for Keycloak..."
692
+ for _ in $(seq 1 30); do
693
+ if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
694
+ break
695
+ fi
696
+ sleep 2
697
+ done
698
+ if ! curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
699
+ log "Keycloak did not become ready."
700
+ exit 1
455
701
  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
702
+ else
703
+ log "Checking for Keycloak (optional in ${auth_mode} mode)..."
704
+ for _ in $(seq 1 5); do
705
+ if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
706
+ log "Keycloak is ready."
707
+ break
708
+ fi
709
+ sleep 2
710
+ done
461
711
  fi
462
712
 
463
713
  log "Waiting for Validator API..."
464
- for _ in $(seq 1 30); do
714
+ for _ in $(seq 1 "${VALIDATOR_READY_ATTEMPTS}"); do
465
715
  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
716
  if [[ "${code}" == "200" || "${code}" == "401" ]]; then
467
717
  break
@@ -475,7 +725,7 @@ wait_for_services() {
475
725
  fi
476
726
 
477
727
  log "Waiting for Scan API..."
478
- for _ in $(seq 1 30); do
728
+ for _ in $(seq 1 "${SCAN_READY_ATTEMPTS}"); do
479
729
  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
730
  break
481
731
  fi
@@ -486,6 +736,20 @@ wait_for_services() {
486
736
  exit 1
487
737
  fi
488
738
 
739
+ log "Waiting for Ledger JSON API..."
740
+ for _ in $(seq 1 60); do
741
+ 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)"
742
+ if [[ "${ledger_code}" == "200" || "${ledger_code}" == "401" ]]; then
743
+ break
744
+ fi
745
+ sleep 2
746
+ done
747
+ 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)"
748
+ if [[ "${ledger_code}" != "200" && "${ledger_code}" != "401" ]]; then
749
+ log "Ledger JSON API did not become ready (HTTP ${ledger_code})."
750
+ exit 1
751
+ fi
752
+
489
753
  log "All localnet services are ready."
490
754
  }
491
755
 
@@ -592,6 +856,7 @@ status_localnet() {
592
856
  keycloak_ok="no"
593
857
  validator_ok="no"
594
858
  scan_ok="no"
859
+ ledger_ok="no"
595
860
  if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
596
861
  keycloak_ok="yes"
597
862
  fi
@@ -602,10 +867,15 @@ status_localnet() {
602
867
  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
868
  scan_ok="yes"
604
869
  fi
870
+ 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)"
871
+ if [[ "${ledger_code}" == "200" || "${ledger_code}" == "401" ]]; then
872
+ ledger_ok="yes"
873
+ fi
605
874
 
606
875
  printf 'Keycloak ready: %s\n' "${keycloak_ok}"
607
876
  printf 'Validator ready: %s (HTTP %s)\n' "${validator_ok}" "${validator_code:-n/a}"
608
877
  printf 'Scan ready: %s\n' "${scan_ok}"
878
+ printf 'Ledger JSON API ready: %s (HTTP %s)\n' "${ledger_ok}" "${ledger_code:-n/a}"
609
879
  }
610
880
 
611
881
  show_localnet_logs() {
@@ -642,13 +912,20 @@ show_localnet_logs() {
642
912
  }
643
913
 
644
914
  run_smoke() {
915
+ local auth_mode=""
645
916
  local validator_code=""
917
+ local ledger_code=""
646
918
 
647
919
  log "Running localnet smoke checks..."
920
+ auth_mode="$(current_auth_mode)"
648
921
 
649
- if ! curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
922
+ if curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time "${CURL_MAX_TIME}" -fsS http://localhost:8082/realms/AppProvider >/dev/null 2>&1; then
923
+ log "Keycloak is reachable."
924
+ elif [[ "${auth_mode}" == "oauth2" ]]; then
650
925
  log "Keycloak is not reachable."
651
926
  exit 1
927
+ else
928
+ log "Keycloak not detected (expected in ${auth_mode} mode)."
652
929
  fi
653
930
 
654
931
  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 +939,12 @@ run_smoke() {
662
939
  exit 1
663
940
  fi
664
941
 
942
+ 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)"
943
+ if [[ "${ledger_code}" != "200" && "${ledger_code}" != "401" ]]; then
944
+ log "Ledger JSON API is not reachable (HTTP ${ledger_code})."
945
+ exit 1
946
+ fi
947
+
665
948
  log "Smoke checks passed."
666
949
  }
667
950
 
@@ -751,6 +1034,7 @@ Environment:
751
1034
  CANTON_LOCALNET_FAST_START=true|false Enable fast startup path (default: true)
752
1035
  CANTON_LOCALNET_FORCE_FULL_START=true|false Force full startup with rebuild
753
1036
  CANTON_LOCALNET_INFRA_ONLY=true|false Start only LocalNet + Keycloak infrastructure
1037
+ CANTON_LOCALNET_AUTH_MODE=oauth2|shared-secret
754
1038
  CANTON_LOCALNET_QUICKSTART_DIR=<path> Use an existing cn-quickstart/quickstart directory
755
1039
  USAGE
756
1040
  }
@@ -763,6 +1047,7 @@ main() {
763
1047
 
764
1048
  case "$1" in
765
1049
  setup)
1050
+ validate_configuration
766
1051
  ensure_docker_packages
767
1052
  start_docker_daemon
768
1053
  ensure_submodules
@@ -770,6 +1055,7 @@ main() {
770
1055
  quickstart_setup
771
1056
  ;;
772
1057
  start)
1058
+ validate_configuration
773
1059
  require_command curl
774
1060
  ensure_docker_packages
775
1061
  start_docker_daemon
@@ -796,6 +1082,7 @@ main() {
796
1082
  run_integration_tests
797
1083
  ;;
798
1084
  verify)
1085
+ validate_configuration
799
1086
  require_command curl
800
1087
  ensure_docker_packages
801
1088
  start_docker_daemon