@caatinga/core 2.3.1 → 2.4.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/dist/index.cjs CHANGED
@@ -37,16 +37,19 @@ __export(index_exports, {
37
37
  CaatingaConfigSchema: () => CaatingaConfigSchema,
38
38
  CaatingaError: () => CaatingaError,
39
39
  CaatingaErrorCode: () => CaatingaErrorCode,
40
+ READ_CALL_FAILURE_REGEX: () => READ_CALL_FAILURE_REGEX,
40
41
  STELLAR_CLI_LAST_TESTED_VERSION: () => STELLAR_CLI_LAST_TESTED_VERSION,
41
42
  STELLAR_CLI_MIN_VERSION: () => STELLAR_CLI_MIN_VERSION,
42
43
  TemplateManifestSchema: () => TemplateManifestSchema,
43
44
  WELL_KNOWN_NETWORKS: () => WELL_KNOWN_NETWORKS,
44
45
  buildContract: () => buildContract,
45
46
  buildDependencyGraph: () => buildDependencyGraph,
47
+ buildReadCallHint: () => buildReadCallHint,
46
48
  checkBinary: () => checkBinary,
47
49
  checkStellarCliVersion: () => checkStellarCliVersion,
48
50
  collectProjectStatus: () => collectProjectStatus,
49
51
  createInitialArtifacts: () => createInitialArtifacts,
52
+ createMinimalProject: () => createMinimalProject,
50
53
  createProjectFromTemplate: () => createProjectFromTemplate,
51
54
  createZkProject: () => createZkProject,
52
55
  defineConfig: () => defineConfig,
@@ -59,6 +62,8 @@ __export(index_exports, {
59
62
  generateBindings: () => generateBindings,
60
63
  generateBindingsGraph: () => generateBindingsGraph,
61
64
  invokeContract: () => invokeContract,
65
+ isCargoBinMissingFromPath: () => isCargoBinMissingFromPath,
66
+ isReadCallFailure: () => isReadCallFailure,
62
67
  isTransientTestnetSmokeFailure: () => isTransientTestnetSmokeFailure,
63
68
  loadConfig: () => loadConfig,
64
69
  parseContractId: () => parseContractId,
@@ -66,11 +71,13 @@ __export(index_exports, {
66
71
  parseStellarCliVersion: () => parseStellarCliVersion,
67
72
  readArtifacts: () => readArtifacts,
68
73
  readBindingMarker: () => readBindingMarker,
74
+ readContract: () => readContract,
69
75
  resolveContract: () => resolveContract,
70
76
  resolveDefaultContractName: () => resolveDefaultContractName,
71
77
  resolveDeployArgs: () => resolveDeployArgs,
72
78
  resolveDeployOrder: () => resolveDeployOrder,
73
79
  resolveNetwork: () => resolveNetwork,
80
+ resolveSubprocessEnv: () => resolveSubprocessEnv,
74
81
  runCommand: () => runCommand,
75
82
  toCaatingaError: () => toCaatingaError,
76
83
  updateArtifact: () => updateArtifact,
@@ -83,6 +90,7 @@ module.exports = __toCommonJS(index_exports);
83
90
  // src/errors/CaatingaErrorCode.ts
84
91
  var CaatingaErrorCode = {
85
92
  CONFIG_NOT_FOUND: "CAATINGA_CONFIG_NOT_FOUND",
93
+ DEPENDENCIES_NOT_INSTALLED: "CAATINGA_DEPENDENCIES_NOT_INSTALLED",
86
94
  INVALID_CONFIG: "CAATINGA_INVALID_CONFIG",
87
95
  COMMAND_FAILED: "CAATINGA_COMMAND_FAILED",
88
96
  UNEXPECTED_ERROR: "CAATINGA_UNEXPECTED_ERROR",
@@ -187,7 +195,7 @@ function formatCause(cause) {
187
195
  }
188
196
 
189
197
  // src/version.ts
190
- var CAATINGA_CORE_VERSION = "2.3.1";
198
+ var CAATINGA_CORE_VERSION = "2.4.1";
191
199
 
192
200
  // src/config/config.schema.ts
193
201
  var import_zod = require("zod");
@@ -239,6 +247,28 @@ var import_promises = require("fs/promises");
239
247
  var import_node_path = __toESM(require("path"), 1);
240
248
  var import_jiti = require("jiti");
241
249
  var import_zod2 = require("zod");
250
+
251
+ // src/config/is-dependencies-not-installed-error.ts
252
+ function isDependenciesNotInstalledError(error) {
253
+ if (!error || typeof error !== "object") {
254
+ return false;
255
+ }
256
+ const candidate = error;
257
+ const code = candidate.code;
258
+ const message = String(candidate.message ?? error);
259
+ if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
260
+ return /@caatinga\/core/.test(message);
261
+ }
262
+ if (/Cannot find (module|package).+@caatinga\/core/i.test(message)) {
263
+ return true;
264
+ }
265
+ if (candidate.cause !== void 0) {
266
+ return isDependenciesNotInstalledError(candidate.cause);
267
+ }
268
+ return false;
269
+ }
270
+
271
+ // src/config/load-config.ts
242
272
  var import_meta = {};
243
273
  async function loadConfig(options = {}) {
244
274
  const cwd = options.cwd ?? process.cwd();
@@ -264,6 +294,14 @@ async function loadConfig(options = {}) {
264
294
  error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
265
295
  );
266
296
  }
297
+ if (isDependenciesNotInstalledError(error)) {
298
+ throw new CaatingaError(
299
+ "Project dependencies are not installed.",
300
+ CaatingaErrorCode.DEPENDENCIES_NOT_INSTALLED,
301
+ "Run npm install (or pnpm install) in the project root, then retry.",
302
+ error
303
+ );
304
+ }
267
305
  throw error;
268
306
  }
269
307
  }
@@ -670,6 +708,64 @@ function defaultEmitWarning(warning) {
670
708
  `);
671
709
  }
672
710
 
711
+ // src/shell/resolve-subprocess-env.ts
712
+ var import_node_fs = require("fs");
713
+ var import_node_os = __toESM(require("os"), 1);
714
+ var import_node_path6 = __toESM(require("path"), 1);
715
+ function uniquePaths(entries) {
716
+ const seen = /* @__PURE__ */ new Set();
717
+ const ordered = [];
718
+ for (const entry of entries) {
719
+ if (!entry || seen.has(entry)) {
720
+ continue;
721
+ }
722
+ seen.add(entry);
723
+ ordered.push(entry);
724
+ }
725
+ return ordered;
726
+ }
727
+ function hasExecutable(binDir, name) {
728
+ return (0, import_node_fs.existsSync)(import_node_path6.default.join(binDir, name));
729
+ }
730
+ function toolchainBinDirs(home, env) {
731
+ const candidates = [
732
+ import_node_path6.default.join(home, ".cargo", "bin"),
733
+ env.CARGO_HOME ? import_node_path6.default.join(env.CARGO_HOME, "bin") : void 0
734
+ ];
735
+ return candidates.filter((entry) => Boolean(entry && (0, import_node_fs.existsSync)(entry)));
736
+ }
737
+ function buildToolchainPrepend(existingPath, toolchainBins, executableExists = hasExecutable) {
738
+ const prepend = [];
739
+ for (const binDir of toolchainBins) {
740
+ const externalStellarDir = existingPath.find(
741
+ (entry) => entry !== binDir && executableExists(entry, "stellar")
742
+ );
743
+ if (externalStellarDir && executableExists(binDir, "stellar")) {
744
+ prepend.push(externalStellarDir);
745
+ }
746
+ prepend.push(binDir);
747
+ }
748
+ return prepend;
749
+ }
750
+ function resolveSubprocessEnv(overrides = {}) {
751
+ const env = { ...process.env, ...overrides };
752
+ const home = env.HOME ?? import_node_os.default.homedir();
753
+ const existingPath = (env.PATH ?? "").split(import_node_path6.default.delimiter).filter(Boolean);
754
+ const toolchainBins = toolchainBinDirs(home, env);
755
+ const prepend = buildToolchainPrepend(existingPath, toolchainBins);
756
+ env.PATH = uniquePaths([...prepend, ...existingPath]).join(import_node_path6.default.delimiter);
757
+ return env;
758
+ }
759
+ function isCargoBinMissingFromPath(baseEnv = process.env) {
760
+ const home = baseEnv.HOME ?? import_node_os.default.homedir();
761
+ const cargoBin = import_node_path6.default.join(home, ".cargo", "bin", "cargo");
762
+ if (!(0, import_node_fs.existsSync)(cargoBin)) {
763
+ return false;
764
+ }
765
+ const pathEntries = (baseEnv.PATH ?? "").split(import_node_path6.default.delimiter);
766
+ return !pathEntries.some((entry) => entry === import_node_path6.default.join(home, ".cargo", "bin"));
767
+ }
768
+
673
769
  // src/shell/run-command.ts
674
770
  async function runCommand(command, args, options = {}) {
675
771
  try {
@@ -678,7 +774,7 @@ async function runCommand(command, args, options = {}) {
678
774
  }
679
775
  const result = await (0, import_execa.execa)(command, args, {
680
776
  cwd: options.cwd,
681
- env: options.env,
777
+ env: resolveSubprocessEnv(options.env ?? {}),
682
778
  input: options.input,
683
779
  all: true,
684
780
  reject: true
@@ -876,7 +972,7 @@ function validateSourceShape(source) {
876
972
  }
877
973
 
878
974
  // src/contracts/resolve-contract.ts
879
- var import_node_path6 = __toESM(require("path"), 1);
975
+ var import_node_path7 = __toESM(require("path"), 1);
880
976
  function resolveContract(config, contractName, cwd = process.cwd()) {
881
977
  const contract = config.contracts[contractName];
882
978
  if (!contract) {
@@ -889,8 +985,8 @@ function resolveContract(config, contractName, cwd = process.cwd()) {
889
985
  return {
890
986
  name: contractName,
891
987
  config: contract,
892
- sourcePath: import_node_path6.default.resolve(cwd, contract.path),
893
- wasmPath: import_node_path6.default.resolve(cwd, contract.wasm)
988
+ sourcePath: import_node_path7.default.resolve(cwd, contract.path),
989
+ wasmPath: import_node_path7.default.resolve(cwd, contract.wasm)
894
990
  };
895
991
  }
896
992
 
@@ -910,7 +1006,7 @@ function resolveDefaultContractName(config) {
910
1006
  // src/contracts/wasm.ts
911
1007
  var import_node_crypto = require("crypto");
912
1008
  var import_promises6 = require("fs/promises");
913
- var import_node_path7 = __toESM(require("path"), 1);
1009
+ var import_node_path8 = __toESM(require("path"), 1);
914
1010
  var LEGACY_RUST_WASM_TARGET = "wasm32-unknown-unknown";
915
1011
  var CURRENT_RUST_WASM_TARGET = "wasm32v1-none";
916
1012
  function toCurrentWasmTargetPath(wasmPath) {
@@ -921,36 +1017,88 @@ function toCurrentWasmTargetPath(wasmPath) {
921
1017
  }
922
1018
  function wasmNotFoundError(configuredWasmPath, options) {
923
1019
  const migratedPath = options?.migratedPath;
924
- const hint = migratedPath === void 0 ? "Run caatinga build before deploy or generate." : [
925
- "Run caatinga build before deploy or generate.",
926
- `Soroban builds use the "${CURRENT_RUST_WASM_TARGET}" target.`,
927
- `Update wasm in caatinga.config.ts to "${toConfigRelativeWasmPath(migratedPath)}" or an equivalent path under target/${CURRENT_RUST_WASM_TARGET}/release/.`
928
- ].join(" ");
1020
+ const cargoTargetDir = process.env.CARGO_TARGET_DIR;
1021
+ const hintParts = ["Run caatinga build before deploy or generate."];
1022
+ if (migratedPath !== void 0) {
1023
+ hintParts.push(
1024
+ `Soroban builds use the "${CURRENT_RUST_WASM_TARGET}" target.`,
1025
+ `Update wasm in caatinga.config.ts to "${toConfigRelativeWasmPath(migratedPath)}" or an equivalent path under target/${CURRENT_RUST_WASM_TARGET}/release/.`
1026
+ );
1027
+ }
1028
+ if (cargoTargetDir) {
1029
+ hintParts.push(
1030
+ `CARGO_TARGET_DIR is set to "${cargoTargetDir}"; the WASM may be under that directory instead of the configured path. Unset CARGO_TARGET_DIR or update wasm in caatinga.config.ts.`
1031
+ );
1032
+ }
929
1033
  return new CaatingaError(
930
1034
  `WASM output was not found at ${configuredWasmPath}.`,
931
1035
  CaatingaErrorCode.ARTIFACT_NOT_FOUND,
932
- hint
1036
+ hintParts.join(" ")
933
1037
  );
934
1038
  }
935
1039
  function toConfigRelativeWasmPath(absoluteWasmPath) {
936
- const relative = import_node_path7.default.relative(process.cwd(), absoluteWasmPath);
937
- return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(import_node_path7.default.sep).join("/")}`;
1040
+ const relative = import_node_path8.default.relative(process.cwd(), absoluteWasmPath);
1041
+ return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(import_node_path8.default.sep).join("/")}`;
1042
+ }
1043
+ function wasmFileName(configuredWasmPath) {
1044
+ return import_node_path8.default.basename(configuredWasmPath);
1045
+ }
1046
+ function buildAlternateWasmCandidates(configuredWasmPath, options) {
1047
+ const fileName = wasmFileName(configuredWasmPath);
1048
+ const candidates = [];
1049
+ const seen = /* @__PURE__ */ new Set();
1050
+ function addCandidate(candidate) {
1051
+ const resolved = import_node_path8.default.resolve(candidate);
1052
+ if (seen.has(resolved)) {
1053
+ return;
1054
+ }
1055
+ seen.add(resolved);
1056
+ candidates.push(resolved);
1057
+ }
1058
+ const cargoTargetDir = process.env.CARGO_TARGET_DIR;
1059
+ if (cargoTargetDir) {
1060
+ addCandidate(import_node_path8.default.join(cargoTargetDir, CURRENT_RUST_WASM_TARGET, "release", fileName));
1061
+ addCandidate(import_node_path8.default.join(cargoTargetDir, LEGACY_RUST_WASM_TARGET, "release", fileName));
1062
+ }
1063
+ if (options?.sourcePath) {
1064
+ addCandidate(import_node_path8.default.join(options.sourcePath, "target", CURRENT_RUST_WASM_TARGET, "release", fileName));
1065
+ addCandidate(import_node_path8.default.join(options.sourcePath, "target", LEGACY_RUST_WASM_TARGET, "release", fileName));
1066
+ }
1067
+ return candidates;
938
1068
  }
939
- async function resolveWasmArtifactPath(configuredWasmPath) {
1069
+ async function firstExistingPath(paths) {
1070
+ for (const candidate of paths) {
1071
+ try {
1072
+ await (0, import_promises6.access)(candidate);
1073
+ return candidate;
1074
+ } catch {
1075
+ continue;
1076
+ }
1077
+ }
1078
+ return void 0;
1079
+ }
1080
+ async function resolveWasmArtifactPath(configuredWasmPath, options) {
1081
+ const resolvedConfiguredPath = import_node_path8.default.resolve(configuredWasmPath);
940
1082
  try {
941
- await (0, import_promises6.access)(configuredWasmPath);
942
- return configuredWasmPath;
1083
+ await (0, import_promises6.access)(resolvedConfiguredPath);
1084
+ return resolvedConfiguredPath;
943
1085
  } catch {
944
- const currentTargetPath = toCurrentWasmTargetPath(configuredWasmPath);
945
- if (currentTargetPath === configuredWasmPath) {
946
- throw wasmNotFoundError(configuredWasmPath);
1086
+ const currentTargetPath = toCurrentWasmTargetPath(resolvedConfiguredPath);
1087
+ if (currentTargetPath !== resolvedConfiguredPath) {
1088
+ const migratedPath = await firstExistingPath([currentTargetPath]);
1089
+ if (migratedPath) {
1090
+ return migratedPath;
1091
+ }
947
1092
  }
948
- try {
949
- await (0, import_promises6.access)(currentTargetPath);
950
- return currentTargetPath;
951
- } catch {
952
- throw wasmNotFoundError(configuredWasmPath, { migratedPath: currentTargetPath });
1093
+ const alternatePath = await firstExistingPath(
1094
+ buildAlternateWasmCandidates(resolvedConfiguredPath, options)
1095
+ );
1096
+ if (alternatePath) {
1097
+ return alternatePath;
953
1098
  }
1099
+ throw wasmNotFoundError(resolvedConfiguredPath, {
1100
+ migratedPath: currentTargetPath !== resolvedConfiguredPath ? currentTargetPath : void 0
1101
+ });
954
1102
  }
955
1103
  }
956
1104
  async function hashWasm(wasmPath) {
@@ -967,7 +1115,7 @@ async function getNewestMtimeInDirectory(directory) {
967
1115
  async function walk(dir) {
968
1116
  const entries = await (0, import_promises6.readdir)(dir, { withFileTypes: true });
969
1117
  for (const entry of entries) {
970
- const entryPath = import_node_path7.default.join(dir, entry.name);
1118
+ const entryPath = import_node_path8.default.join(dir, entry.name);
971
1119
  if (entry.isDirectory()) {
972
1120
  await walk(entryPath);
973
1121
  continue;
@@ -983,7 +1131,7 @@ async function getNewestMtimeInDirectory(directory) {
983
1131
  return newest > 0 ? newest : void 0;
984
1132
  }
985
1133
  async function isWasmOlderThanSources(input) {
986
- const srcDir = import_node_path7.default.join(input.contractPath, "src");
1134
+ const srcDir = import_node_path8.default.join(input.contractPath, "src");
987
1135
  const newestSourceMtime = await getNewestMtimeInDirectory(srcDir);
988
1136
  if (newestSourceMtime === void 0) {
989
1137
  return false;
@@ -1045,7 +1193,9 @@ async function buildContract(options) {
1045
1193
  }
1046
1194
  throw error;
1047
1195
  }
1048
- const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
1196
+ const wasmPath = await resolveWasmArtifactPath(contract.wasmPath, {
1197
+ sourcePath: contract.sourcePath
1198
+ });
1049
1199
  return {
1050
1200
  contract: {
1051
1201
  ...contract,
@@ -1056,7 +1206,7 @@ async function buildContract(options) {
1056
1206
  }
1057
1207
 
1058
1208
  // src/contracts/deploy-contract.ts
1059
- var import_node_path8 = __toESM(require("path"), 1);
1209
+ var import_node_path9 = __toESM(require("path"), 1);
1060
1210
 
1061
1211
  // src/contracts/dependency-graph.ts
1062
1212
  function buildDependencyGraph(contracts) {
@@ -1113,6 +1263,9 @@ function assertSafeSourceAccount(source) {
1113
1263
  }
1114
1264
  return source;
1115
1265
  }
1266
+ function resolveCliSource(explicit) {
1267
+ return assertSafeSourceAccount(explicit ?? process.env.CAATINGA_SOURCE ?? "alice");
1268
+ }
1116
1269
 
1117
1270
  // src/contracts/deploy-contract.ts
1118
1271
  function toSnakeCaseFlag(key) {
@@ -1135,7 +1288,9 @@ async function deployContract(options) {
1135
1288
  const network = resolveNetwork(options.config, options.networkName);
1136
1289
  const source = assertSafeSourceAccount(options.source);
1137
1290
  await checkBinary("stellar", "Install Stellar CLI before running caatinga deploy.");
1138
- const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
1291
+ const wasmPath = await resolveWasmArtifactPath(contract.wasmPath, {
1292
+ sourcePath: contract.sourcePath
1293
+ });
1139
1294
  const contractWithWasm = {
1140
1295
  ...contract,
1141
1296
  wasmPath
@@ -1157,7 +1312,7 @@ async function deployContract(options) {
1157
1312
  contract: contractWithWasm,
1158
1313
  network,
1159
1314
  contractId: existing.contractId,
1160
- artifactsPath: import_node_path8.default.resolve(cwd, "caatinga.artifacts.json"),
1315
+ artifactsPath: import_node_path9.default.resolve(cwd, "caatinga.artifacts.json"),
1161
1316
  output: "",
1162
1317
  skipped: true,
1163
1318
  staleWasmWarning
@@ -1434,13 +1589,13 @@ async function deployContractGraph(options) {
1434
1589
 
1435
1590
  // src/contracts/generate-bindings.ts
1436
1591
  var import_promises7 = require("fs/promises");
1437
- var import_node_path9 = __toESM(require("path"), 1);
1592
+ var import_node_path10 = __toESM(require("path"), 1);
1438
1593
  function toBindingImportPath(bindingsOutput, contractName) {
1439
- const normalized = bindingsOutput.replace(/^\.\//, "").split(import_node_path9.default.sep).join("/");
1440
- return `./${import_node_path9.default.posix.join(normalized, contractName, "src", "index.js")}`;
1594
+ const normalized = bindingsOutput.replace(/^\.\//, "").split(import_node_path10.default.sep).join("/");
1595
+ return `./${import_node_path10.default.posix.join(normalized, contractName, "src", "index.js")}`;
1441
1596
  }
1442
1597
  async function removeLegacyBindingStub(cwd, bindingsOutput, contractName) {
1443
- const legacyPath = import_node_path9.default.resolve(cwd, bindingsOutput, `${contractName}.ts`);
1598
+ const legacyPath = import_node_path10.default.resolve(cwd, bindingsOutput, `${contractName}.ts`);
1444
1599
  try {
1445
1600
  await (0, import_promises7.access)(legacyPath);
1446
1601
  await (0, import_promises7.unlink)(legacyPath);
@@ -1469,7 +1624,7 @@ async function generateBindings(options) {
1469
1624
  );
1470
1625
  }
1471
1626
  await checkBinary("stellar", "Install Stellar CLI before running caatinga generate.");
1472
- const outputDir = import_node_path9.default.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1627
+ const outputDir = import_node_path10.default.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1473
1628
  await (0, import_promises7.mkdir)(outputDir, { recursive: true });
1474
1629
  const result = await runCommand("stellar", [
1475
1630
  "contract",
@@ -1543,8 +1698,8 @@ async function generateBindingsGraph(options) {
1543
1698
  return { network, results };
1544
1699
  }
1545
1700
 
1546
- // src/contracts/invoke-contract.ts
1547
- var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
1701
+ // src/contracts/invoke-target.ts
1702
+ var READ_CALL_FAILURE_REGEX = /this is a read call|read-only/i;
1548
1703
  function parseInvokeTarget(target) {
1549
1704
  const [contractName, method, extra] = target.split(".");
1550
1705
  if (!contractName || !method || extra) {
@@ -1556,6 +1711,29 @@ function parseInvokeTarget(target) {
1556
1711
  }
1557
1712
  return { contractName, method };
1558
1713
  }
1714
+ function buildReadCallHint(target, networkName) {
1715
+ return [
1716
+ `"${target.contractName}.${target.method}" is a read-only contract method.`,
1717
+ "Simulate without signing instead:",
1718
+ ` npx caatinga read ${target.contractName}.${target.method} --network ${networkName}`,
1719
+ " (--source is optional; Caatinga resolves CAATINGA_SOURCE or defaults to alice)",
1720
+ "In browser code, use:",
1721
+ ` client.contract("${target.contractName}").read("${target.method}")`,
1722
+ ` client.contract("${target.contractName}").simulate("${target.method}")`,
1723
+ "Pass method args as the second argument to read() when the contract method takes parameters.",
1724
+ "Only pass Stellar CLI --force when you intentionally need a signed read simulation."
1725
+ ].join("\n");
1726
+ }
1727
+ function isReadCallFailure(error) {
1728
+ if (!(error instanceof CaatingaError) || error.code !== CaatingaErrorCode.INVOKE_FAILED) {
1729
+ return false;
1730
+ }
1731
+ return READ_CALL_FAILURE_REGEX.test(`${error.message}
1732
+ ${error.hint ?? ""}`);
1733
+ }
1734
+
1735
+ // src/contracts/invoke-contract.ts
1736
+ var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
1559
1737
  async function invokeContract(options) {
1560
1738
  const cwd = options.cwd ?? process.cwd();
1561
1739
  const network = resolveNetwork(options.config, options.networkName);
@@ -1589,6 +1767,14 @@ async function invokeContract(options) {
1589
1767
  failureCode: CaatingaErrorCode.INVOKE_FAILED
1590
1768
  });
1591
1769
  } catch (error) {
1770
+ if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && isReadCallFailure(error)) {
1771
+ throw new CaatingaError(
1772
+ error.message,
1773
+ error.code,
1774
+ buildReadCallHint(target, network.name),
1775
+ error
1776
+ );
1777
+ }
1592
1778
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && INVOKE_SIGNING_FAILURE_REGEX.test(`${error.message}
1593
1779
  ${error.hint ?? ""}`)) {
1594
1780
  throw new CaatingaError(
@@ -1614,9 +1800,48 @@ ${error.hint ?? ""}`)) {
1614
1800
  };
1615
1801
  }
1616
1802
 
1803
+ // src/contracts/read-contract.ts
1804
+ async function readContract(options) {
1805
+ const cwd = options.cwd ?? process.cwd();
1806
+ const network = resolveNetwork(options.config, options.networkName);
1807
+ const target = parseInvokeTarget(options.target);
1808
+ const artifacts = await readArtifacts(cwd);
1809
+ const contractArtifact = artifacts.networks[network.name]?.contracts[target.contractName];
1810
+ if (!contractArtifact) {
1811
+ throw new CaatingaError(
1812
+ `No deployed artifact found for "${target.contractName}" on "${network.name}".`,
1813
+ CaatingaErrorCode.ARTIFACT_NOT_FOUND,
1814
+ "Run caatinga deploy for this contract and network before reading it."
1815
+ );
1816
+ }
1817
+ await checkBinary("stellar", "Install Stellar CLI before running caatinga read.");
1818
+ const stellarArgs = [
1819
+ "contract",
1820
+ "invoke",
1821
+ "--id",
1822
+ contractArtifact.contractId,
1823
+ "--source-account",
1824
+ resolveCliSource(options.source),
1825
+ "--send=no",
1826
+ ...buildStellarNetworkArgs(network),
1827
+ "--",
1828
+ target.method,
1829
+ ...options.args ?? []
1830
+ ];
1831
+ const result = await runCommand("stellar", stellarArgs, {
1832
+ cwd,
1833
+ failureCode: CaatingaErrorCode.INVOKE_FAILED
1834
+ });
1835
+ return {
1836
+ target,
1837
+ network,
1838
+ result: result.stdout || result.all
1839
+ };
1840
+ }
1841
+
1617
1842
  // src/templates/create-project-from-template.ts
1618
1843
  var import_promises8 = require("fs/promises");
1619
- var import_node_path10 = __toESM(require("path"), 1);
1844
+ var import_node_path11 = __toESM(require("path"), 1);
1620
1845
  var import_zod7 = require("zod");
1621
1846
 
1622
1847
  // src/templates/template-manifest.schema.ts
@@ -1692,8 +1917,8 @@ var TEMPLATE_COPY_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1692
1917
  ".git"
1693
1918
  ]);
1694
1919
  async function createProjectFromTemplate(options) {
1695
- const targetDir = import_node_path10.default.resolve(options.targetDir);
1696
- const templateDir = import_node_path10.default.resolve(options.templateDir);
1920
+ const targetDir = import_node_path11.default.resolve(options.targetDir);
1921
+ const templateDir = import_node_path11.default.resolve(options.templateDir);
1697
1922
  try {
1698
1923
  await (0, import_promises8.stat)(templateDir);
1699
1924
  } catch {
@@ -1731,7 +1956,7 @@ async function ensureArtifacts(targetDir, projectName) {
1731
1956
  }
1732
1957
  }
1733
1958
  async function readTemplateManifest(templateDir) {
1734
- const manifestPath = import_node_path10.default.join(templateDir, "caatinga.template.json");
1959
+ const manifestPath = import_node_path11.default.join(templateDir, "caatinga.template.json");
1735
1960
  try {
1736
1961
  const rawManifest = await (0, import_promises8.readFile)(manifestPath, "utf8");
1737
1962
  const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
@@ -1768,7 +1993,7 @@ async function readTemplateManifest(templateDir) {
1768
1993
  async function replaceTemplateVariables(dir, projectName) {
1769
1994
  const entries = await (0, import_promises8.readdir)(dir);
1770
1995
  await Promise.all(entries.map(async (entry) => {
1771
- const entryPath = import_node_path10.default.join(dir, entry);
1996
+ const entryPath = import_node_path11.default.join(dir, entry);
1772
1997
  const entryStat = await (0, import_promises8.stat)(entryPath);
1773
1998
  if (entryStat.isDirectory()) {
1774
1999
  await replaceTemplateVariables(entryPath, projectName);
@@ -1782,15 +2007,15 @@ async function replaceTemplateVariables(dir, projectName) {
1782
2007
  }));
1783
2008
  }
1784
2009
  function shouldCopyTemplateEntry(templateDir, source, userFilter) {
1785
- const relativePath = import_node_path10.default.relative(templateDir, source);
2010
+ const relativePath = import_node_path11.default.relative(templateDir, source);
1786
2011
  if (!relativePath || relativePath === ".") {
1787
2012
  return true;
1788
2013
  }
1789
- const normalizedPath = relativePath.split(import_node_path10.default.sep).join("/");
2014
+ const normalizedPath = relativePath.split(import_node_path11.default.sep).join("/");
1790
2015
  if (userFilter && !userFilter(normalizedPath)) {
1791
2016
  return false;
1792
2017
  }
1793
- return !relativePath.split(import_node_path10.default.sep).some((segment) => TEMPLATE_COPY_EXCLUDED_DIRS.has(segment));
2018
+ return !relativePath.split(import_node_path11.default.sep).some((segment) => TEMPLATE_COPY_EXCLUDED_DIRS.has(segment));
1794
2019
  }
1795
2020
  function isTextTemplateFile(filePath) {
1796
2021
  return [
@@ -1802,22 +2027,22 @@ function isTextTemplateFile(filePath) {
1802
2027
  ".tsx",
1803
2028
  ".css",
1804
2029
  ".html"
1805
- ].includes(import_node_path10.default.extname(filePath));
2030
+ ].includes(import_node_path11.default.extname(filePath));
1806
2031
  }
1807
2032
 
1808
2033
  // src/scaffold/create-zk-project.ts
1809
2034
  var import_promises9 = require("fs/promises");
1810
- var import_node_fs = require("fs");
1811
- var import_node_path11 = __toESM(require("path"), 1);
2035
+ var import_node_fs2 = require("fs");
2036
+ var import_node_path12 = __toESM(require("path"), 1);
1812
2037
  var import_node_url = require("url");
1813
2038
  var import_meta2 = {};
1814
- var moduleDir = typeof __dirname === "string" ? __dirname : import_node_path11.default.dirname((0, import_node_url.fileURLToPath)(import_meta2.url));
2039
+ var moduleDir = typeof __dirname === "string" ? __dirname : import_node_path12.default.dirname((0, import_node_url.fileURLToPath)(import_meta2.url));
1815
2040
  function scaffoldRoot() {
1816
2041
  const candidates = [
1817
- import_node_path11.default.resolve(moduleDir, "../../scaffolds"),
1818
- import_node_path11.default.resolve(moduleDir, "../scaffolds")
2042
+ import_node_path12.default.resolve(moduleDir, "../../scaffolds"),
2043
+ import_node_path12.default.resolve(moduleDir, "../scaffolds")
1819
2044
  ];
1820
- const found = candidates.find((candidate) => (0, import_node_fs.existsSync)(candidate));
2045
+ const found = candidates.find((candidate) => (0, import_node_fs2.existsSync)(candidate));
1821
2046
  return found ?? candidates[0];
1822
2047
  }
1823
2048
  function configSource(projectName) {
@@ -1861,7 +2086,8 @@ function packageJsonSource(projectName) {
1861
2086
  "zk:build": "caatinga zk build main",
1862
2087
  "zk:prove": "caatinga zk prove main",
1863
2088
  build: "caatinga build verifier",
1864
- deploy: "caatinga deploy verifier"
2089
+ deploy: "caatinga deploy verifier --network testnet --source ${CAATINGA_SOURCE:-alice}",
2090
+ doctor: "caatinga doctor --network testnet"
1865
2091
  },
1866
2092
  devDependencies: {
1867
2093
  "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
@@ -1881,7 +2107,7 @@ Minimal Caatinga ZK project.
1881
2107
  npm install
1882
2108
  npx caatinga zk build main
1883
2109
  npx caatinga build verifier
1884
- npx caatinga deploy verifier --network testnet
2110
+ npx caatinga deploy verifier --network testnet --source <identity>
1885
2111
  npx caatinga zk prove main
1886
2112
  \`\`\`
1887
2113
 
@@ -1889,25 +2115,25 @@ Replace \`circuits/main.circom\` with your circuit. Keep the entry point named \
1889
2115
  `;
1890
2116
  }
1891
2117
  async function createZkProject(options) {
1892
- const targetDir = import_node_path11.default.resolve(options.targetDir);
2118
+ const targetDir = import_node_path12.default.resolve(options.targetDir);
1893
2119
  const force = options.force ?? false;
1894
2120
  const projectFiles = options.projectFiles ?? true;
1895
2121
  await (0, import_promises9.mkdir)(targetDir, { recursive: true });
1896
2122
  if (projectFiles) {
1897
2123
  await Promise.all([
1898
- (0, import_promises9.writeFile)(import_node_path11.default.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
1899
- (0, import_promises9.writeFile)(import_node_path11.default.join(targetDir, "package.json"), packageJsonSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
1900
- (0, import_promises9.writeFile)(import_node_path11.default.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
1901
- (0, import_promises9.writeFile)(import_node_path11.default.join(targetDir, "README.md"), readmeSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
2124
+ (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2125
+ (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "package.json"), packageJsonSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2126
+ (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2127
+ (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "README.md"), readmeSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
1902
2128
  ]);
1903
2129
  }
1904
- await (0, import_promises9.mkdir)(import_node_path11.default.join(targetDir, "contracts"), { recursive: true });
1905
- await (0, import_promises9.cp)(import_node_path11.default.join(scaffoldRoot(), "zk-circuit-stub"), import_node_path11.default.join(targetDir, "circuits"), {
2130
+ await (0, import_promises9.mkdir)(import_node_path12.default.join(targetDir, "contracts"), { recursive: true });
2131
+ await (0, import_promises9.cp)(import_node_path12.default.join(scaffoldRoot(), "zk-circuit-stub"), import_node_path12.default.join(targetDir, "circuits"), {
1906
2132
  recursive: true,
1907
2133
  force,
1908
2134
  errorOnExist: !force
1909
2135
  });
1910
- await (0, import_promises9.cp)(import_node_path11.default.join(scaffoldRoot(), "zk-verifier"), import_node_path11.default.join(targetDir, "contracts", "verifier"), {
2136
+ await (0, import_promises9.cp)(import_node_path12.default.join(scaffoldRoot(), "zk-verifier"), import_node_path12.default.join(targetDir, "contracts", "verifier"), {
1911
2137
  recursive: true,
1912
2138
  force,
1913
2139
  errorOnExist: !force
@@ -1918,6 +2144,110 @@ async function createZkProject(options) {
1918
2144
  return { targetDir };
1919
2145
  }
1920
2146
 
2147
+ // src/scaffold/create-minimal-project.ts
2148
+ var import_promises10 = require("fs/promises");
2149
+ var import_node_fs3 = require("fs");
2150
+ var import_node_path13 = __toESM(require("path"), 1);
2151
+ var import_node_url2 = require("url");
2152
+ var import_meta3 = {};
2153
+ var moduleDir2 = typeof __dirname === "string" ? __dirname : import_node_path13.default.dirname((0, import_node_url2.fileURLToPath)(import_meta3.url));
2154
+ function scaffoldRoot2() {
2155
+ const candidates = [
2156
+ import_node_path13.default.resolve(moduleDir2, "../../scaffolds"),
2157
+ import_node_path13.default.resolve(moduleDir2, "../scaffolds")
2158
+ ];
2159
+ const found = candidates.find((candidate) => (0, import_node_fs3.existsSync)(candidate));
2160
+ return found ?? candidates[0];
2161
+ }
2162
+ function configSource2(projectName) {
2163
+ return `import { defineConfig } from "@caatinga/core";
2164
+
2165
+ export default defineConfig({
2166
+ project: "${projectName}",
2167
+ defaultNetwork: "testnet",
2168
+ contracts: {
2169
+ app: {
2170
+ path: "./contracts/app",
2171
+ wasm: "./contracts/app/target/wasm32v1-none/release/app.wasm"
2172
+ }
2173
+ },
2174
+ networks: {
2175
+ testnet: {
2176
+ rpcUrl: "https://soroban-testnet.stellar.org",
2177
+ networkPassphrase: "Test SDF Network ; September 2015"
2178
+ }
2179
+ }
2180
+ });
2181
+ `;
2182
+ }
2183
+ function packageJsonSource2(projectName) {
2184
+ return `${JSON.stringify({
2185
+ name: projectName,
2186
+ version: "0.1.0",
2187
+ private: true,
2188
+ type: "module",
2189
+ scripts: {
2190
+ build: "caatinga build app",
2191
+ deploy: "caatinga deploy app --network testnet --source ${CAATINGA_SOURCE:-alice}",
2192
+ doctor: "caatinga doctor --network testnet",
2193
+ "read:hello": "caatinga read app.hello --network testnet --source ${CAATINGA_SOURCE:-alice}",
2194
+ "read:version": "caatinga read app.version --network testnet --source ${CAATINGA_SOURCE:-alice}"
2195
+ },
2196
+ devDependencies: {
2197
+ "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2198
+ "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2199
+ }
2200
+ }, null, 2)}
2201
+ `;
2202
+ }
2203
+ function readmeSource2(projectName) {
2204
+ return `# ${projectName}
2205
+
2206
+ Minimal Caatinga project with a Soroban contract stub (no frontend template).
2207
+
2208
+ ## Workflow
2209
+
2210
+ \`\`\`bash
2211
+ npm install
2212
+ npx caatinga doctor
2213
+ npx caatinga build app
2214
+ npx caatinga deploy app --network testnet --source <identity>
2215
+ npx caatinga read app.version --network testnet
2216
+ npx caatinga read app.hello --network testnet
2217
+ \`\`\`
2218
+
2219
+ ## Contract
2220
+
2221
+ - \`hello()\` \u2014 read-only; returns Soroban Symbol \`hello\`
2222
+ - \`version()\` \u2014 read-only; returns \`1\`
2223
+
2224
+ Use \`caatinga read\` for read-only methods. Use \`caatinga invoke\` only after you add state-changing methods to the contract.
2225
+
2226
+ Soroban \`Symbol\` parameters are generated as TypeScript \`string\` values with host-specific restrictions \u2014 see the Caatinga docs on [Soroban types](https://github.com/caatinga/caatinga/blob/main/docs/soroban-types.md).
2227
+
2228
+ Edit \`contracts/app/src/lib.rs\` to customize the contract. Add a frontend later with \`@caatinga/client\` and your chosen UI stack.
2229
+ `;
2230
+ }
2231
+ async function createMinimalProject(options) {
2232
+ const targetDir = import_node_path13.default.resolve(options.targetDir);
2233
+ const force = options.force ?? false;
2234
+ await (0, import_promises10.mkdir)(targetDir, { recursive: true });
2235
+ await Promise.all([
2236
+ (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "caatinga.config.ts"), configSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2237
+ (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "package.json"), packageJsonSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2238
+ (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2239
+ (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "README.md"), readmeSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
2240
+ ]);
2241
+ await (0, import_promises10.mkdir)(import_node_path13.default.join(targetDir, "contracts"), { recursive: true });
2242
+ await (0, import_promises10.cp)(import_node_path13.default.join(scaffoldRoot2(), "soroban-contract-stub"), import_node_path13.default.join(targetDir, "contracts", "app"), {
2243
+ recursive: true,
2244
+ force,
2245
+ errorOnExist: !force
2246
+ });
2247
+ await writeArtifacts(createInitialArtifacts(options.projectName, { networks: ["testnet"] }), targetDir);
2248
+ return { targetDir };
2249
+ }
2250
+
1921
2251
  // src/ci/is-transient-testnet-smoke-failure.ts
1922
2252
  var NO_RETRY_CAATINGA_SUBSTRINGS = [
1923
2253
  "CAATINGA_UNSUPPORTED_CLI_VERSION",
@@ -1947,16 +2277,19 @@ function isTransientTestnetSmokeFailure(logText) {
1947
2277
  CaatingaConfigSchema,
1948
2278
  CaatingaError,
1949
2279
  CaatingaErrorCode,
2280
+ READ_CALL_FAILURE_REGEX,
1950
2281
  STELLAR_CLI_LAST_TESTED_VERSION,
1951
2282
  STELLAR_CLI_MIN_VERSION,
1952
2283
  TemplateManifestSchema,
1953
2284
  WELL_KNOWN_NETWORKS,
1954
2285
  buildContract,
1955
2286
  buildDependencyGraph,
2287
+ buildReadCallHint,
1956
2288
  checkBinary,
1957
2289
  checkStellarCliVersion,
1958
2290
  collectProjectStatus,
1959
2291
  createInitialArtifacts,
2292
+ createMinimalProject,
1960
2293
  createProjectFromTemplate,
1961
2294
  createZkProject,
1962
2295
  defineConfig,
@@ -1969,6 +2302,8 @@ function isTransientTestnetSmokeFailure(logText) {
1969
2302
  generateBindings,
1970
2303
  generateBindingsGraph,
1971
2304
  invokeContract,
2305
+ isCargoBinMissingFromPath,
2306
+ isReadCallFailure,
1972
2307
  isTransientTestnetSmokeFailure,
1973
2308
  loadConfig,
1974
2309
  parseContractId,
@@ -1976,11 +2311,13 @@ function isTransientTestnetSmokeFailure(logText) {
1976
2311
  parseStellarCliVersion,
1977
2312
  readArtifacts,
1978
2313
  readBindingMarker,
2314
+ readContract,
1979
2315
  resolveContract,
1980
2316
  resolveDefaultContractName,
1981
2317
  resolveDeployArgs,
1982
2318
  resolveDeployOrder,
1983
2319
  resolveNetwork,
2320
+ resolveSubprocessEnv,
1984
2321
  runCommand,
1985
2322
  toCaatingaError,
1986
2323
  updateArtifact,