@caatinga/core 0.2.1 → 0.2.2

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.js CHANGED
@@ -1,72 +1,11 @@
1
- // src/errors/CaatingaErrorCode.ts
2
- var CaatingaErrorCode = {
3
- CONFIG_NOT_FOUND: "CAATINGA_CONFIG_NOT_FOUND",
4
- INVALID_CONFIG: "CAATINGA_INVALID_CONFIG",
5
- COMMAND_FAILED: "CAATINGA_COMMAND_FAILED",
6
- UNEXPECTED_ERROR: "CAATINGA_UNEXPECTED_ERROR",
7
- STELLAR_CLI_NOT_FOUND: "CAATINGA_STELLAR_CLI_NOT_FOUND",
8
- STELLAR_CLI_VERSION_PARSE_FAILED: "CAATINGA_STELLAR_CLI_VERSION_PARSE_FAILED",
9
- UNSUPPORTED_CLI_VERSION: "CAATINGA_UNSUPPORTED_CLI_VERSION",
10
- UNTESTED_CLI_VERSION: "CAATINGA_UNTESTED_CLI_VERSION",
11
- RUST_NOT_FOUND: "CAATINGA_RUST_NOT_FOUND",
12
- RUST_TARGET_NOT_FOUND: "CAATINGA_RUST_TARGET_NOT_FOUND",
13
- DEPLOY_FAILED: "CAATINGA_DEPLOY_FAILED",
14
- BUILD_FAILED: "CAATINGA_BUILD_FAILED",
15
- BINDINGS_FAILED: "CAATINGA_BINDINGS_FAILED",
16
- INVOKE_FAILED: "CAATINGA_INVOKE_FAILED",
17
- CONTRACT_NOT_FOUND: "CAATINGA_CONTRACT_NOT_FOUND",
18
- NETWORK_NOT_FOUND: "CAATINGA_NETWORK_NOT_FOUND",
19
- ARTIFACT_NOT_FOUND: "CAATINGA_ARTIFACT_NOT_FOUND",
20
- ARTIFACT_INVALID: "CAATINGA_ARTIFACT_INVALID",
21
- CONTRACT_ID_NOT_FOUND: "CAATINGA_CONTRACT_ID_NOT_FOUND",
22
- CONTRACT_ARTIFACT_NOT_FOUND: "CAATINGA_CONTRACT_ARTIFACT_NOT_FOUND",
23
- CONTRACT_DEPENDENCY_NOT_FOUND: "CAATINGA_CONTRACT_DEPENDENCY_NOT_FOUND",
24
- CONTRACT_DEPENDENCY_CYCLE: "CAATINGA_CONTRACT_DEPENDENCY_CYCLE",
25
- CONTRACT_DEPENDENCY_ARTIFACT_NOT_FOUND: "CAATINGA_CONTRACT_DEPENDENCY_ARTIFACT_NOT_FOUND",
26
- DEPLOY_ARG_PLACEHOLDER_INVALID: "CAATINGA_DEPLOY_ARG_PLACEHOLDER_INVALID",
27
- DEPLOY_ARG_PLACEHOLDER_UNRESOLVED: "CAATINGA_DEPLOY_ARG_PLACEHOLDER_UNRESOLVED",
28
- BINDING_CLIENT_NOT_FOUND: "CAATINGA_BINDING_CLIENT_NOT_FOUND",
29
- BINDING_METHOD_NOT_FOUND: "CAATINGA_BINDING_METHOD_NOT_FOUND",
30
- XDR_BUILD_FAILED: "CAATINGA_XDR_BUILD_FAILED",
31
- XDR_PREPARE_FAILED: "CAATINGA_XDR_PREPARE_FAILED",
32
- XDR_SIGN_FAILED: "CAATINGA_XDR_SIGN_FAILED",
33
- XDR_SUBMIT_FAILED: "CAATINGA_XDR_SUBMIT_FAILED",
34
- XDR_RESULT_FAILED: "CAATINGA_XDR_RESULT_FAILED",
35
- WALLET_NOT_CONNECTED: "CAATINGA_WALLET_NOT_CONNECTED",
36
- SOURCE_ACCOUNT_REQUIRED: "CAATINGA_SOURCE_ACCOUNT_REQUIRED",
37
- UNSAFE_SOURCE_ACCOUNT: "CAATINGA_UNSAFE_SOURCE_ACCOUNT",
38
- INVOKE_TARGET_INVALID: "CAATINGA_INVOKE_TARGET_INVALID",
39
- TEMPLATE_NOT_FOUND: "CAATINGA_TEMPLATE_NOT_FOUND",
40
- INVALID_TEMPLATE_MANIFEST: "CAATINGA_INVALID_TEMPLATE_MANIFEST",
41
- TEMPLATE_MANIFEST_NOT_FOUND: "CAATINGA_TEMPLATE_MANIFEST_NOT_FOUND",
42
- TEMPLATE_INCOMPATIBLE: "CAATINGA_TEMPLATE_INCOMPATIBLE"
43
- };
44
-
45
- // src/errors/CaatingaError.ts
46
- var CaatingaError = class extends Error {
47
- constructor(message, code, hint, cause) {
48
- super(message);
49
- this.code = code;
50
- this.hint = hint;
51
- this.cause = cause;
52
- this.name = "CaatingaError";
53
- }
54
- code;
55
- hint;
56
- cause;
57
- };
58
- function toCaatingaError(error) {
59
- if (error instanceof CaatingaError) {
60
- return error;
61
- }
62
- if (error instanceof Error) {
63
- return new CaatingaError(error.message, CaatingaErrorCode.UNEXPECTED_ERROR, void 0, error);
64
- }
65
- return new CaatingaError("An unexpected error occurred.", CaatingaErrorCode.UNEXPECTED_ERROR);
66
- }
1
+ import {
2
+ CaatingaError,
3
+ CaatingaErrorCode,
4
+ toCaatingaError
5
+ } from "./chunk-GMABXVEY.js";
67
6
 
68
7
  // src/version.ts
69
- var CAATINGA_CORE_VERSION = "0.2.1";
8
+ var CAATINGA_CORE_VERSION = "0.2.2";
70
9
 
71
10
  // src/config/config.schema.ts
72
11
  import { z } from "zod";
@@ -253,7 +192,7 @@ import { execa } from "execa";
253
192
 
254
193
  // src/stellar-cli/version.ts
255
194
  import semver from "semver";
256
- var STELLAR_CLI_MIN_VERSION = "22.0.0";
195
+ var STELLAR_CLI_MIN_VERSION = "23.0.0";
257
196
  var STELLAR_CLI_TESTED_MAX_VERSION = "25.2.0";
258
197
  var STELLAR_CLI_SEMVER_REGEX = /\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?)\b/;
259
198
  function parseStellarCliVersion(output) {
@@ -386,6 +325,146 @@ function parseContractId(output) {
386
325
  return match[0];
387
326
  }
388
327
 
328
+ // src/stellar-cli/build-stellar-network-args.ts
329
+ function matchesWellKnownNetwork(name, config) {
330
+ const known = WELL_KNOWN_NETWORKS[name];
331
+ if (!known) {
332
+ return false;
333
+ }
334
+ return known.rpcUrl === config.rpcUrl && known.networkPassphrase === config.networkPassphrase;
335
+ }
336
+ function buildRpcNetworkArgs(config) {
337
+ return [
338
+ "--rpc-url",
339
+ config.rpcUrl,
340
+ "--network-passphrase",
341
+ config.networkPassphrase
342
+ ];
343
+ }
344
+ function buildStellarNetworkArgsFromConfig(config) {
345
+ for (const [name, known] of Object.entries(WELL_KNOWN_NETWORKS)) {
346
+ if (known.rpcUrl === config.rpcUrl && known.networkPassphrase === config.networkPassphrase) {
347
+ return ["--network", name];
348
+ }
349
+ }
350
+ return buildRpcNetworkArgs(config);
351
+ }
352
+ function buildStellarNetworkArgs(network) {
353
+ if (matchesWellKnownNetwork(network.name, network.config)) {
354
+ return ["--network", network.name];
355
+ }
356
+ return buildStellarNetworkArgsFromConfig(network.config);
357
+ }
358
+
359
+ // src/stellar-cli/recover-deploy-contract-id.ts
360
+ var TX_HASH_REGEX = /Transaction hash is ([a-f0-9]{64})/i;
361
+ var DEPLOY_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
362
+ var HORIZON_URL_BY_PASSPHRASE = {
363
+ "Test SDF Network ; September 2015": "https://horizon-testnet.stellar.org",
364
+ "Public Global Stellar Network ; September 2015": "https://horizon.stellar.org"
365
+ };
366
+ function isLikelyPublicKeySource(source) {
367
+ return /^G[A-Z2-7]{55}$/.test(source);
368
+ }
369
+ function decimalSaltToHex(salt) {
370
+ return BigInt(salt).toString(16).padStart(64, "0");
371
+ }
372
+ function resolveHorizonUrl(network) {
373
+ const horizonUrl = HORIZON_URL_BY_PASSPHRASE[network.networkPassphrase];
374
+ if (!horizonUrl) {
375
+ throw new CaatingaError(
376
+ `No Horizon URL mapping for network passphrase "${network.networkPassphrase}".`,
377
+ CaatingaErrorCode.NETWORK_NOT_FOUND,
378
+ "Use testnet or mainnet, or extend Caatinga network metadata."
379
+ );
380
+ }
381
+ return horizonUrl;
382
+ }
383
+ async function fetchCreateContractSalt(horizonUrl, transactionHash, fetchImpl = fetch) {
384
+ const response = await fetchImpl(`${horizonUrl}/transactions/${transactionHash}/operations`);
385
+ if (!response.ok) {
386
+ return null;
387
+ }
388
+ const body = await response.json();
389
+ const operation = body._embedded?.records?.find(
390
+ (record) => record.transaction_successful === true && record.type === "invoke_host_function" && record.function === "HostFunctionTypeHostFunctionTypeCreateContract" && typeof record.salt === "string"
391
+ );
392
+ return operation?.salt ?? null;
393
+ }
394
+ async function resolveContractIdFromDeploySalt(options) {
395
+ const saltHex = decimalSaltToHex(options.salt);
396
+ const result = await runCommand("stellar", [
397
+ "contract",
398
+ "id",
399
+ "wasm",
400
+ "--salt",
401
+ saltHex,
402
+ "--source-account",
403
+ options.source,
404
+ ...buildStellarNetworkArgsFromConfig(options.network)
405
+ ], {
406
+ cwd: options.cwd,
407
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
408
+ skipStellarVersionCheck: true
409
+ });
410
+ return parseContractId(result.all || `${result.stdout}
411
+ ${result.stderr}`);
412
+ }
413
+ async function tryRecoverContractIdFromDeployFailure(options) {
414
+ if (!DEPLOY_SIGNING_FAILURE_REGEX.test(options.output)) {
415
+ return null;
416
+ }
417
+ const hashMatch = options.output.match(TX_HASH_REGEX);
418
+ if (!hashMatch) {
419
+ return null;
420
+ }
421
+ const horizonUrl = resolveHorizonUrl(options.network);
422
+ const salt = await fetchCreateContractSalt(horizonUrl, hashMatch[1], options.fetchImpl);
423
+ if (!salt) {
424
+ return null;
425
+ }
426
+ return resolveContractIdFromDeploySalt({
427
+ salt,
428
+ source: options.source,
429
+ network: options.network,
430
+ cwd: options.cwd,
431
+ allowUntestedStellarCli: options.allowUntestedStellarCli
432
+ });
433
+ }
434
+
435
+ // src/contracts/validate-source-shape.ts
436
+ function validateSourceShape(source) {
437
+ if (source.startsWith("S")) {
438
+ return new CaatingaError(
439
+ "Refusing to accept a Stellar secret key as --source.",
440
+ CaatingaErrorCode.SOURCE_IS_SECRET_KEY,
441
+ "Use a Stellar CLI identity alias instead, for example: --source alice"
442
+ );
443
+ }
444
+ if (source.trim().includes(" ")) {
445
+ return new CaatingaError(
446
+ "Refusing to accept a seed phrase as --source.",
447
+ CaatingaErrorCode.SOURCE_IS_SEED_PHRASE,
448
+ "Use a Stellar CLI identity alias instead, for example: --source alice"
449
+ );
450
+ }
451
+ if (isLikelyPublicKeySource(source)) {
452
+ return new CaatingaError(
453
+ `Public account address cannot sign transactions: ${source}`,
454
+ CaatingaErrorCode.SOURCE_IS_PUBLIC_KEY,
455
+ "Use a Stellar CLI identity with a secret key. Example: stellar keys generate alice --fund --network testnet, then --source alice"
456
+ );
457
+ }
458
+ if (source.startsWith("G")) {
459
+ return new CaatingaError(
460
+ "Refusing to accept a public account address as --source.",
461
+ CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
462
+ "Use a Stellar CLI identity alias, not a public address. Example: --source alice"
463
+ );
464
+ }
465
+ return void 0;
466
+ }
467
+
389
468
  // src/contracts/resolve-contract.ts
390
469
  import path4 from "path";
391
470
  function resolveContract(config, contractName, cwd = process.cwd()) {
@@ -407,25 +486,99 @@ function resolveContract(config, contractName, cwd = process.cwd()) {
407
486
 
408
487
  // src/contracts/wasm.ts
409
488
  import { createHash } from "crypto";
410
- import { access as access2, readFile as readFile2 } from "fs/promises";
411
- async function assertWasmExists(wasmPath) {
489
+ import { access as access2, readdir, readFile as readFile2, stat } from "fs/promises";
490
+ import path5 from "path";
491
+ var LEGACY_RUST_WASM_TARGET = "wasm32-unknown-unknown";
492
+ var CURRENT_RUST_WASM_TARGET = "wasm32v1-none";
493
+ function toCurrentWasmTargetPath(wasmPath) {
494
+ if (!wasmPath.includes(LEGACY_RUST_WASM_TARGET)) {
495
+ return wasmPath;
496
+ }
497
+ return wasmPath.replaceAll(LEGACY_RUST_WASM_TARGET, CURRENT_RUST_WASM_TARGET);
498
+ }
499
+ function wasmNotFoundError(configuredWasmPath, options) {
500
+ const migratedPath = options?.migratedPath;
501
+ const hint = migratedPath === void 0 ? "Run caatinga build before deploy or generate." : [
502
+ "Run caatinga build before deploy or generate.",
503
+ `Soroban builds use the "${CURRENT_RUST_WASM_TARGET}" target.`,
504
+ `Update wasm in caatinga.config.ts to "${toConfigRelativeWasmPath(migratedPath)}" or an equivalent path under target/${CURRENT_RUST_WASM_TARGET}/release/.`
505
+ ].join(" ");
506
+ return new CaatingaError(
507
+ `WASM output was not found at ${configuredWasmPath}.`,
508
+ CaatingaErrorCode.ARTIFACT_NOT_FOUND,
509
+ hint
510
+ );
511
+ }
512
+ function toConfigRelativeWasmPath(absoluteWasmPath) {
513
+ const relative = path5.relative(process.cwd(), absoluteWasmPath);
514
+ return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(path5.sep).join("/")}`;
515
+ }
516
+ async function resolveWasmArtifactPath(configuredWasmPath) {
412
517
  try {
413
- await access2(wasmPath);
518
+ await access2(configuredWasmPath);
519
+ return configuredWasmPath;
414
520
  } catch {
415
- throw new CaatingaError(
416
- `WASM output was not found at ${wasmPath}.`,
417
- CaatingaErrorCode.ARTIFACT_NOT_FOUND,
418
- "Run caatinga build before deploy or generate."
419
- );
521
+ const currentTargetPath = toCurrentWasmTargetPath(configuredWasmPath);
522
+ if (currentTargetPath === configuredWasmPath) {
523
+ throw wasmNotFoundError(configuredWasmPath);
524
+ }
525
+ try {
526
+ await access2(currentTargetPath);
527
+ return currentTargetPath;
528
+ } catch {
529
+ throw wasmNotFoundError(configuredWasmPath, { migratedPath: currentTargetPath });
530
+ }
420
531
  }
421
532
  }
422
533
  async function hashWasm(wasmPath) {
423
534
  const bytes = await readFile2(wasmPath);
424
535
  return createHash("sha256").update(bytes).digest("hex");
425
536
  }
537
+ async function getNewestMtimeInDirectory(directory) {
538
+ try {
539
+ await access2(directory);
540
+ } catch {
541
+ return void 0;
542
+ }
543
+ let newest = 0;
544
+ async function walk(dir) {
545
+ const entries = await readdir(dir, { withFileTypes: true });
546
+ for (const entry of entries) {
547
+ const entryPath = path5.join(dir, entry.name);
548
+ if (entry.isDirectory()) {
549
+ await walk(entryPath);
550
+ continue;
551
+ }
552
+ if (!entry.isFile()) {
553
+ continue;
554
+ }
555
+ const fileStat = await stat(entryPath);
556
+ newest = Math.max(newest, fileStat.mtimeMs);
557
+ }
558
+ }
559
+ await walk(directory);
560
+ return newest > 0 ? newest : void 0;
561
+ }
562
+ async function isWasmOlderThanSources(input) {
563
+ const srcDir = path5.join(input.contractPath, "src");
564
+ const newestSourceMtime = await getNewestMtimeInDirectory(srcDir);
565
+ if (newestSourceMtime === void 0) {
566
+ return false;
567
+ }
568
+ let wasmStat;
569
+ try {
570
+ wasmStat = await stat(input.wasmPath);
571
+ } catch {
572
+ return false;
573
+ }
574
+ return wasmStat.mtimeMs < newestSourceMtime;
575
+ }
576
+ function formatStaleWasmWarning(contractName) {
577
+ return `WASM for "${contractName}" may be stale: contract sources under src/ are newer than the WASM file. Run \`caatinga build\` before deploy.`;
578
+ }
426
579
 
427
580
  // src/contracts/build-contract.ts
428
- var RUST_WASM_TARGET = "wasm32-unknown-unknown";
581
+ var RUST_WASM_TARGET = "wasm32v1-none";
429
582
  var MISSING_WASM_TARGET_HINT_SUBSTRINGS = [
430
583
  "not installed",
431
584
  "not found",
@@ -467,100 +620,24 @@ async function buildContract(options) {
467
620
  throw new CaatingaError(
468
621
  `Required Rust wasm target "${RUST_WASM_TARGET}" is missing.`,
469
622
  CaatingaErrorCode.RUST_TARGET_NOT_FOUND,
470
- `Run \`rustup target add ${RUST_WASM_TARGET}\` and retry the build.`,
623
+ `Run \`rustup target add ${RUST_WASM_TARGET}\` and retry.`,
471
624
  error
472
625
  );
473
626
  }
474
627
  throw error;
475
628
  }
476
- await assertWasmExists(contract.wasmPath);
629
+ const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
477
630
  return {
478
- contract,
631
+ contract: {
632
+ ...contract,
633
+ wasmPath
634
+ },
479
635
  output: result.all || result.stdout
480
636
  };
481
637
  }
482
638
 
483
639
  // src/contracts/deploy-contract.ts
484
- import path5 from "path";
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
- }
640
+ import path6 from "path";
564
641
 
565
642
  // src/contracts/dependency-graph.ts
566
643
  function buildDependencyGraph(contracts) {
@@ -611,19 +688,9 @@ function assertSafeSourceAccount(source) {
611
688
  "Pass a Stellar CLI identity alias, for example: --source alice"
612
689
  );
613
690
  }
614
- if (source.startsWith("S") || source.trim().includes(" ")) {
615
- throw new CaatingaError(
616
- "Refusing to accept a likely secret key or seed phrase as --source.",
617
- CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
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"
626
- );
691
+ const unsafeSource = validateSourceShape(source);
692
+ if (unsafeSource) {
693
+ throw unsafeSource;
627
694
  }
628
695
  return source;
629
696
  }
@@ -651,17 +718,32 @@ async function deployContract(options) {
651
718
  await checkBinary("stellar", "Install Stellar CLI before running caatinga deploy.", {
652
719
  allowUntestedStellarCli: options.allowUntestedStellarCli
653
720
  });
654
- await assertWasmExists(contract.wasmPath);
721
+ const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
722
+ const contractWithWasm = {
723
+ ...contract,
724
+ wasmPath
725
+ };
726
+ let staleWasmWarning;
727
+ if (options.checkStaleWasm !== false) {
728
+ const stale = await isWasmOlderThanSources({
729
+ wasmPath,
730
+ contractPath: contract.sourcePath
731
+ });
732
+ if (stale) {
733
+ staleWasmWarning = formatStaleWasmWarning(contract.name);
734
+ }
735
+ }
655
736
  const artifactsBefore = await readArtifacts(cwd);
656
737
  const existing = artifactsBefore.networks[network.name]?.contracts[contract.name];
657
738
  if (existing?.contractId && !options.force) {
658
739
  return {
659
- contract,
740
+ contract: contractWithWasm,
660
741
  network,
661
742
  contractId: existing.contractId,
662
- artifactsPath: path5.resolve(cwd, "caatinga.artifacts.json"),
743
+ artifactsPath: path6.resolve(cwd, "caatinga.artifacts.json"),
663
744
  output: "",
664
- skipped: true
745
+ skipped: true,
746
+ staleWasmWarning
665
747
  };
666
748
  }
667
749
  const rawDeployArgs = contract.config.deployArgs;
@@ -692,13 +774,10 @@ async function deployContract(options) {
692
774
  "contract",
693
775
  "deploy",
694
776
  "--wasm",
695
- contract.wasmPath,
777
+ wasmPath,
696
778
  "--source-account",
697
779
  source,
698
- "--rpc-url",
699
- network.config.rpcUrl,
700
- "--network-passphrase",
701
- network.config.networkPassphrase,
780
+ ...buildStellarNetworkArgs(network),
702
781
  ...constructorArgs
703
782
  ];
704
783
  let output = "";
@@ -734,7 +813,7 @@ ${error.hint ?? ""}`,
734
813
  `Contract ID: ${contractId}`
735
814
  ].filter(Boolean).join("\n");
736
815
  }
737
- const wasmHash = await hashWasm(contract.wasmPath);
816
+ const wasmHash = await hashWasm(wasmPath);
738
817
  const dependencyGraph = buildDependencyGraph(options.config.contracts);
739
818
  const dependencies = options.dependencies ?? contract.config.dependsOn;
740
819
  const nextArtifacts = updateArtifact(
@@ -754,12 +833,13 @@ ${error.hint ?? ""}`,
754
833
  );
755
834
  const artifactsPath = await writeArtifacts(nextArtifacts, cwd);
756
835
  return {
757
- contract,
836
+ contract: contractWithWasm,
758
837
  network,
759
838
  contractId,
760
839
  artifactsPath,
761
840
  output,
762
- skipped: false
841
+ skipped: false,
842
+ staleWasmWarning
763
843
  };
764
844
  }
765
845
 
@@ -814,6 +894,53 @@ function resolveDeployOrder(input) {
814
894
  }
815
895
  }
816
896
 
897
+ // src/contracts/verify-dependency-contract.ts
898
+ async function verifyDependencyContract(options) {
899
+ try {
900
+ await runCommand("stellar", [
901
+ "contract",
902
+ "info",
903
+ "interface",
904
+ "--contract-id",
905
+ options.contractId,
906
+ ...buildStellarNetworkArgs(options.network)
907
+ ], {
908
+ cwd: options.cwd,
909
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
910
+ failureCode: CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND
911
+ });
912
+ } catch (error) {
913
+ if (error instanceof CaatingaError && error.code === CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND) {
914
+ throw new CaatingaError(
915
+ `Dependency "${options.dependencyName}" is not deployed on "${options.network.name}" (contract ID ${options.contractId}).`,
916
+ CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND,
917
+ "Deploy the dependency on this network, fix caatinga.artifacts.json, or omit --verify-deps.",
918
+ error.cause
919
+ );
920
+ }
921
+ throw error;
922
+ }
923
+ }
924
+ async function verifyDependencyContracts(options) {
925
+ for (const dependencyName of options.dependencies) {
926
+ const contractArtifact = options.artifacts.networks[options.network.name]?.contracts[dependencyName];
927
+ if (!contractArtifact?.contractId) {
928
+ throw new CaatingaError(
929
+ `No dependency artifact found for "${dependencyName}" on "${options.network.name}".`,
930
+ CaatingaErrorCode.CONTRACT_DEPENDENCY_ARTIFACT_NOT_FOUND,
931
+ "Deploy the dependency first or run deploy without --no-deps."
932
+ );
933
+ }
934
+ await verifyDependencyContract({
935
+ dependencyName,
936
+ contractId: contractArtifact.contractId,
937
+ network: options.network,
938
+ cwd: options.cwd,
939
+ allowUntestedStellarCli: options.allowUntestedStellarCli
940
+ });
941
+ }
942
+ }
943
+
817
944
  // src/contracts/deploy-contract-graph.ts
818
945
  async function deployContractGraph(options) {
819
946
  const cwd = options.cwd ?? process.cwd();
@@ -824,17 +951,33 @@ async function deployContractGraph(options) {
824
951
  includeDependencies: options.includeDependencies
825
952
  });
826
953
  const deployedContracts = [];
954
+ const skippedContracts = [];
955
+ const staleWasmWarnings = [];
827
956
  for (const contractName of order) {
828
957
  const artifacts = await readArtifacts(cwd);
829
958
  const existing = artifacts.networks[network.name]?.contracts[contractName];
830
959
  const contractConfig = options.config.contracts[contractName];
960
+ if (options.verifyDeps && contractConfig.dependsOn.length > 0) {
961
+ await verifyDependencyContracts({
962
+ dependencies: contractConfig.dependsOn,
963
+ artifacts,
964
+ network,
965
+ cwd,
966
+ allowUntestedStellarCli: options.allowUntestedStellarCli
967
+ });
968
+ }
831
969
  const resolvedDeployArgs = resolveDeployArgs({
832
970
  deployArgs: contractConfig.deployArgs,
833
971
  artifacts,
834
972
  network: network.name
835
973
  });
836
974
  if (existing?.contractId && !options.force) {
837
- deployedContracts.push({ name: contractName, contractId: existing.contractId });
975
+ skippedContracts.push({
976
+ name: contractName,
977
+ contractId: existing.contractId,
978
+ network: network.name,
979
+ reason: "already-deployed"
980
+ });
838
981
  continue;
839
982
  }
840
983
  const result = await deployContract({
@@ -845,20 +988,38 @@ async function deployContractGraph(options) {
845
988
  cwd,
846
989
  allowUntestedStellarCli: options.allowUntestedStellarCli,
847
990
  force: options.force,
991
+ checkStaleWasm: options.checkStaleWasm,
848
992
  resolvedDeployArgs,
849
993
  dependencies: contractConfig.dependsOn
850
994
  });
851
- deployedContracts.push({ name: contractName, contractId: result.contractId });
995
+ if (result.staleWasmWarning) {
996
+ staleWasmWarnings.push({
997
+ contract: contractName,
998
+ message: result.staleWasmWarning
999
+ });
1000
+ }
1001
+ if (result.skipped) {
1002
+ skippedContracts.push({
1003
+ name: contractName,
1004
+ contractId: result.contractId,
1005
+ network: network.name,
1006
+ reason: "already-deployed"
1007
+ });
1008
+ } else {
1009
+ deployedContracts.push({ name: contractName, contractId: result.contractId });
1010
+ }
852
1011
  }
853
1012
  return {
854
1013
  network,
855
- deployedContracts
1014
+ deployedContracts,
1015
+ skippedContracts,
1016
+ staleWasmWarnings
856
1017
  };
857
1018
  }
858
1019
 
859
1020
  // src/contracts/generate-bindings.ts
860
1021
  import { mkdir as mkdir2 } from "fs/promises";
861
- import path6 from "path";
1022
+ import path7 from "path";
862
1023
  async function generateBindings(options) {
863
1024
  const cwd = options.cwd ?? process.cwd();
864
1025
  const network = resolveNetwork(options.config, options.networkName);
@@ -874,7 +1035,7 @@ async function generateBindings(options) {
874
1035
  await checkBinary("stellar", "Install Stellar CLI before running caatinga generate.", {
875
1036
  allowUntestedStellarCli: options.allowUntestedStellarCli
876
1037
  });
877
- const outputDir = path6.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1038
+ const outputDir = path7.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
878
1039
  await mkdir2(outputDir, { recursive: true });
879
1040
  const result = await runCommand("stellar", [
880
1041
  "contract",
@@ -885,10 +1046,7 @@ async function generateBindings(options) {
885
1046
  "--output-dir",
886
1047
  outputDir,
887
1048
  "--overwrite",
888
- "--rpc-url",
889
- network.config.rpcUrl,
890
- "--network-passphrase",
891
- network.config.networkPassphrase
1049
+ ...buildStellarNetworkArgs(network)
892
1050
  ], {
893
1051
  cwd,
894
1052
  allowUntestedStellarCli: options.allowUntestedStellarCli,
@@ -903,6 +1061,7 @@ async function generateBindings(options) {
903
1061
  }
904
1062
 
905
1063
  // src/contracts/invoke-contract.ts
1064
+ var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
906
1065
  function parseInvokeTarget(target) {
907
1066
  const [contractName, method, extra] = target.split(".");
908
1067
  if (!contractName || !method || extra) {
@@ -931,25 +1090,43 @@ async function invokeContract(options) {
931
1090
  await checkBinary("stellar", "Install Stellar CLI before running caatinga invoke.", {
932
1091
  allowUntestedStellarCli: options.allowUntestedStellarCli
933
1092
  });
934
- const result = await runCommand("stellar", [
935
- "contract",
936
- "invoke",
937
- "--id",
938
- contractArtifact.contractId,
939
- "--source-account",
940
- source,
941
- "--rpc-url",
942
- network.config.rpcUrl,
943
- "--network-passphrase",
944
- network.config.networkPassphrase,
945
- "--",
946
- target.method,
947
- ...options.args ?? []
948
- ], {
949
- cwd,
950
- allowUntestedStellarCli: options.allowUntestedStellarCli,
951
- failureCode: CaatingaErrorCode.INVOKE_FAILED
952
- });
1093
+ let result;
1094
+ try {
1095
+ result = await runCommand("stellar", [
1096
+ "contract",
1097
+ "invoke",
1098
+ "--id",
1099
+ contractArtifact.contractId,
1100
+ "--source-account",
1101
+ source,
1102
+ ...buildStellarNetworkArgs(network),
1103
+ "--",
1104
+ target.method,
1105
+ ...options.args ?? []
1106
+ ], {
1107
+ cwd,
1108
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
1109
+ failureCode: CaatingaErrorCode.INVOKE_FAILED
1110
+ });
1111
+ } catch (error) {
1112
+ if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && INVOKE_SIGNING_FAILURE_REGEX.test(`${error.message}
1113
+ ${error.hint ?? ""}`)) {
1114
+ throw new CaatingaError(
1115
+ error.message,
1116
+ error.code,
1117
+ [
1118
+ "Stellar CLI could not sign the invoke transaction (xdr value invalid).",
1119
+ "Stellar CLI 22.x has a known invoke signing bug; upgrade to 23.0.0 or newer (25.2.0 recommended).",
1120
+ " stellar --version",
1121
+ "Then retry with a funded identity, for example:",
1122
+ " stellar keys generate alice --fund --network testnet",
1123
+ " npx caatinga invoke counter.increment --network testnet --source alice"
1124
+ ].join("\n"),
1125
+ error
1126
+ );
1127
+ }
1128
+ throw error;
1129
+ }
953
1130
  return {
954
1131
  target,
955
1132
  network,
@@ -958,8 +1135,8 @@ async function invokeContract(options) {
958
1135
  }
959
1136
 
960
1137
  // src/templates/create-project-from-template.ts
961
- import { cp, mkdir as mkdir3, readFile as readFile3, readdir, stat, writeFile as writeFile2 } from "fs/promises";
962
- import path7 from "path";
1138
+ import { cp, mkdir as mkdir3, readFile as readFile3, readdir as readdir2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
1139
+ import path8 from "path";
963
1140
  import { z as z6 } from "zod";
964
1141
 
965
1142
  // src/templates/template-manifest.schema.ts
@@ -1029,10 +1206,10 @@ function formatTemplateCompatibilityHint(issue) {
1029
1206
 
1030
1207
  // src/templates/create-project-from-template.ts
1031
1208
  async function createProjectFromTemplate(options) {
1032
- const targetDir = path7.resolve(options.targetDir);
1033
- const templateDir = path7.resolve(options.templateDir);
1209
+ const targetDir = path8.resolve(options.targetDir);
1210
+ const templateDir = path8.resolve(options.templateDir);
1034
1211
  try {
1035
- await stat(templateDir);
1212
+ await stat2(templateDir);
1036
1213
  } catch {
1037
1214
  throw new CaatingaError(
1038
1215
  `Template directory was not found: ${templateDir}`,
@@ -1052,7 +1229,7 @@ async function createProjectFromTemplate(options) {
1052
1229
  return { targetDir, template: manifest };
1053
1230
  }
1054
1231
  async function readTemplateManifest(templateDir) {
1055
- const manifestPath = path7.join(templateDir, "caatinga.template.json");
1232
+ const manifestPath = path8.join(templateDir, "caatinga.template.json");
1056
1233
  try {
1057
1234
  const rawManifest = await readFile3(manifestPath, "utf8");
1058
1235
  const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
@@ -1087,10 +1264,10 @@ async function readTemplateManifest(templateDir) {
1087
1264
  }
1088
1265
  }
1089
1266
  async function replaceTemplateVariables(dir, projectName) {
1090
- const entries = await readdir(dir);
1267
+ const entries = await readdir2(dir);
1091
1268
  await Promise.all(entries.map(async (entry) => {
1092
- const entryPath = path7.join(dir, entry);
1093
- const entryStat = await stat(entryPath);
1269
+ const entryPath = path8.join(dir, entry);
1270
+ const entryStat = await stat2(entryPath);
1094
1271
  if (entryStat.isDirectory()) {
1095
1272
  await replaceTemplateVariables(entryPath, projectName);
1096
1273
  return;
@@ -1112,7 +1289,7 @@ function isTextTemplateFile(filePath) {
1112
1289
  ".tsx",
1113
1290
  ".css",
1114
1291
  ".html"
1115
- ].includes(path7.extname(filePath));
1292
+ ].includes(path8.extname(filePath));
1116
1293
  }
1117
1294
 
1118
1295
  // src/ci/is-transient-testnet-smoke-failure.ts
@@ -1170,5 +1347,6 @@ export {
1170
1347
  runCommand,
1171
1348
  toCaatingaError,
1172
1349
  updateArtifact,
1350
+ validateSourceShape,
1173
1351
  writeArtifacts
1174
1352
  };