@caatinga/core 0.2.0 → 0.2.1

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) Kaleido contributors
3
+ Copyright (c) Caatinga contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,33 +1,107 @@
1
1
  # @caatinga/core
2
2
 
3
- Core config, artifacts, command orchestration, and error primitives for Caatinga.
3
+ Core config, artifacts, command orchestration, and error primitives for the Caatinga / Stellar Soroban toolkit.
4
4
 
5
- ## Supported Use
5
+ ## Supported use
6
6
 
7
- `@caatinga/core` is primarily an internal package for the Caatinga CLI and templates; direct use beyond the documented config/template helpers is advanced and less stable than the CLI contract.
7
+ `@caatinga/core` is primarily an internal package for `@caatinga/cli` and official templates. Direct use beyond the documented config and template surface is advanced and less stable than the CLI contract.
8
8
 
9
- ## What Is In This Package
9
+ Most applications should install `@caatinga/cli` or `@caatinga/client` instead of depending on core directly.
10
10
 
11
- - `defineConfig` for Caatinga project config files generated by Caatinga templates
12
- - config loading and validation
13
- - artifact resolution and update helpers
14
- - shared command orchestration primitives
15
- - public `CAATINGA_*` error foundations used by higher-level packages
11
+ ## What is in this package
16
12
 
17
- ## Consumer Guidance
13
+ ### Config and networks
18
14
 
19
- If you want the supported end-user workflow, use `@caatinga/cli`.
15
+ - `defineConfig` for `caatinga.config.ts` in generated projects
16
+ - `loadConfig`, `CaatingaConfigSchema`, and related types
17
+ - `WELL_KNOWN_NETWORKS`, `resolveNetwork`
20
18
 
21
- If you are authoring a Caatinga project, importing `defineConfig` from `@caatinga/core` in `caatinga.config.ts` is part of the intended template/config surface.
19
+ ### Artifacts
22
20
 
23
- Reach for broader `@caatinga/core` APIs directly only when you are building advanced tooling on top of Caatinga internals and are prepared to track lower-level package changes more closely than a CLI consumer.
21
+ - `readArtifacts`, `writeArtifacts`, `createInitialArtifacts`, `updateArtifact`
22
+ - `CaatingaArtifactsSchema` and artifact types (`caatinga.artifacts.json`, schema version `1`)
24
23
 
25
- ## Stability Posture
24
+ ### Contracts and Stellar CLI orchestration
26
25
 
27
- Being published does not make every export a first-class end-user contract. The stable consumer surfaces are:
26
+ - `buildContract`, `deployContract`, `deployContractGraph`
27
+ - `buildDependencyGraph`, `resolveDeployOrder`, `resolveDeployArgs`
28
+ - `generateBindings`, `invokeContract`, `parseInvokeTarget`
29
+ - `resolveContract`, `parseContractId`
30
+ - Stellar CLI version constants and guards (`STELLAR_CLI_MIN_VERSION`, `STELLAR_CLI_TESTED_MAX_VERSION`, `assertSupportedStellarCliVersion`)
31
+
32
+ ### Templates
33
+
34
+ - `createProjectFromTemplate` and `TemplateManifestSchema`
35
+ - compatibility checks against `CAATINGA_CORE_VERSION` and template manifests
36
+
37
+ ### Shell and errors
38
+
39
+ - `runCommand`, `checkBinary`
40
+ - `CaatingaError`, `CaatingaErrorCode`, `toCaatingaError`
41
+ - `CAATINGA_CORE_VERSION`
42
+
43
+ ## Intended config surface
44
+
45
+ Generated projects import `defineConfig` from `@caatinga/core`:
46
+
47
+ ```ts
48
+ import { defineConfig } from "@caatinga/core";
49
+
50
+ export default defineConfig({
51
+ project: "my-dapp",
52
+ defaultNetwork: "testnet",
53
+ contracts: {
54
+ counter: {
55
+ path: "./contracts/counter",
56
+ wasm: "./contracts/counter/target/wasm32-unknown-unknown/release/counter.wasm"
57
+ }
58
+ },
59
+ networks: {
60
+ testnet: {
61
+ rpcUrl: "https://soroban-testnet.stellar.org",
62
+ networkPassphrase: "Test SDF Network ; September 2015"
63
+ }
64
+ },
65
+ frontend: {
66
+ framework: "vite-react",
67
+ bindingsOutput: "./contracts/generated"
68
+ }
69
+ });
70
+ ```
71
+
72
+ Multi-contract projects may declare `dependsOn` and `${contracts.<name>.contractId}` placeholders in `deployArgs`; `deployContractGraph` resolves and deploys dependencies before dependents.
73
+
74
+ ## Consumer guidance
75
+
76
+ | Goal | Package |
77
+ | --- | --- |
78
+ | End-user CLI workflow | `@caatinga/cli` |
79
+ | Browser / Node client over generated bindings | `@caatinga/client` |
80
+ | `caatinga.config.ts` in a Caatinga project | `defineConfig` from `@caatinga/core` |
81
+ | Custom tooling on deploy graphs, artifacts, or Stellar CLI orchestration | `@caatinga/core` (advanced; track releases closely) |
82
+
83
+ ## Error codes
84
+
85
+ Core owns the canonical `CAATINGA_*` enum used by CLI, client, and templates. Automation should match on codes, not message text.
86
+
87
+ Common codes surfaced through core-backed commands:
88
+
89
+ - config and artifacts: `CAATINGA_CONFIG_NOT_FOUND`, `CAATINGA_INVALID_CONFIG`, `CAATINGA_ARTIFACT_NOT_FOUND`, `CAATINGA_ARTIFACT_INVALID`
90
+ - Stellar CLI: `CAATINGA_STELLAR_CLI_NOT_FOUND`, `CAATINGA_UNSUPPORTED_CLI_VERSION`, `CAATINGA_UNTESTED_CLI_VERSION`
91
+ - contracts: `CAATINGA_BUILD_FAILED`, `CAATINGA_DEPLOY_FAILED`, `CAATINGA_BINDINGS_FAILED`, `CAATINGA_INVOKE_FAILED`
92
+ - dependencies: `CAATINGA_CONTRACT_DEPENDENCY_NOT_FOUND`, `CAATINGA_CONTRACT_DEPENDENCY_CYCLE`, `CAATINGA_DEPLOY_ARG_PLACEHOLDER_UNRESOLVED`
93
+ - templates: `CAATINGA_TEMPLATE_MANIFEST_NOT_FOUND`, `CAATINGA_TEMPLATE_INCOMPATIBLE`
94
+
95
+ Full table: [docs/errors.md](https://github.com/Dione-b/caatinga/blob/main/docs/errors.md)
96
+
97
+ ## Stability posture
98
+
99
+ Being published does not make every export a first-class end-user contract. Stable consumer surfaces are:
28
100
 
29
101
  - the documented CLI workflow in `@caatinga/cli`
30
102
  - the documented `@caatinga/client` APIs
31
103
  - the narrow `@caatinga/core` config/template surface used by generated projects, including `defineConfig`
32
104
 
33
105
  Direct `@caatinga/core` usage should be treated as advanced integration with a narrower support posture than the CLI package.
106
+
107
+ Further reference: [architecture](https://github.com/Dione-b/caatinga/blob/main/docs/architecture.md), [config](https://github.com/Dione-b/caatinga/blob/main/docs/config.md), [templates](https://github.com/Dione-b/caatinga/blob/main/docs/templates.md).
package/dist/index.cjs CHANGED
@@ -135,7 +135,7 @@ function toCaatingaError(error) {
135
135
  }
136
136
 
137
137
  // src/version.ts
138
- var CAATINGA_CORE_VERSION = "0.2.0";
138
+ var CAATINGA_CORE_VERSION = "0.2.1";
139
139
 
140
140
  // src/config/config.schema.ts
141
141
  var import_zod = require("zod");
@@ -553,6 +553,85 @@ async function buildContract(options) {
553
553
  // src/contracts/deploy-contract.ts
554
554
  var import_node_path5 = __toESM(require("path"), 1);
555
555
 
556
+ // src/stellar-cli/recover-deploy-contract-id.ts
557
+ var TX_HASH_REGEX = /Transaction hash is ([a-f0-9]{64})/i;
558
+ var DEPLOY_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
559
+ var HORIZON_URL_BY_PASSPHRASE = {
560
+ "Test SDF Network ; September 2015": "https://horizon-testnet.stellar.org",
561
+ "Public Global Stellar Network ; September 2015": "https://horizon.stellar.org"
562
+ };
563
+ function isLikelyPublicKeySource(source) {
564
+ return /^G[A-Z2-7]{55}$/.test(source);
565
+ }
566
+ function decimalSaltToHex(salt) {
567
+ return BigInt(salt).toString(16).padStart(64, "0");
568
+ }
569
+ function resolveHorizonUrl(network) {
570
+ const horizonUrl = HORIZON_URL_BY_PASSPHRASE[network.networkPassphrase];
571
+ if (!horizonUrl) {
572
+ throw new CaatingaError(
573
+ `No Horizon URL mapping for network passphrase "${network.networkPassphrase}".`,
574
+ CaatingaErrorCode.NETWORK_NOT_FOUND,
575
+ "Use testnet or mainnet, or extend Caatinga network metadata."
576
+ );
577
+ }
578
+ return horizonUrl;
579
+ }
580
+ async function fetchCreateContractSalt(horizonUrl, transactionHash, fetchImpl = fetch) {
581
+ const response = await fetchImpl(`${horizonUrl}/transactions/${transactionHash}/operations`);
582
+ if (!response.ok) {
583
+ return null;
584
+ }
585
+ const body = await response.json();
586
+ const operation = body._embedded?.records?.find(
587
+ (record) => record.transaction_successful === true && record.type === "invoke_host_function" && record.function === "HostFunctionTypeHostFunctionTypeCreateContract" && typeof record.salt === "string"
588
+ );
589
+ return operation?.salt ?? null;
590
+ }
591
+ async function resolveContractIdFromDeploySalt(options) {
592
+ const saltHex = decimalSaltToHex(options.salt);
593
+ const result = await runCommand("stellar", [
594
+ "contract",
595
+ "id",
596
+ "wasm",
597
+ "--salt",
598
+ saltHex,
599
+ "--source-account",
600
+ options.source,
601
+ "--rpc-url",
602
+ options.network.rpcUrl,
603
+ "--network-passphrase",
604
+ options.network.networkPassphrase
605
+ ], {
606
+ cwd: options.cwd,
607
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
608
+ skipStellarVersionCheck: true
609
+ });
610
+ return parseContractId(result.all || `${result.stdout}
611
+ ${result.stderr}`);
612
+ }
613
+ async function tryRecoverContractIdFromDeployFailure(options) {
614
+ if (!DEPLOY_SIGNING_FAILURE_REGEX.test(options.output)) {
615
+ return null;
616
+ }
617
+ const hashMatch = options.output.match(TX_HASH_REGEX);
618
+ if (!hashMatch) {
619
+ return null;
620
+ }
621
+ const horizonUrl = resolveHorizonUrl(options.network);
622
+ const salt = await fetchCreateContractSalt(horizonUrl, hashMatch[1], options.fetchImpl);
623
+ if (!salt) {
624
+ return null;
625
+ }
626
+ return resolveContractIdFromDeploySalt({
627
+ salt,
628
+ source: options.source,
629
+ network: options.network,
630
+ cwd: options.cwd,
631
+ allowUntestedStellarCli: options.allowUntestedStellarCli
632
+ });
633
+ }
634
+
556
635
  // src/contracts/dependency-graph.ts
557
636
  function buildDependencyGraph(contracts) {
558
637
  const graph = {};
@@ -599,14 +678,21 @@ function assertSafeSourceAccount(source) {
599
678
  throw new CaatingaError(
600
679
  "A source account or Stellar CLI identity is required.",
601
680
  CaatingaErrorCode.SOURCE_ACCOUNT_REQUIRED,
602
- "Pass --source alice or --source G...; do not pass secret keys or seed phrases."
681
+ "Pass a Stellar CLI identity alias, for example: --source alice"
603
682
  );
604
683
  }
605
684
  if (source.startsWith("S") || source.trim().includes(" ")) {
606
685
  throw new CaatingaError(
607
686
  "Refusing to accept a likely secret key or seed phrase as --source.",
608
687
  CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
609
- "Use a Stellar CLI identity alias or public account address instead."
688
+ "Use a Stellar CLI identity alias instead, for example: --source alice"
689
+ );
690
+ }
691
+ if (isLikelyPublicKeySource(source)) {
692
+ throw new CaatingaError(
693
+ `Public account address cannot sign transactions: ${source}`,
694
+ CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
695
+ "Use a Stellar CLI identity with a secret key. Example: stellar keys generate alice --fund --network testnet, then --source alice"
610
696
  );
611
697
  }
612
698
  return source;
@@ -685,14 +771,39 @@ async function deployContract(options) {
685
771
  network.config.networkPassphrase,
686
772
  ...constructorArgs
687
773
  ];
688
- const result = await runCommand("stellar", stellarArgs, {
689
- cwd,
690
- allowUntestedStellarCli: options.allowUntestedStellarCli,
691
- failureCode: CaatingaErrorCode.DEPLOY_FAILED
692
- });
693
- const output = result.all || `${result.stdout}
774
+ let output = "";
775
+ let contractId;
776
+ try {
777
+ const result = await runCommand("stellar", stellarArgs, {
778
+ cwd,
779
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
780
+ failureCode: CaatingaErrorCode.DEPLOY_FAILED
781
+ });
782
+ output = result.all || `${result.stdout}
694
783
  ${result.stderr}`;
695
- const contractId = parseContractId(output);
784
+ contractId = parseContractId(output);
785
+ } catch (error) {
786
+ if (!(error instanceof CaatingaError) || error.code !== CaatingaErrorCode.DEPLOY_FAILED) {
787
+ throw error;
788
+ }
789
+ const recoveredContractId = await tryRecoverContractIdFromDeployFailure({
790
+ output: `${error.message}
791
+ ${error.hint ?? ""}`,
792
+ source,
793
+ network: network.config,
794
+ cwd,
795
+ allowUntestedStellarCli: options.allowUntestedStellarCli
796
+ });
797
+ if (!recoveredContractId) {
798
+ throw error;
799
+ }
800
+ contractId = recoveredContractId;
801
+ output = [
802
+ error.hint ?? "",
803
+ "Caatinga recovered the contract ID from the on-chain deploy transaction.",
804
+ `Contract ID: ${contractId}`
805
+ ].filter(Boolean).join("\n");
806
+ }
696
807
  const wasmHash = await hashWasm(contract.wasmPath);
697
808
  const dependencyGraph = buildDependencyGraph(options.config.contracts);
698
809
  const dependencies = options.dependencies ?? contract.config.dependsOn;
@@ -946,9 +1057,45 @@ var TemplateManifestSchema = import_zod5.z.object({
946
1057
  artifacts: import_zod5.z.string().default("caatinga.artifacts.json")
947
1058
  })
948
1059
  });
1060
+ function defaultCompatibleCoreRange(coreVersion = CAATINGA_CORE_VERSION) {
1061
+ const version = import_semver2.default.valid(import_semver2.default.coerce(coreVersion));
1062
+ if (!version) {
1063
+ throw new Error(`Invalid core version: ${coreVersion}`);
1064
+ }
1065
+ return `^${version}`;
1066
+ }
949
1067
  function isCoreVersionCompatible(range, coreVersion = CAATINGA_CORE_VERSION) {
950
1068
  return import_semver2.default.satisfies(coreVersion, range);
951
1069
  }
1070
+ function getTemplateCompatibilityIssue(manifest, coreVersion = CAATINGA_CORE_VERSION) {
1071
+ if (manifest.caatinga.templateVersion !== CURRENT_TEMPLATE_VERSION) {
1072
+ return {
1073
+ kind: "template-version",
1074
+ expected: CURRENT_TEMPLATE_VERSION,
1075
+ actual: manifest.caatinga.templateVersion
1076
+ };
1077
+ }
1078
+ if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore, coreVersion)) {
1079
+ return {
1080
+ kind: "core-range",
1081
+ requiredRange: manifest.caatinga.compatibleCore,
1082
+ runningVersion: coreVersion
1083
+ };
1084
+ }
1085
+ return null;
1086
+ }
1087
+ function formatTemplateCompatibilityMessage(issue) {
1088
+ if (issue.kind === "template-version") {
1089
+ return `Template manifest version ${issue.actual} is not supported; Caatinga requires templateVersion ${issue.expected}.`;
1090
+ }
1091
+ return `Template requires @caatinga/core ${issue.requiredRange} but running ${issue.runningVersion}.`;
1092
+ }
1093
+ function formatTemplateCompatibilityHint(issue) {
1094
+ if (issue.kind === "template-version") {
1095
+ return "Use a template built for this Caatinga release or upgrade Caatinga.";
1096
+ }
1097
+ return `Use a template with compatibleCore ${defaultCompatibleCoreRange(issue.runningVersion)} or install a matching Caatinga version.`;
1098
+ }
952
1099
 
953
1100
  // src/templates/create-project-from-template.ts
954
1101
  async function createProjectFromTemplate(options) {
@@ -979,18 +1126,12 @@ async function readTemplateManifest(templateDir) {
979
1126
  try {
980
1127
  const rawManifest = await (0, import_promises6.readFile)(manifestPath, "utf8");
981
1128
  const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
982
- if (manifest.caatinga.templateVersion !== CURRENT_TEMPLATE_VERSION) {
983
- throw new CaatingaError(
984
- "Template is not compatible with this Caatinga version.",
985
- CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
986
- "Use a compatible template version or upgrade Caatinga."
987
- );
988
- }
989
- if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore)) {
1129
+ const compatibilityIssue = getTemplateCompatibilityIssue(manifest);
1130
+ if (compatibilityIssue) {
990
1131
  throw new CaatingaError(
991
- "Template is not compatible with this Caatinga version.",
1132
+ formatTemplateCompatibilityMessage(compatibilityIssue),
992
1133
  CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
993
- "Use a compatible template version or upgrade Caatinga."
1134
+ formatTemplateCompatibilityHint(compatibilityIssue)
994
1135
  );
995
1136
  }
996
1137
  return manifest;
package/dist/index.d.cts CHANGED
@@ -52,7 +52,7 @@ declare class CaatingaError extends Error {
52
52
  }
53
53
  declare function toCaatingaError(error: unknown): CaatingaError;
54
54
 
55
- declare const CAATINGA_CORE_VERSION = "0.2.0";
55
+ declare const CAATINGA_CORE_VERSION = "0.2.1";
56
56
 
57
57
  declare const ContractConfigSchema: z.ZodObject<{
58
58
  path: z.ZodString;
package/dist/index.d.ts CHANGED
@@ -52,7 +52,7 @@ declare class CaatingaError extends Error {
52
52
  }
53
53
  declare function toCaatingaError(error: unknown): CaatingaError;
54
54
 
55
- declare const CAATINGA_CORE_VERSION = "0.2.0";
55
+ declare const CAATINGA_CORE_VERSION = "0.2.1";
56
56
 
57
57
  declare const ContractConfigSchema: z.ZodObject<{
58
58
  path: z.ZodString;
package/dist/index.js CHANGED
@@ -66,7 +66,7 @@ function toCaatingaError(error) {
66
66
  }
67
67
 
68
68
  // src/version.ts
69
- var CAATINGA_CORE_VERSION = "0.2.0";
69
+ var CAATINGA_CORE_VERSION = "0.2.1";
70
70
 
71
71
  // src/config/config.schema.ts
72
72
  import { z } from "zod";
@@ -483,6 +483,85 @@ async function buildContract(options) {
483
483
  // src/contracts/deploy-contract.ts
484
484
  import path5 from "path";
485
485
 
486
+ // src/stellar-cli/recover-deploy-contract-id.ts
487
+ var TX_HASH_REGEX = /Transaction hash is ([a-f0-9]{64})/i;
488
+ var DEPLOY_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
489
+ var HORIZON_URL_BY_PASSPHRASE = {
490
+ "Test SDF Network ; September 2015": "https://horizon-testnet.stellar.org",
491
+ "Public Global Stellar Network ; September 2015": "https://horizon.stellar.org"
492
+ };
493
+ function isLikelyPublicKeySource(source) {
494
+ return /^G[A-Z2-7]{55}$/.test(source);
495
+ }
496
+ function decimalSaltToHex(salt) {
497
+ return BigInt(salt).toString(16).padStart(64, "0");
498
+ }
499
+ function resolveHorizonUrl(network) {
500
+ const horizonUrl = HORIZON_URL_BY_PASSPHRASE[network.networkPassphrase];
501
+ if (!horizonUrl) {
502
+ throw new CaatingaError(
503
+ `No Horizon URL mapping for network passphrase "${network.networkPassphrase}".`,
504
+ CaatingaErrorCode.NETWORK_NOT_FOUND,
505
+ "Use testnet or mainnet, or extend Caatinga network metadata."
506
+ );
507
+ }
508
+ return horizonUrl;
509
+ }
510
+ async function fetchCreateContractSalt(horizonUrl, transactionHash, fetchImpl = fetch) {
511
+ const response = await fetchImpl(`${horizonUrl}/transactions/${transactionHash}/operations`);
512
+ if (!response.ok) {
513
+ return null;
514
+ }
515
+ const body = await response.json();
516
+ const operation = body._embedded?.records?.find(
517
+ (record) => record.transaction_successful === true && record.type === "invoke_host_function" && record.function === "HostFunctionTypeHostFunctionTypeCreateContract" && typeof record.salt === "string"
518
+ );
519
+ return operation?.salt ?? null;
520
+ }
521
+ async function resolveContractIdFromDeploySalt(options) {
522
+ const saltHex = decimalSaltToHex(options.salt);
523
+ const result = await runCommand("stellar", [
524
+ "contract",
525
+ "id",
526
+ "wasm",
527
+ "--salt",
528
+ saltHex,
529
+ "--source-account",
530
+ options.source,
531
+ "--rpc-url",
532
+ options.network.rpcUrl,
533
+ "--network-passphrase",
534
+ options.network.networkPassphrase
535
+ ], {
536
+ cwd: options.cwd,
537
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
538
+ skipStellarVersionCheck: true
539
+ });
540
+ return parseContractId(result.all || `${result.stdout}
541
+ ${result.stderr}`);
542
+ }
543
+ async function tryRecoverContractIdFromDeployFailure(options) {
544
+ if (!DEPLOY_SIGNING_FAILURE_REGEX.test(options.output)) {
545
+ return null;
546
+ }
547
+ const hashMatch = options.output.match(TX_HASH_REGEX);
548
+ if (!hashMatch) {
549
+ return null;
550
+ }
551
+ const horizonUrl = resolveHorizonUrl(options.network);
552
+ const salt = await fetchCreateContractSalt(horizonUrl, hashMatch[1], options.fetchImpl);
553
+ if (!salt) {
554
+ return null;
555
+ }
556
+ return resolveContractIdFromDeploySalt({
557
+ salt,
558
+ source: options.source,
559
+ network: options.network,
560
+ cwd: options.cwd,
561
+ allowUntestedStellarCli: options.allowUntestedStellarCli
562
+ });
563
+ }
564
+
486
565
  // src/contracts/dependency-graph.ts
487
566
  function buildDependencyGraph(contracts) {
488
567
  const graph = {};
@@ -529,14 +608,21 @@ function assertSafeSourceAccount(source) {
529
608
  throw new CaatingaError(
530
609
  "A source account or Stellar CLI identity is required.",
531
610
  CaatingaErrorCode.SOURCE_ACCOUNT_REQUIRED,
532
- "Pass --source alice or --source G...; do not pass secret keys or seed phrases."
611
+ "Pass a Stellar CLI identity alias, for example: --source alice"
533
612
  );
534
613
  }
535
614
  if (source.startsWith("S") || source.trim().includes(" ")) {
536
615
  throw new CaatingaError(
537
616
  "Refusing to accept a likely secret key or seed phrase as --source.",
538
617
  CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
539
- "Use a Stellar CLI identity alias or public account address instead."
618
+ "Use a Stellar CLI identity alias instead, for example: --source alice"
619
+ );
620
+ }
621
+ if (isLikelyPublicKeySource(source)) {
622
+ throw new CaatingaError(
623
+ `Public account address cannot sign transactions: ${source}`,
624
+ CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
625
+ "Use a Stellar CLI identity with a secret key. Example: stellar keys generate alice --fund --network testnet, then --source alice"
540
626
  );
541
627
  }
542
628
  return source;
@@ -615,14 +701,39 @@ async function deployContract(options) {
615
701
  network.config.networkPassphrase,
616
702
  ...constructorArgs
617
703
  ];
618
- const result = await runCommand("stellar", stellarArgs, {
619
- cwd,
620
- allowUntestedStellarCli: options.allowUntestedStellarCli,
621
- failureCode: CaatingaErrorCode.DEPLOY_FAILED
622
- });
623
- const output = result.all || `${result.stdout}
704
+ let output = "";
705
+ let contractId;
706
+ try {
707
+ const result = await runCommand("stellar", stellarArgs, {
708
+ cwd,
709
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
710
+ failureCode: CaatingaErrorCode.DEPLOY_FAILED
711
+ });
712
+ output = result.all || `${result.stdout}
624
713
  ${result.stderr}`;
625
- const contractId = parseContractId(output);
714
+ contractId = parseContractId(output);
715
+ } catch (error) {
716
+ if (!(error instanceof CaatingaError) || error.code !== CaatingaErrorCode.DEPLOY_FAILED) {
717
+ throw error;
718
+ }
719
+ const recoveredContractId = await tryRecoverContractIdFromDeployFailure({
720
+ output: `${error.message}
721
+ ${error.hint ?? ""}`,
722
+ source,
723
+ network: network.config,
724
+ cwd,
725
+ allowUntestedStellarCli: options.allowUntestedStellarCli
726
+ });
727
+ if (!recoveredContractId) {
728
+ throw error;
729
+ }
730
+ contractId = recoveredContractId;
731
+ output = [
732
+ error.hint ?? "",
733
+ "Caatinga recovered the contract ID from the on-chain deploy transaction.",
734
+ `Contract ID: ${contractId}`
735
+ ].filter(Boolean).join("\n");
736
+ }
626
737
  const wasmHash = await hashWasm(contract.wasmPath);
627
738
  const dependencyGraph = buildDependencyGraph(options.config.contracts);
628
739
  const dependencies = options.dependencies ?? contract.config.dependsOn;
@@ -876,9 +987,45 @@ var TemplateManifestSchema = z5.object({
876
987
  artifacts: z5.string().default("caatinga.artifacts.json")
877
988
  })
878
989
  });
990
+ function defaultCompatibleCoreRange(coreVersion = CAATINGA_CORE_VERSION) {
991
+ const version = semver2.valid(semver2.coerce(coreVersion));
992
+ if (!version) {
993
+ throw new Error(`Invalid core version: ${coreVersion}`);
994
+ }
995
+ return `^${version}`;
996
+ }
879
997
  function isCoreVersionCompatible(range, coreVersion = CAATINGA_CORE_VERSION) {
880
998
  return semver2.satisfies(coreVersion, range);
881
999
  }
1000
+ function getTemplateCompatibilityIssue(manifest, coreVersion = CAATINGA_CORE_VERSION) {
1001
+ if (manifest.caatinga.templateVersion !== CURRENT_TEMPLATE_VERSION) {
1002
+ return {
1003
+ kind: "template-version",
1004
+ expected: CURRENT_TEMPLATE_VERSION,
1005
+ actual: manifest.caatinga.templateVersion
1006
+ };
1007
+ }
1008
+ if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore, coreVersion)) {
1009
+ return {
1010
+ kind: "core-range",
1011
+ requiredRange: manifest.caatinga.compatibleCore,
1012
+ runningVersion: coreVersion
1013
+ };
1014
+ }
1015
+ return null;
1016
+ }
1017
+ function formatTemplateCompatibilityMessage(issue) {
1018
+ if (issue.kind === "template-version") {
1019
+ return `Template manifest version ${issue.actual} is not supported; Caatinga requires templateVersion ${issue.expected}.`;
1020
+ }
1021
+ return `Template requires @caatinga/core ${issue.requiredRange} but running ${issue.runningVersion}.`;
1022
+ }
1023
+ function formatTemplateCompatibilityHint(issue) {
1024
+ if (issue.kind === "template-version") {
1025
+ return "Use a template built for this Caatinga release or upgrade Caatinga.";
1026
+ }
1027
+ return `Use a template with compatibleCore ${defaultCompatibleCoreRange(issue.runningVersion)} or install a matching Caatinga version.`;
1028
+ }
882
1029
 
883
1030
  // src/templates/create-project-from-template.ts
884
1031
  async function createProjectFromTemplate(options) {
@@ -909,18 +1056,12 @@ async function readTemplateManifest(templateDir) {
909
1056
  try {
910
1057
  const rawManifest = await readFile3(manifestPath, "utf8");
911
1058
  const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
912
- if (manifest.caatinga.templateVersion !== CURRENT_TEMPLATE_VERSION) {
913
- throw new CaatingaError(
914
- "Template is not compatible with this Caatinga version.",
915
- CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
916
- "Use a compatible template version or upgrade Caatinga."
917
- );
918
- }
919
- if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore)) {
1059
+ const compatibilityIssue = getTemplateCompatibilityIssue(manifest);
1060
+ if (compatibilityIssue) {
920
1061
  throw new CaatingaError(
921
- "Template is not compatible with this Caatinga version.",
1062
+ formatTemplateCompatibilityMessage(compatibilityIssue),
922
1063
  CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
923
- "Use a compatible template version or upgrade Caatinga."
1064
+ formatTemplateCompatibilityHint(compatibilityIssue)
924
1065
  );
925
1066
  }
926
1067
  return manifest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caatinga/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Core config, artifacts, command orchestration, and error primitives for Caatinga/Soroban toolkit",
5
5
  "keywords": [
6
6
  "stellar",