@caatinga/core 3.0.0 → 3.1.0

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/README.md CHANGED
@@ -53,19 +53,19 @@ export default defineConfig({
53
53
  contracts: {
54
54
  counter: {
55
55
  path: "./contracts/counter",
56
- wasm: "./contracts/counter/target/wasm32v1-none/release/counter.wasm"
57
- }
56
+ wasm: "./contracts/counter/target/wasm32v1-none/release/counter.wasm",
57
+ },
58
58
  },
59
59
  networks: {
60
60
  testnet: {
61
61
  rpcUrl: "https://soroban-testnet.stellar.org",
62
- networkPassphrase: "Test SDF Network ; September 2015"
63
- }
62
+ networkPassphrase: "Test SDF Network ; September 2015",
63
+ },
64
64
  },
65
65
  frontend: {
66
66
  framework: "vite-react",
67
- bindingsOutput: "./contracts/generated"
68
- }
67
+ bindingsOutput: "./contracts/generated",
68
+ },
69
69
  });
70
70
  ```
71
71
 
@@ -73,11 +73,11 @@ Multi-contract projects may declare `dependsOn` and `${contracts.<name>.contract
73
73
 
74
74
  ## Consumer guidance
75
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` |
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
81
  | Custom tooling on deploy graphs, artifacts, or Stellar CLI orchestration | `@caatinga/core` (advanced; track releases closely) |
82
82
 
83
83
  ## Error codes
@@ -52,7 +52,7 @@ declare const CaatingaErrorCode: {
52
52
  readonly DOCTOR_PARTIAL_DEPLOY: "CAATINGA_DOCTOR_PARTIAL_DEPLOY";
53
53
  readonly MULTI_AUTH_REQUIRED: "CAATINGA_MULTI_AUTH_REQUIRED";
54
54
  };
55
- type CaatingaErrorCodeValue = typeof CaatingaErrorCode[keyof typeof CaatingaErrorCode];
55
+ type CaatingaErrorCodeValue = (typeof CaatingaErrorCode)[keyof typeof CaatingaErrorCode];
56
56
 
57
57
  declare class CaatingaError extends Error {
58
58
  readonly code: CaatingaErrorCodeValue;
@@ -52,7 +52,7 @@ declare const CaatingaErrorCode: {
52
52
  readonly DOCTOR_PARTIAL_DEPLOY: "CAATINGA_DOCTOR_PARTIAL_DEPLOY";
53
53
  readonly MULTI_AUTH_REQUIRED: "CAATINGA_MULTI_AUTH_REQUIRED";
54
54
  };
55
- type CaatingaErrorCodeValue = typeof CaatingaErrorCode[keyof typeof CaatingaErrorCode];
55
+ type CaatingaErrorCodeValue = (typeof CaatingaErrorCode)[keyof typeof CaatingaErrorCode];
56
56
 
57
57
  declare class CaatingaError extends Error {
58
58
  readonly code: CaatingaErrorCodeValue;
@@ -1,4 +1,4 @@
1
- export { C as CaatingaArtifacts, a as CaatingaError, b as CaatingaErrorCode, c as ContractArtifact, f as formatCaatingaError, t as toCaatingaError } from './artifact.schema-CGCFfl0g.cjs';
1
+ export { C as CaatingaArtifacts, a as CaatingaError, b as CaatingaErrorCode, c as ContractArtifact, f as formatCaatingaError, t as toCaatingaError } from './artifact.schema-D4r8dyYK.cjs';
2
2
  import 'zod';
3
3
 
4
4
  declare function assertSorobanSymbol(value: string, paramName?: string): void;
package/dist/browser.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { C as CaatingaArtifacts, a as CaatingaError, b as CaatingaErrorCode, c as ContractArtifact, f as formatCaatingaError, t as toCaatingaError } from './artifact.schema-CGCFfl0g.js';
1
+ export { C as CaatingaArtifacts, a as CaatingaError, b as CaatingaErrorCode, c as ContractArtifact, f as formatCaatingaError, t as toCaatingaError } from './artifact.schema-D4r8dyYK.js';
2
2
  import 'zod';
3
3
 
4
4
  declare function assertSorobanSymbol(value: string, paramName?: string): void;
package/dist/index.cjs CHANGED
@@ -211,9 +211,7 @@ function formatCause(cause) {
211
211
  // src/version.ts
212
212
  var import_node_module = require("module");
213
213
  var import_meta = {};
214
- var require2 = (0, import_node_module.createRequire)(
215
- typeof __filename === "string" ? __filename : import_meta.url
216
- );
214
+ var require2 = (0, import_node_module.createRequire)(typeof __filename === "string" ? __filename : import_meta.url);
217
215
  var CAATINGA_CORE_VERSION = require2("../package.json").version;
218
216
 
219
217
  // src/config/config.schema.ts
@@ -861,12 +859,7 @@ function matchesWellKnownNetwork(name, config) {
861
859
  return known.rpcUrl === config.rpcUrl && known.networkPassphrase === config.networkPassphrase;
862
860
  }
863
861
  function buildRpcNetworkArgs(config) {
864
- return [
865
- "--rpc-url",
866
- config.rpcUrl,
867
- "--network-passphrase",
868
- config.networkPassphrase
869
- ];
862
+ return ["--rpc-url", config.rpcUrl, "--network-passphrase", config.networkPassphrase];
870
863
  }
871
864
  function buildStellarNetworkArgsFromConfig(config) {
872
865
  for (const [name, known] of Object.entries(WELL_KNOWN_NETWORKS)) {
@@ -920,19 +913,23 @@ async function fetchCreateContractSalt(horizonUrl, transactionHash, fetchImpl =
920
913
  }
921
914
  async function resolveContractIdFromDeploySalt(options) {
922
915
  const saltHex = decimalSaltToHex(options.salt);
923
- const result = await runCommand("stellar", [
924
- "contract",
925
- "id",
926
- "wasm",
927
- "--salt",
928
- saltHex,
929
- "--source-account",
930
- options.source,
931
- ...buildStellarNetworkArgsFromConfig(options.network)
932
- ], {
933
- cwd: options.cwd,
934
- skipStellarVersionCheck: true
935
- });
916
+ const result = await runCommand(
917
+ "stellar",
918
+ [
919
+ "contract",
920
+ "id",
921
+ "wasm",
922
+ "--salt",
923
+ saltHex,
924
+ "--source-account",
925
+ options.source,
926
+ ...buildStellarNetworkArgsFromConfig(options.network)
927
+ ],
928
+ {
929
+ cwd: options.cwd,
930
+ skipStellarVersionCheck: true
931
+ }
932
+ );
936
933
  return parseContractId(result.all || `${result.stdout}
937
934
  ${result.stderr}`);
938
935
  }
@@ -1080,8 +1077,12 @@ function buildAlternateWasmCandidates(configuredWasmPath, options) {
1080
1077
  addCandidate(import_node_path8.default.join(cargoTargetDir, LEGACY_RUST_WASM_TARGET, "release", fileName));
1081
1078
  }
1082
1079
  if (options?.sourcePath) {
1083
- addCandidate(import_node_path8.default.join(options.sourcePath, "target", CURRENT_RUST_WASM_TARGET, "release", fileName));
1084
- addCandidate(import_node_path8.default.join(options.sourcePath, "target", LEGACY_RUST_WASM_TARGET, "release", fileName));
1080
+ addCandidate(
1081
+ import_node_path8.default.join(options.sourcePath, "target", CURRENT_RUST_WASM_TARGET, "release", fileName)
1082
+ );
1083
+ addCandidate(
1084
+ import_node_path8.default.join(options.sourcePath, "target", LEGACY_RUST_WASM_TARGET, "release", fileName)
1085
+ );
1085
1086
  }
1086
1087
  return candidates;
1087
1088
  }
@@ -1496,17 +1497,21 @@ function toSkippedContract(name, contractId, network) {
1496
1497
  // src/contracts/verify-dependency-contract.ts
1497
1498
  async function verifyDependencyContract(options) {
1498
1499
  try {
1499
- await runCommand("stellar", [
1500
- "contract",
1501
- "info",
1502
- "interface",
1503
- "--contract-id",
1504
- options.contractId,
1505
- ...buildStellarNetworkArgs(options.network)
1506
- ], {
1507
- cwd: options.cwd,
1508
- failureCode: CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND
1509
- });
1500
+ await runCommand(
1501
+ "stellar",
1502
+ [
1503
+ "contract",
1504
+ "info",
1505
+ "interface",
1506
+ "--contract-id",
1507
+ options.contractId,
1508
+ ...buildStellarNetworkArgs(options.network)
1509
+ ],
1510
+ {
1511
+ cwd: options.cwd,
1512
+ failureCode: CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND
1513
+ }
1514
+ );
1510
1515
  } catch (error) {
1511
1516
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND) {
1512
1517
  throw new CaatingaError(
@@ -1568,9 +1573,7 @@ async function deployContractGraph(options) {
1568
1573
  network: network.name
1569
1574
  });
1570
1575
  if (existing?.contractId && !options.force) {
1571
- skippedContracts.push(
1572
- toSkippedContract(contractName, existing.contractId, network.name)
1573
- );
1576
+ skippedContracts.push(toSkippedContract(contractName, existing.contractId, network.name));
1574
1577
  continue;
1575
1578
  }
1576
1579
  const result = await deployContract({
@@ -1591,9 +1594,7 @@ async function deployContractGraph(options) {
1591
1594
  });
1592
1595
  }
1593
1596
  if (result.skipped) {
1594
- skippedContracts.push(
1595
- toSkippedContract(contractName, result.contractId, network.name)
1596
- );
1597
+ skippedContracts.push(toSkippedContract(contractName, result.contractId, network.name));
1597
1598
  } else {
1598
1599
  deployedContracts.push({ name: contractName, contractId: result.contractId });
1599
1600
  }
@@ -1654,22 +1655,26 @@ async function generateBindings(options) {
1654
1655
  }
1655
1656
  const outputDir = import_node_path10.default.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1656
1657
  await (0, import_promises7.mkdir)(outputDir, { recursive: true });
1657
- const result = await runCommand("npx", [
1658
- "--yes",
1659
- "@stellar/stellar-sdk",
1660
- "generate",
1661
- "--contract-id",
1662
- contractArtifact.contractId,
1663
- "--output-dir",
1664
- outputDir,
1665
- "--contract-name",
1666
- options.contractName,
1667
- "--overwrite",
1668
- ...buildGenerateNetworkArgs(network)
1669
- ], {
1670
- cwd,
1671
- failureCode: CaatingaErrorCode.BINDINGS_FAILED
1672
- });
1658
+ const result = await runCommand(
1659
+ "npx",
1660
+ [
1661
+ "--yes",
1662
+ "@stellar/stellar-sdk",
1663
+ "generate",
1664
+ "--contract-id",
1665
+ contractArtifact.contractId,
1666
+ "--output-dir",
1667
+ outputDir,
1668
+ "--contract-name",
1669
+ options.contractName,
1670
+ "--overwrite",
1671
+ ...buildGenerateNetworkArgs(network)
1672
+ ],
1673
+ {
1674
+ cwd,
1675
+ failureCode: CaatingaErrorCode.BINDINGS_FAILED
1676
+ }
1677
+ );
1673
1678
  const legacyStubRemoved = await removeLegacyBindingStub(
1674
1679
  cwd,
1675
1680
  options.config.frontend.bindingsOutput,
@@ -1781,21 +1786,25 @@ async function invokeContract(options) {
1781
1786
  await checkBinary("stellar", "Install Stellar CLI before running caatinga invoke.");
1782
1787
  let result;
1783
1788
  try {
1784
- result = await runCommand("stellar", [
1785
- "contract",
1786
- "invoke",
1787
- "--id",
1788
- contractArtifact.contractId,
1789
- "--source-account",
1790
- source,
1791
- ...buildStellarNetworkArgs(network),
1792
- "--",
1793
- target.method,
1794
- ...options.args ?? []
1795
- ], {
1796
- cwd,
1797
- failureCode: CaatingaErrorCode.INVOKE_FAILED
1798
- });
1789
+ result = await runCommand(
1790
+ "stellar",
1791
+ [
1792
+ "contract",
1793
+ "invoke",
1794
+ "--id",
1795
+ contractArtifact.contractId,
1796
+ "--source-account",
1797
+ source,
1798
+ ...buildStellarNetworkArgs(network),
1799
+ "--",
1800
+ target.method,
1801
+ ...options.args ?? []
1802
+ ],
1803
+ {
1804
+ cwd,
1805
+ failureCode: CaatingaErrorCode.INVOKE_FAILED
1806
+ }
1807
+ );
1799
1808
  } catch (error) {
1800
1809
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && isReadCallFailure(error)) {
1801
1810
  throw new CaatingaError(
@@ -1940,12 +1949,7 @@ function formatTemplateCompatibilityHint(issue) {
1940
1949
  }
1941
1950
 
1942
1951
  // src/templates/create-project-from-template.ts
1943
- var TEMPLATE_COPY_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1944
- "target",
1945
- "test_snapshots",
1946
- "node_modules",
1947
- ".git"
1948
- ]);
1952
+ var TEMPLATE_COPY_EXCLUDED_DIRS = /* @__PURE__ */ new Set(["target", "test_snapshots", "node_modules", ".git"]);
1949
1953
  async function createProjectFromTemplate(options) {
1950
1954
  const targetDir = import_node_path11.default.resolve(options.targetDir);
1951
1955
  const templateDir = import_node_path11.default.resolve(options.templateDir);
@@ -1963,8 +1967,8 @@ async function createProjectFromTemplate(options) {
1963
1967
  await (0, import_promises8.mkdir)(targetDir, { recursive: true });
1964
1968
  await (0, import_promises8.cp)(templateDir, targetDir, {
1965
1969
  recursive: true,
1966
- force: mergeIntoExisting,
1967
- errorOnExist: !mergeIntoExisting,
1970
+ force: true,
1971
+ errorOnExist: false,
1968
1972
  filter: (source) => shouldCopyTemplateEntry(templateDir, source, options.filter)
1969
1973
  });
1970
1974
  await replaceTemplateVariables(targetDir, options.projectName);
@@ -1979,7 +1983,10 @@ async function ensureArtifacts(targetDir, projectName) {
1979
1983
  await writeArtifacts({ ...artifacts, project: projectName }, targetDir);
1980
1984
  } catch (error) {
1981
1985
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.ARTIFACT_NOT_FOUND) {
1982
- await writeArtifacts(createInitialArtifacts(projectName, { networks: ["testnet"] }), targetDir);
1986
+ await writeArtifacts(
1987
+ createInitialArtifacts(projectName, { networks: ["testnet"] }),
1988
+ targetDir
1989
+ );
1983
1990
  return;
1984
1991
  }
1985
1992
  throw error;
@@ -2022,19 +2029,21 @@ async function readTemplateManifest(templateDir) {
2022
2029
  }
2023
2030
  async function replaceTemplateVariables(dir, projectName) {
2024
2031
  const entries = await (0, import_promises8.readdir)(dir);
2025
- await Promise.all(entries.map(async (entry) => {
2026
- const entryPath = import_node_path11.default.join(dir, entry);
2027
- const entryStat = await (0, import_promises8.stat)(entryPath);
2028
- if (entryStat.isDirectory()) {
2029
- await replaceTemplateVariables(entryPath, projectName);
2030
- return;
2031
- }
2032
- if (!isTextTemplateFile(entryPath)) {
2033
- return;
2034
- }
2035
- const content = await (0, import_promises8.readFile)(entryPath, "utf8");
2036
- await (0, import_promises8.writeFile)(entryPath, content.replaceAll("__PROJECT_NAME__", projectName), "utf8");
2037
- }));
2032
+ await Promise.all(
2033
+ entries.map(async (entry) => {
2034
+ const entryPath = import_node_path11.default.join(dir, entry);
2035
+ const entryStat = await (0, import_promises8.stat)(entryPath);
2036
+ if (entryStat.isDirectory()) {
2037
+ await replaceTemplateVariables(entryPath, projectName);
2038
+ return;
2039
+ }
2040
+ if (!isTextTemplateFile(entryPath)) {
2041
+ return;
2042
+ }
2043
+ const content = await (0, import_promises8.readFile)(entryPath, "utf8");
2044
+ await (0, import_promises8.writeFile)(entryPath, content.replaceAll("__PROJECT_NAME__", projectName), "utf8");
2045
+ })
2046
+ );
2038
2047
  }
2039
2048
  function shouldCopyTemplateEntry(templateDir, source, userFilter) {
2040
2049
  const relativePath = import_node_path11.default.relative(templateDir, source);
@@ -2048,16 +2057,9 @@ function shouldCopyTemplateEntry(templateDir, source, userFilter) {
2048
2057
  return !relativePath.split(import_node_path11.default.sep).some((segment) => TEMPLATE_COPY_EXCLUDED_DIRS.has(segment));
2049
2058
  }
2050
2059
  function isTextTemplateFile(filePath) {
2051
- return [
2052
- ".json",
2053
- ".md",
2054
- ".rs",
2055
- ".toml",
2056
- ".ts",
2057
- ".tsx",
2058
- ".css",
2059
- ".html"
2060
- ].includes(import_node_path11.default.extname(filePath));
2060
+ return [".json", ".md", ".rs", ".toml", ".ts", ".tsx", ".css", ".html"].includes(
2061
+ import_node_path11.default.extname(filePath)
2062
+ );
2061
2063
  }
2062
2064
 
2063
2065
  // src/scaffold/create-zk-project.ts
@@ -2107,23 +2109,28 @@ export default defineConfig({
2107
2109
  `;
2108
2110
  }
2109
2111
  function packageJsonSource(projectName) {
2110
- return `${JSON.stringify({
2111
- name: projectName,
2112
- version: "0.1.0",
2113
- private: true,
2114
- type: "module",
2115
- scripts: {
2116
- "zk:build": "caatinga zk build main",
2117
- "zk:prove": "caatinga zk prove main",
2118
- build: "caatinga build verifier",
2119
- deploy: "caatinga deploy verifier --network testnet --source ${CAATINGA_SOURCE:-alice}",
2120
- doctor: "caatinga doctor --network testnet"
2112
+ return `${JSON.stringify(
2113
+ {
2114
+ name: projectName,
2115
+ version: "0.1.0",
2116
+ private: true,
2117
+ type: "module",
2118
+ scripts: {
2119
+ "zk:build": "caatinga zk build main",
2120
+ "zk:prove": "caatinga zk prove main",
2121
+ build: "caatinga build verifier",
2122
+ deploy: "caatinga deploy verifier --network testnet --source ${CAATINGA_SOURCE:-alice}",
2123
+ doctor: "caatinga doctor --network testnet",
2124
+ test: "cargo test --manifest-path contracts/verifier/Cargo.toml"
2125
+ },
2126
+ devDependencies: {
2127
+ "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2128
+ "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2129
+ }
2121
2130
  },
2122
- devDependencies: {
2123
- "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2124
- "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2125
- }
2126
- }, null, 2)}
2131
+ null,
2132
+ 2
2133
+ )}
2127
2134
  `;
2128
2135
  }
2129
2136
  function readmeSource(projectName) {
@@ -2135,12 +2142,23 @@ Minimal Caatinga ZK project.
2135
2142
 
2136
2143
  \`\`\`bash
2137
2144
  npm install
2145
+ npm test
2138
2146
  npx caatinga zk build main
2139
2147
  npx caatinga build verifier
2140
2148
  npx caatinga deploy verifier --network testnet --source <identity>
2141
2149
  npx caatinga zk prove main
2142
2150
  \`\`\`
2143
2151
 
2152
+ ## Tests
2153
+
2154
+ Run the Rust verifier contract tests from the project root:
2155
+
2156
+ \`\`\`bash
2157
+ npm test
2158
+ # or directly:
2159
+ cargo test --manifest-path contracts/verifier/Cargo.toml
2160
+ \`\`\`
2161
+
2144
2162
  Replace \`circuits/main.circom\` with your circuit. Keep the entry point named \`main\`.
2145
2163
  `;
2146
2164
  }
@@ -2151,10 +2169,22 @@ async function createZkProject(options) {
2151
2169
  await (0, import_promises9.mkdir)(targetDir, { recursive: true });
2152
2170
  if (projectFiles) {
2153
2171
  await Promise.all([
2154
- (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2155
- (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "package.json"), packageJsonSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2156
- (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2157
- (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "README.md"), readmeSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
2172
+ (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), {
2173
+ encoding: "utf8",
2174
+ flag: force ? "w" : "wx"
2175
+ }),
2176
+ (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "package.json"), packageJsonSource(options.projectName), {
2177
+ encoding: "utf8",
2178
+ flag: force ? "w" : "wx"
2179
+ }),
2180
+ (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", {
2181
+ encoding: "utf8",
2182
+ flag: force ? "w" : "wx"
2183
+ }),
2184
+ (0, import_promises9.writeFile)(import_node_path12.default.join(targetDir, "README.md"), readmeSource(options.projectName), {
2185
+ encoding: "utf8",
2186
+ flag: force ? "w" : "wx"
2187
+ })
2158
2188
  ]);
2159
2189
  }
2160
2190
  await (0, import_promises9.mkdir)(import_node_path12.default.join(targetDir, "contracts"), { recursive: true });
@@ -2163,13 +2193,20 @@ async function createZkProject(options) {
2163
2193
  force,
2164
2194
  errorOnExist: !force
2165
2195
  });
2166
- await (0, import_promises9.cp)(import_node_path12.default.join(scaffoldRoot(), "zk-verifier"), import_node_path12.default.join(targetDir, "contracts", "verifier"), {
2167
- recursive: true,
2168
- force,
2169
- errorOnExist: !force
2170
- });
2196
+ await (0, import_promises9.cp)(
2197
+ import_node_path12.default.join(scaffoldRoot(), "zk-verifier"),
2198
+ import_node_path12.default.join(targetDir, "contracts", "verifier"),
2199
+ {
2200
+ recursive: true,
2201
+ force,
2202
+ errorOnExist: !force
2203
+ }
2204
+ );
2171
2205
  if (projectFiles) {
2172
- await writeArtifacts(createInitialArtifacts(options.projectName, { networks: ["testnet"] }), targetDir);
2206
+ await writeArtifacts(
2207
+ createInitialArtifacts(options.projectName, { networks: ["testnet"] }),
2208
+ targetDir
2209
+ );
2173
2210
  }
2174
2211
  return { targetDir };
2175
2212
  }
@@ -2211,23 +2248,28 @@ export default defineConfig({
2211
2248
  `;
2212
2249
  }
2213
2250
  function packageJsonSource2(projectName) {
2214
- return `${JSON.stringify({
2215
- name: projectName,
2216
- version: "0.1.0",
2217
- private: true,
2218
- type: "module",
2219
- scripts: {
2220
- build: "caatinga build app",
2221
- deploy: "caatinga deploy app --network testnet --source ${CAATINGA_SOURCE:-alice}",
2222
- doctor: "caatinga doctor --network testnet",
2223
- "read:hello": "caatinga read app.hello --network testnet --source ${CAATINGA_SOURCE:-alice}",
2224
- "read:version": "caatinga read app.version --network testnet --source ${CAATINGA_SOURCE:-alice}"
2251
+ return `${JSON.stringify(
2252
+ {
2253
+ name: projectName,
2254
+ version: "0.1.0",
2255
+ private: true,
2256
+ type: "module",
2257
+ scripts: {
2258
+ build: "caatinga build app",
2259
+ deploy: "caatinga deploy app --network testnet --source ${CAATINGA_SOURCE:-alice}",
2260
+ doctor: "caatinga doctor --network testnet",
2261
+ test: "cargo test --manifest-path contracts/app/Cargo.toml",
2262
+ "read:hello": "caatinga read app.hello --network testnet --source ${CAATINGA_SOURCE:-alice}",
2263
+ "read:version": "caatinga read app.version --network testnet --source ${CAATINGA_SOURCE:-alice}"
2264
+ },
2265
+ devDependencies: {
2266
+ "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2267
+ "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2268
+ }
2225
2269
  },
2226
- devDependencies: {
2227
- "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2228
- "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2229
- }
2230
- }, null, 2)}
2270
+ null,
2271
+ 2
2272
+ )}
2231
2273
  `;
2232
2274
  }
2233
2275
  function readmeSource2(projectName) {
@@ -2239,6 +2281,7 @@ Minimal Caatinga project with a Soroban contract stub (no frontend template).
2239
2281
 
2240
2282
  \`\`\`bash
2241
2283
  npm install
2284
+ npm test
2242
2285
  npx caatinga doctor
2243
2286
  npx caatinga build app
2244
2287
  npx caatinga deploy app --network testnet --source <identity>
@@ -2246,6 +2289,16 @@ npx caatinga read app.version --network testnet
2246
2289
  npx caatinga read app.hello --network testnet
2247
2290
  \`\`\`
2248
2291
 
2292
+ ## Tests
2293
+
2294
+ Run the Rust contract tests from the project root:
2295
+
2296
+ \`\`\`bash
2297
+ npm test
2298
+ # or directly:
2299
+ cargo test --manifest-path contracts/app/Cargo.toml
2300
+ \`\`\`
2301
+
2249
2302
  ## Contract
2250
2303
 
2251
2304
  - \`hello()\` \u2014 read-only; returns Soroban Symbol \`hello\`
@@ -2263,18 +2316,37 @@ async function createMinimalProject(options) {
2263
2316
  const force = options.force ?? false;
2264
2317
  await (0, import_promises10.mkdir)(targetDir, { recursive: true });
2265
2318
  await Promise.all([
2266
- (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "caatinga.config.ts"), configSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2267
- (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "package.json"), packageJsonSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2268
- (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2269
- (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "README.md"), readmeSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
2319
+ (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "caatinga.config.ts"), configSource2(options.projectName), {
2320
+ encoding: "utf8",
2321
+ flag: force ? "w" : "wx"
2322
+ }),
2323
+ (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "package.json"), packageJsonSource2(options.projectName), {
2324
+ encoding: "utf8",
2325
+ flag: force ? "w" : "wx"
2326
+ }),
2327
+ (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", {
2328
+ encoding: "utf8",
2329
+ flag: force ? "w" : "wx"
2330
+ }),
2331
+ (0, import_promises10.writeFile)(import_node_path13.default.join(targetDir, "README.md"), readmeSource2(options.projectName), {
2332
+ encoding: "utf8",
2333
+ flag: force ? "w" : "wx"
2334
+ })
2270
2335
  ]);
2271
2336
  await (0, import_promises10.mkdir)(import_node_path13.default.join(targetDir, "contracts"), { recursive: true });
2272
- await (0, import_promises10.cp)(import_node_path13.default.join(scaffoldRoot2(), "soroban-contract-stub"), import_node_path13.default.join(targetDir, "contracts", "app"), {
2273
- recursive: true,
2274
- force,
2275
- errorOnExist: !force
2276
- });
2277
- await writeArtifacts(createInitialArtifacts(options.projectName, { networks: ["testnet"] }), targetDir);
2337
+ await (0, import_promises10.cp)(
2338
+ import_node_path13.default.join(scaffoldRoot2(), "soroban-contract-stub"),
2339
+ import_node_path13.default.join(targetDir, "contracts", "app"),
2340
+ {
2341
+ recursive: true,
2342
+ force,
2343
+ errorOnExist: !force
2344
+ }
2345
+ );
2346
+ await writeArtifacts(
2347
+ createInitialArtifacts(options.projectName, { networks: ["testnet"] }),
2348
+ targetDir
2349
+ );
2278
2350
  return { targetDir };
2279
2351
  }
2280
2352
 
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CaatingaArtifacts, c as ContractArtifact, d as CaatingaErrorCodeValue, a as CaatingaError } from './artifact.schema-CGCFfl0g.cjs';
2
- export { e as CaatingaArtifactsSchema, b as CaatingaErrorCode, f as formatCaatingaError, t as toCaatingaError } from './artifact.schema-CGCFfl0g.cjs';
1
+ import { C as CaatingaArtifacts, c as ContractArtifact, d as CaatingaErrorCodeValue, a as CaatingaError } from './artifact.schema-D4r8dyYK.cjs';
2
+ export { e as CaatingaArtifactsSchema, b as CaatingaErrorCode, f as formatCaatingaError, t as toCaatingaError } from './artifact.schema-D4r8dyYK.cjs';
3
3
  import { z } from 'zod';
4
4
 
5
5
  declare const CAATINGA_CORE_VERSION: string;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CaatingaArtifacts, c as ContractArtifact, d as CaatingaErrorCodeValue, a as CaatingaError } from './artifact.schema-CGCFfl0g.js';
2
- export { e as CaatingaArtifactsSchema, b as CaatingaErrorCode, f as formatCaatingaError, t as toCaatingaError } from './artifact.schema-CGCFfl0g.js';
1
+ import { C as CaatingaArtifacts, c as ContractArtifact, d as CaatingaErrorCodeValue, a as CaatingaError } from './artifact.schema-D4r8dyYK.js';
2
+ export { e as CaatingaArtifactsSchema, b as CaatingaErrorCode, f as formatCaatingaError, t as toCaatingaError } from './artifact.schema-D4r8dyYK.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  declare const CAATINGA_CORE_VERSION: string;
package/dist/index.js CHANGED
@@ -121,9 +121,7 @@ function formatCause(cause) {
121
121
 
122
122
  // src/version.ts
123
123
  import { createRequire } from "module";
124
- var require2 = createRequire(
125
- typeof __filename === "string" ? __filename : import.meta.url
126
- );
124
+ var require2 = createRequire(typeof __filename === "string" ? __filename : import.meta.url);
127
125
  var CAATINGA_CORE_VERSION = require2("../package.json").version;
128
126
 
129
127
  // src/config/config.schema.ts
@@ -770,12 +768,7 @@ function matchesWellKnownNetwork(name, config) {
770
768
  return known.rpcUrl === config.rpcUrl && known.networkPassphrase === config.networkPassphrase;
771
769
  }
772
770
  function buildRpcNetworkArgs(config) {
773
- return [
774
- "--rpc-url",
775
- config.rpcUrl,
776
- "--network-passphrase",
777
- config.networkPassphrase
778
- ];
771
+ return ["--rpc-url", config.rpcUrl, "--network-passphrase", config.networkPassphrase];
779
772
  }
780
773
  function buildStellarNetworkArgsFromConfig(config) {
781
774
  for (const [name, known] of Object.entries(WELL_KNOWN_NETWORKS)) {
@@ -829,19 +822,23 @@ async function fetchCreateContractSalt(horizonUrl, transactionHash, fetchImpl =
829
822
  }
830
823
  async function resolveContractIdFromDeploySalt(options) {
831
824
  const saltHex = decimalSaltToHex(options.salt);
832
- const result = await runCommand("stellar", [
833
- "contract",
834
- "id",
835
- "wasm",
836
- "--salt",
837
- saltHex,
838
- "--source-account",
839
- options.source,
840
- ...buildStellarNetworkArgsFromConfig(options.network)
841
- ], {
842
- cwd: options.cwd,
843
- skipStellarVersionCheck: true
844
- });
825
+ const result = await runCommand(
826
+ "stellar",
827
+ [
828
+ "contract",
829
+ "id",
830
+ "wasm",
831
+ "--salt",
832
+ saltHex,
833
+ "--source-account",
834
+ options.source,
835
+ ...buildStellarNetworkArgsFromConfig(options.network)
836
+ ],
837
+ {
838
+ cwd: options.cwd,
839
+ skipStellarVersionCheck: true
840
+ }
841
+ );
845
842
  return parseContractId(result.all || `${result.stdout}
846
843
  ${result.stderr}`);
847
844
  }
@@ -989,8 +986,12 @@ function buildAlternateWasmCandidates(configuredWasmPath, options) {
989
986
  addCandidate(path8.join(cargoTargetDir, LEGACY_RUST_WASM_TARGET, "release", fileName));
990
987
  }
991
988
  if (options?.sourcePath) {
992
- addCandidate(path8.join(options.sourcePath, "target", CURRENT_RUST_WASM_TARGET, "release", fileName));
993
- addCandidate(path8.join(options.sourcePath, "target", LEGACY_RUST_WASM_TARGET, "release", fileName));
989
+ addCandidate(
990
+ path8.join(options.sourcePath, "target", CURRENT_RUST_WASM_TARGET, "release", fileName)
991
+ );
992
+ addCandidate(
993
+ path8.join(options.sourcePath, "target", LEGACY_RUST_WASM_TARGET, "release", fileName)
994
+ );
994
995
  }
995
996
  return candidates;
996
997
  }
@@ -1405,17 +1406,21 @@ function toSkippedContract(name, contractId, network) {
1405
1406
  // src/contracts/verify-dependency-contract.ts
1406
1407
  async function verifyDependencyContract(options) {
1407
1408
  try {
1408
- await runCommand("stellar", [
1409
- "contract",
1410
- "info",
1411
- "interface",
1412
- "--contract-id",
1413
- options.contractId,
1414
- ...buildStellarNetworkArgs(options.network)
1415
- ], {
1416
- cwd: options.cwd,
1417
- failureCode: CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND
1418
- });
1409
+ await runCommand(
1410
+ "stellar",
1411
+ [
1412
+ "contract",
1413
+ "info",
1414
+ "interface",
1415
+ "--contract-id",
1416
+ options.contractId,
1417
+ ...buildStellarNetworkArgs(options.network)
1418
+ ],
1419
+ {
1420
+ cwd: options.cwd,
1421
+ failureCode: CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND
1422
+ }
1423
+ );
1419
1424
  } catch (error) {
1420
1425
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND) {
1421
1426
  throw new CaatingaError(
@@ -1477,9 +1482,7 @@ async function deployContractGraph(options) {
1477
1482
  network: network.name
1478
1483
  });
1479
1484
  if (existing?.contractId && !options.force) {
1480
- skippedContracts.push(
1481
- toSkippedContract(contractName, existing.contractId, network.name)
1482
- );
1485
+ skippedContracts.push(toSkippedContract(contractName, existing.contractId, network.name));
1483
1486
  continue;
1484
1487
  }
1485
1488
  const result = await deployContract({
@@ -1500,9 +1503,7 @@ async function deployContractGraph(options) {
1500
1503
  });
1501
1504
  }
1502
1505
  if (result.skipped) {
1503
- skippedContracts.push(
1504
- toSkippedContract(contractName, result.contractId, network.name)
1505
- );
1506
+ skippedContracts.push(toSkippedContract(contractName, result.contractId, network.name));
1506
1507
  } else {
1507
1508
  deployedContracts.push({ name: contractName, contractId: result.contractId });
1508
1509
  }
@@ -1563,22 +1564,26 @@ async function generateBindings(options) {
1563
1564
  }
1564
1565
  const outputDir = path10.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1565
1566
  await mkdir2(outputDir, { recursive: true });
1566
- const result = await runCommand("npx", [
1567
- "--yes",
1568
- "@stellar/stellar-sdk",
1569
- "generate",
1570
- "--contract-id",
1571
- contractArtifact.contractId,
1572
- "--output-dir",
1573
- outputDir,
1574
- "--contract-name",
1575
- options.contractName,
1576
- "--overwrite",
1577
- ...buildGenerateNetworkArgs(network)
1578
- ], {
1579
- cwd,
1580
- failureCode: CaatingaErrorCode.BINDINGS_FAILED
1581
- });
1567
+ const result = await runCommand(
1568
+ "npx",
1569
+ [
1570
+ "--yes",
1571
+ "@stellar/stellar-sdk",
1572
+ "generate",
1573
+ "--contract-id",
1574
+ contractArtifact.contractId,
1575
+ "--output-dir",
1576
+ outputDir,
1577
+ "--contract-name",
1578
+ options.contractName,
1579
+ "--overwrite",
1580
+ ...buildGenerateNetworkArgs(network)
1581
+ ],
1582
+ {
1583
+ cwd,
1584
+ failureCode: CaatingaErrorCode.BINDINGS_FAILED
1585
+ }
1586
+ );
1582
1587
  const legacyStubRemoved = await removeLegacyBindingStub(
1583
1588
  cwd,
1584
1589
  options.config.frontend.bindingsOutput,
@@ -1690,21 +1695,25 @@ async function invokeContract(options) {
1690
1695
  await checkBinary("stellar", "Install Stellar CLI before running caatinga invoke.");
1691
1696
  let result;
1692
1697
  try {
1693
- result = await runCommand("stellar", [
1694
- "contract",
1695
- "invoke",
1696
- "--id",
1697
- contractArtifact.contractId,
1698
- "--source-account",
1699
- source,
1700
- ...buildStellarNetworkArgs(network),
1701
- "--",
1702
- target.method,
1703
- ...options.args ?? []
1704
- ], {
1705
- cwd,
1706
- failureCode: CaatingaErrorCode.INVOKE_FAILED
1707
- });
1698
+ result = await runCommand(
1699
+ "stellar",
1700
+ [
1701
+ "contract",
1702
+ "invoke",
1703
+ "--id",
1704
+ contractArtifact.contractId,
1705
+ "--source-account",
1706
+ source,
1707
+ ...buildStellarNetworkArgs(network),
1708
+ "--",
1709
+ target.method,
1710
+ ...options.args ?? []
1711
+ ],
1712
+ {
1713
+ cwd,
1714
+ failureCode: CaatingaErrorCode.INVOKE_FAILED
1715
+ }
1716
+ );
1708
1717
  } catch (error) {
1709
1718
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && isReadCallFailure(error)) {
1710
1719
  throw new CaatingaError(
@@ -1849,12 +1858,7 @@ function formatTemplateCompatibilityHint(issue) {
1849
1858
  }
1850
1859
 
1851
1860
  // src/templates/create-project-from-template.ts
1852
- var TEMPLATE_COPY_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1853
- "target",
1854
- "test_snapshots",
1855
- "node_modules",
1856
- ".git"
1857
- ]);
1861
+ var TEMPLATE_COPY_EXCLUDED_DIRS = /* @__PURE__ */ new Set(["target", "test_snapshots", "node_modules", ".git"]);
1858
1862
  async function createProjectFromTemplate(options) {
1859
1863
  const targetDir = path11.resolve(options.targetDir);
1860
1864
  const templateDir = path11.resolve(options.templateDir);
@@ -1872,8 +1876,8 @@ async function createProjectFromTemplate(options) {
1872
1876
  await mkdir3(targetDir, { recursive: true });
1873
1877
  await cp(templateDir, targetDir, {
1874
1878
  recursive: true,
1875
- force: mergeIntoExisting,
1876
- errorOnExist: !mergeIntoExisting,
1879
+ force: true,
1880
+ errorOnExist: false,
1877
1881
  filter: (source) => shouldCopyTemplateEntry(templateDir, source, options.filter)
1878
1882
  });
1879
1883
  await replaceTemplateVariables(targetDir, options.projectName);
@@ -1888,7 +1892,10 @@ async function ensureArtifacts(targetDir, projectName) {
1888
1892
  await writeArtifacts({ ...artifacts, project: projectName }, targetDir);
1889
1893
  } catch (error) {
1890
1894
  if (error instanceof CaatingaError && error.code === CaatingaErrorCode.ARTIFACT_NOT_FOUND) {
1891
- await writeArtifacts(createInitialArtifacts(projectName, { networks: ["testnet"] }), targetDir);
1895
+ await writeArtifacts(
1896
+ createInitialArtifacts(projectName, { networks: ["testnet"] }),
1897
+ targetDir
1898
+ );
1892
1899
  return;
1893
1900
  }
1894
1901
  throw error;
@@ -1931,19 +1938,21 @@ async function readTemplateManifest(templateDir) {
1931
1938
  }
1932
1939
  async function replaceTemplateVariables(dir, projectName) {
1933
1940
  const entries = await readdir3(dir);
1934
- await Promise.all(entries.map(async (entry) => {
1935
- const entryPath = path11.join(dir, entry);
1936
- const entryStat = await stat2(entryPath);
1937
- if (entryStat.isDirectory()) {
1938
- await replaceTemplateVariables(entryPath, projectName);
1939
- return;
1940
- }
1941
- if (!isTextTemplateFile(entryPath)) {
1942
- return;
1943
- }
1944
- const content = await readFile4(entryPath, "utf8");
1945
- await writeFile3(entryPath, content.replaceAll("__PROJECT_NAME__", projectName), "utf8");
1946
- }));
1941
+ await Promise.all(
1942
+ entries.map(async (entry) => {
1943
+ const entryPath = path11.join(dir, entry);
1944
+ const entryStat = await stat2(entryPath);
1945
+ if (entryStat.isDirectory()) {
1946
+ await replaceTemplateVariables(entryPath, projectName);
1947
+ return;
1948
+ }
1949
+ if (!isTextTemplateFile(entryPath)) {
1950
+ return;
1951
+ }
1952
+ const content = await readFile4(entryPath, "utf8");
1953
+ await writeFile3(entryPath, content.replaceAll("__PROJECT_NAME__", projectName), "utf8");
1954
+ })
1955
+ );
1947
1956
  }
1948
1957
  function shouldCopyTemplateEntry(templateDir, source, userFilter) {
1949
1958
  const relativePath = path11.relative(templateDir, source);
@@ -1957,16 +1966,9 @@ function shouldCopyTemplateEntry(templateDir, source, userFilter) {
1957
1966
  return !relativePath.split(path11.sep).some((segment) => TEMPLATE_COPY_EXCLUDED_DIRS.has(segment));
1958
1967
  }
1959
1968
  function isTextTemplateFile(filePath) {
1960
- return [
1961
- ".json",
1962
- ".md",
1963
- ".rs",
1964
- ".toml",
1965
- ".ts",
1966
- ".tsx",
1967
- ".css",
1968
- ".html"
1969
- ].includes(path11.extname(filePath));
1969
+ return [".json", ".md", ".rs", ".toml", ".ts", ".tsx", ".css", ".html"].includes(
1970
+ path11.extname(filePath)
1971
+ );
1970
1972
  }
1971
1973
 
1972
1974
  // src/scaffold/create-zk-project.ts
@@ -2015,23 +2017,28 @@ export default defineConfig({
2015
2017
  `;
2016
2018
  }
2017
2019
  function packageJsonSource(projectName) {
2018
- return `${JSON.stringify({
2019
- name: projectName,
2020
- version: "0.1.0",
2021
- private: true,
2022
- type: "module",
2023
- scripts: {
2024
- "zk:build": "caatinga zk build main",
2025
- "zk:prove": "caatinga zk prove main",
2026
- build: "caatinga build verifier",
2027
- deploy: "caatinga deploy verifier --network testnet --source ${CAATINGA_SOURCE:-alice}",
2028
- doctor: "caatinga doctor --network testnet"
2020
+ return `${JSON.stringify(
2021
+ {
2022
+ name: projectName,
2023
+ version: "0.1.0",
2024
+ private: true,
2025
+ type: "module",
2026
+ scripts: {
2027
+ "zk:build": "caatinga zk build main",
2028
+ "zk:prove": "caatinga zk prove main",
2029
+ build: "caatinga build verifier",
2030
+ deploy: "caatinga deploy verifier --network testnet --source ${CAATINGA_SOURCE:-alice}",
2031
+ doctor: "caatinga doctor --network testnet",
2032
+ test: "cargo test --manifest-path contracts/verifier/Cargo.toml"
2033
+ },
2034
+ devDependencies: {
2035
+ "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2036
+ "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2037
+ }
2029
2038
  },
2030
- devDependencies: {
2031
- "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2032
- "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2033
- }
2034
- }, null, 2)}
2039
+ null,
2040
+ 2
2041
+ )}
2035
2042
  `;
2036
2043
  }
2037
2044
  function readmeSource(projectName) {
@@ -2043,12 +2050,23 @@ Minimal Caatinga ZK project.
2043
2050
 
2044
2051
  \`\`\`bash
2045
2052
  npm install
2053
+ npm test
2046
2054
  npx caatinga zk build main
2047
2055
  npx caatinga build verifier
2048
2056
  npx caatinga deploy verifier --network testnet --source <identity>
2049
2057
  npx caatinga zk prove main
2050
2058
  \`\`\`
2051
2059
 
2060
+ ## Tests
2061
+
2062
+ Run the Rust verifier contract tests from the project root:
2063
+
2064
+ \`\`\`bash
2065
+ npm test
2066
+ # or directly:
2067
+ cargo test --manifest-path contracts/verifier/Cargo.toml
2068
+ \`\`\`
2069
+
2052
2070
  Replace \`circuits/main.circom\` with your circuit. Keep the entry point named \`main\`.
2053
2071
  `;
2054
2072
  }
@@ -2059,10 +2077,22 @@ async function createZkProject(options) {
2059
2077
  await mkdir4(targetDir, { recursive: true });
2060
2078
  if (projectFiles) {
2061
2079
  await Promise.all([
2062
- writeFile4(path12.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2063
- writeFile4(path12.join(targetDir, "package.json"), packageJsonSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2064
- writeFile4(path12.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2065
- writeFile4(path12.join(targetDir, "README.md"), readmeSource(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
2080
+ writeFile4(path12.join(targetDir, "caatinga.config.ts"), configSource(options.projectName), {
2081
+ encoding: "utf8",
2082
+ flag: force ? "w" : "wx"
2083
+ }),
2084
+ writeFile4(path12.join(targetDir, "package.json"), packageJsonSource(options.projectName), {
2085
+ encoding: "utf8",
2086
+ flag: force ? "w" : "wx"
2087
+ }),
2088
+ writeFile4(path12.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", {
2089
+ encoding: "utf8",
2090
+ flag: force ? "w" : "wx"
2091
+ }),
2092
+ writeFile4(path12.join(targetDir, "README.md"), readmeSource(options.projectName), {
2093
+ encoding: "utf8",
2094
+ flag: force ? "w" : "wx"
2095
+ })
2066
2096
  ]);
2067
2097
  }
2068
2098
  await mkdir4(path12.join(targetDir, "contracts"), { recursive: true });
@@ -2071,13 +2101,20 @@ async function createZkProject(options) {
2071
2101
  force,
2072
2102
  errorOnExist: !force
2073
2103
  });
2074
- await cp2(path12.join(scaffoldRoot(), "zk-verifier"), path12.join(targetDir, "contracts", "verifier"), {
2075
- recursive: true,
2076
- force,
2077
- errorOnExist: !force
2078
- });
2104
+ await cp2(
2105
+ path12.join(scaffoldRoot(), "zk-verifier"),
2106
+ path12.join(targetDir, "contracts", "verifier"),
2107
+ {
2108
+ recursive: true,
2109
+ force,
2110
+ errorOnExist: !force
2111
+ }
2112
+ );
2079
2113
  if (projectFiles) {
2080
- await writeArtifacts(createInitialArtifacts(options.projectName, { networks: ["testnet"] }), targetDir);
2114
+ await writeArtifacts(
2115
+ createInitialArtifacts(options.projectName, { networks: ["testnet"] }),
2116
+ targetDir
2117
+ );
2081
2118
  }
2082
2119
  return { targetDir };
2083
2120
  }
@@ -2118,23 +2155,28 @@ export default defineConfig({
2118
2155
  `;
2119
2156
  }
2120
2157
  function packageJsonSource2(projectName) {
2121
- return `${JSON.stringify({
2122
- name: projectName,
2123
- version: "0.1.0",
2124
- private: true,
2125
- type: "module",
2126
- scripts: {
2127
- build: "caatinga build app",
2128
- deploy: "caatinga deploy app --network testnet --source ${CAATINGA_SOURCE:-alice}",
2129
- doctor: "caatinga doctor --network testnet",
2130
- "read:hello": "caatinga read app.hello --network testnet --source ${CAATINGA_SOURCE:-alice}",
2131
- "read:version": "caatinga read app.version --network testnet --source ${CAATINGA_SOURCE:-alice}"
2158
+ return `${JSON.stringify(
2159
+ {
2160
+ name: projectName,
2161
+ version: "0.1.0",
2162
+ private: true,
2163
+ type: "module",
2164
+ scripts: {
2165
+ build: "caatinga build app",
2166
+ deploy: "caatinga deploy app --network testnet --source ${CAATINGA_SOURCE:-alice}",
2167
+ doctor: "caatinga doctor --network testnet",
2168
+ test: "cargo test --manifest-path contracts/app/Cargo.toml",
2169
+ "read:hello": "caatinga read app.hello --network testnet --source ${CAATINGA_SOURCE:-alice}",
2170
+ "read:version": "caatinga read app.version --network testnet --source ${CAATINGA_SOURCE:-alice}"
2171
+ },
2172
+ devDependencies: {
2173
+ "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2174
+ "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2175
+ }
2132
2176
  },
2133
- devDependencies: {
2134
- "@caatinga/cli": `^${CAATINGA_CORE_VERSION}`,
2135
- "@caatinga/core": `^${CAATINGA_CORE_VERSION}`
2136
- }
2137
- }, null, 2)}
2177
+ null,
2178
+ 2
2179
+ )}
2138
2180
  `;
2139
2181
  }
2140
2182
  function readmeSource2(projectName) {
@@ -2146,6 +2188,7 @@ Minimal Caatinga project with a Soroban contract stub (no frontend template).
2146
2188
 
2147
2189
  \`\`\`bash
2148
2190
  npm install
2191
+ npm test
2149
2192
  npx caatinga doctor
2150
2193
  npx caatinga build app
2151
2194
  npx caatinga deploy app --network testnet --source <identity>
@@ -2153,6 +2196,16 @@ npx caatinga read app.version --network testnet
2153
2196
  npx caatinga read app.hello --network testnet
2154
2197
  \`\`\`
2155
2198
 
2199
+ ## Tests
2200
+
2201
+ Run the Rust contract tests from the project root:
2202
+
2203
+ \`\`\`bash
2204
+ npm test
2205
+ # or directly:
2206
+ cargo test --manifest-path contracts/app/Cargo.toml
2207
+ \`\`\`
2208
+
2156
2209
  ## Contract
2157
2210
 
2158
2211
  - \`hello()\` \u2014 read-only; returns Soroban Symbol \`hello\`
@@ -2170,18 +2223,37 @@ async function createMinimalProject(options) {
2170
2223
  const force = options.force ?? false;
2171
2224
  await mkdir5(targetDir, { recursive: true });
2172
2225
  await Promise.all([
2173
- writeFile5(path13.join(targetDir, "caatinga.config.ts"), configSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2174
- writeFile5(path13.join(targetDir, "package.json"), packageJsonSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" }),
2175
- writeFile5(path13.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", { encoding: "utf8", flag: force ? "w" : "wx" }),
2176
- writeFile5(path13.join(targetDir, "README.md"), readmeSource2(options.projectName), { encoding: "utf8", flag: force ? "w" : "wx" })
2226
+ writeFile5(path13.join(targetDir, "caatinga.config.ts"), configSource2(options.projectName), {
2227
+ encoding: "utf8",
2228
+ flag: force ? "w" : "wx"
2229
+ }),
2230
+ writeFile5(path13.join(targetDir, "package.json"), packageJsonSource2(options.projectName), {
2231
+ encoding: "utf8",
2232
+ flag: force ? "w" : "wx"
2233
+ }),
2234
+ writeFile5(path13.join(targetDir, ".gitignore"), "node_modules\n.artifacts\ntarget\n", {
2235
+ encoding: "utf8",
2236
+ flag: force ? "w" : "wx"
2237
+ }),
2238
+ writeFile5(path13.join(targetDir, "README.md"), readmeSource2(options.projectName), {
2239
+ encoding: "utf8",
2240
+ flag: force ? "w" : "wx"
2241
+ })
2177
2242
  ]);
2178
2243
  await mkdir5(path13.join(targetDir, "contracts"), { recursive: true });
2179
- await cp3(path13.join(scaffoldRoot2(), "soroban-contract-stub"), path13.join(targetDir, "contracts", "app"), {
2180
- recursive: true,
2181
- force,
2182
- errorOnExist: !force
2183
- });
2184
- await writeArtifacts(createInitialArtifacts(options.projectName, { networks: ["testnet"] }), targetDir);
2244
+ await cp3(
2245
+ path13.join(scaffoldRoot2(), "soroban-contract-stub"),
2246
+ path13.join(targetDir, "contracts", "app"),
2247
+ {
2248
+ recursive: true,
2249
+ force,
2250
+ errorOnExist: !force
2251
+ }
2252
+ );
2253
+ await writeArtifacts(
2254
+ createInitialArtifacts(options.projectName, { networks: ["testnet"] }),
2255
+ targetDir
2256
+ );
2185
2257
  return { targetDir };
2186
2258
  }
2187
2259
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caatinga/core",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Core config, artifacts, command orchestration, and error primitives for Caatinga/Soroban toolkit",
5
5
  "keywords": [
6
6
  "stellar",