@caatinga/core 0.2.1 → 0.2.3

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
@@ -63,6 +63,7 @@ __export(index_exports, {
63
63
  runCommand: () => runCommand,
64
64
  toCaatingaError: () => toCaatingaError,
65
65
  updateArtifact: () => updateArtifact,
66
+ validateSourceShape: () => validateSourceShape,
66
67
  writeArtifacts: () => writeArtifacts
67
68
  });
68
69
  module.exports = __toCommonJS(index_exports);
@@ -92,6 +93,7 @@ var CaatingaErrorCode = {
92
93
  CONTRACT_DEPENDENCY_NOT_FOUND: "CAATINGA_CONTRACT_DEPENDENCY_NOT_FOUND",
93
94
  CONTRACT_DEPENDENCY_CYCLE: "CAATINGA_CONTRACT_DEPENDENCY_CYCLE",
94
95
  CONTRACT_DEPENDENCY_ARTIFACT_NOT_FOUND: "CAATINGA_CONTRACT_DEPENDENCY_ARTIFACT_NOT_FOUND",
96
+ DEPENDENCY_CONTRACT_NOT_FOUND: "CAATINGA_DEPENDENCY_CONTRACT_NOT_FOUND",
95
97
  DEPLOY_ARG_PLACEHOLDER_INVALID: "CAATINGA_DEPLOY_ARG_PLACEHOLDER_INVALID",
96
98
  DEPLOY_ARG_PLACEHOLDER_UNRESOLVED: "CAATINGA_DEPLOY_ARG_PLACEHOLDER_UNRESOLVED",
97
99
  BINDING_CLIENT_NOT_FOUND: "CAATINGA_BINDING_CLIENT_NOT_FOUND",
@@ -101,14 +103,20 @@ var CaatingaErrorCode = {
101
103
  XDR_SIGN_FAILED: "CAATINGA_XDR_SIGN_FAILED",
102
104
  XDR_SUBMIT_FAILED: "CAATINGA_XDR_SUBMIT_FAILED",
103
105
  XDR_RESULT_FAILED: "CAATINGA_XDR_RESULT_FAILED",
106
+ READ_RESULT_MISSING: "CAATINGA_READ_RESULT_MISSING",
104
107
  WALLET_NOT_CONNECTED: "CAATINGA_WALLET_NOT_CONNECTED",
108
+ WALLET_TIMEOUT: "CAATINGA_WALLET_TIMEOUT",
105
109
  SOURCE_ACCOUNT_REQUIRED: "CAATINGA_SOURCE_ACCOUNT_REQUIRED",
110
+ SOURCE_IS_SECRET_KEY: "CAATINGA_SOURCE_IS_SECRET_KEY",
111
+ SOURCE_IS_SEED_PHRASE: "CAATINGA_SOURCE_IS_SEED_PHRASE",
112
+ SOURCE_IS_PUBLIC_KEY: "CAATINGA_SOURCE_IS_PUBLIC_KEY",
106
113
  UNSAFE_SOURCE_ACCOUNT: "CAATINGA_UNSAFE_SOURCE_ACCOUNT",
107
114
  INVOKE_TARGET_INVALID: "CAATINGA_INVOKE_TARGET_INVALID",
108
115
  TEMPLATE_NOT_FOUND: "CAATINGA_TEMPLATE_NOT_FOUND",
109
116
  INVALID_TEMPLATE_MANIFEST: "CAATINGA_INVALID_TEMPLATE_MANIFEST",
110
117
  TEMPLATE_MANIFEST_NOT_FOUND: "CAATINGA_TEMPLATE_MANIFEST_NOT_FOUND",
111
- TEMPLATE_INCOMPATIBLE: "CAATINGA_TEMPLATE_INCOMPATIBLE"
118
+ TEMPLATE_INCOMPATIBLE: "CAATINGA_TEMPLATE_INCOMPATIBLE",
119
+ DOCTOR_PARTIAL_DEPLOY: "CAATINGA_DOCTOR_PARTIAL_DEPLOY"
112
120
  };
113
121
 
114
122
  // src/errors/CaatingaError.ts
@@ -135,7 +143,7 @@ function toCaatingaError(error) {
135
143
  }
136
144
 
137
145
  // src/version.ts
138
- var CAATINGA_CORE_VERSION = "0.2.1";
146
+ var CAATINGA_CORE_VERSION = "0.2.3";
139
147
 
140
148
  // src/config/config.schema.ts
141
149
  var import_zod = require("zod");
@@ -323,7 +331,7 @@ var import_execa = require("execa");
323
331
 
324
332
  // src/stellar-cli/version.ts
325
333
  var import_semver = __toESM(require("semver"), 1);
326
- var STELLAR_CLI_MIN_VERSION = "22.0.0";
334
+ var STELLAR_CLI_MIN_VERSION = "23.0.0";
327
335
  var STELLAR_CLI_TESTED_MAX_VERSION = "25.2.0";
328
336
  var STELLAR_CLI_SEMVER_REGEX = /\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?)\b/;
329
337
  function parseStellarCliVersion(output) {
@@ -456,6 +464,146 @@ function parseContractId(output) {
456
464
  return match[0];
457
465
  }
458
466
 
467
+ // src/stellar-cli/build-stellar-network-args.ts
468
+ function matchesWellKnownNetwork(name, config) {
469
+ const known = WELL_KNOWN_NETWORKS[name];
470
+ if (!known) {
471
+ return false;
472
+ }
473
+ return known.rpcUrl === config.rpcUrl && known.networkPassphrase === config.networkPassphrase;
474
+ }
475
+ function buildRpcNetworkArgs(config) {
476
+ return [
477
+ "--rpc-url",
478
+ config.rpcUrl,
479
+ "--network-passphrase",
480
+ config.networkPassphrase
481
+ ];
482
+ }
483
+ function buildStellarNetworkArgsFromConfig(config) {
484
+ for (const [name, known] of Object.entries(WELL_KNOWN_NETWORKS)) {
485
+ if (known.rpcUrl === config.rpcUrl && known.networkPassphrase === config.networkPassphrase) {
486
+ return ["--network", name];
487
+ }
488
+ }
489
+ return buildRpcNetworkArgs(config);
490
+ }
491
+ function buildStellarNetworkArgs(network) {
492
+ if (matchesWellKnownNetwork(network.name, network.config)) {
493
+ return ["--network", network.name];
494
+ }
495
+ return buildStellarNetworkArgsFromConfig(network.config);
496
+ }
497
+
498
+ // src/stellar-cli/recover-deploy-contract-id.ts
499
+ var TX_HASH_REGEX = /Transaction hash is ([a-f0-9]{64})/i;
500
+ var DEPLOY_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
501
+ var HORIZON_URL_BY_PASSPHRASE = {
502
+ "Test SDF Network ; September 2015": "https://horizon-testnet.stellar.org",
503
+ "Public Global Stellar Network ; September 2015": "https://horizon.stellar.org"
504
+ };
505
+ function isLikelyPublicKeySource(source) {
506
+ return /^G[A-Z2-7]{55}$/.test(source);
507
+ }
508
+ function decimalSaltToHex(salt) {
509
+ return BigInt(salt).toString(16).padStart(64, "0");
510
+ }
511
+ function resolveHorizonUrl(network) {
512
+ const horizonUrl = HORIZON_URL_BY_PASSPHRASE[network.networkPassphrase];
513
+ if (!horizonUrl) {
514
+ throw new CaatingaError(
515
+ `No Horizon URL mapping for network passphrase "${network.networkPassphrase}".`,
516
+ CaatingaErrorCode.NETWORK_NOT_FOUND,
517
+ "Use testnet or mainnet, or extend Caatinga network metadata."
518
+ );
519
+ }
520
+ return horizonUrl;
521
+ }
522
+ async function fetchCreateContractSalt(horizonUrl, transactionHash, fetchImpl = fetch) {
523
+ const response = await fetchImpl(`${horizonUrl}/transactions/${transactionHash}/operations`);
524
+ if (!response.ok) {
525
+ return null;
526
+ }
527
+ const body = await response.json();
528
+ const operation = body._embedded?.records?.find(
529
+ (record) => record.transaction_successful === true && record.type === "invoke_host_function" && record.function === "HostFunctionTypeHostFunctionTypeCreateContract" && typeof record.salt === "string"
530
+ );
531
+ return operation?.salt ?? null;
532
+ }
533
+ async function resolveContractIdFromDeploySalt(options) {
534
+ const saltHex = decimalSaltToHex(options.salt);
535
+ const result = await runCommand("stellar", [
536
+ "contract",
537
+ "id",
538
+ "wasm",
539
+ "--salt",
540
+ saltHex,
541
+ "--source-account",
542
+ options.source,
543
+ ...buildStellarNetworkArgsFromConfig(options.network)
544
+ ], {
545
+ cwd: options.cwd,
546
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
547
+ skipStellarVersionCheck: true
548
+ });
549
+ return parseContractId(result.all || `${result.stdout}
550
+ ${result.stderr}`);
551
+ }
552
+ async function tryRecoverContractIdFromDeployFailure(options) {
553
+ if (!DEPLOY_SIGNING_FAILURE_REGEX.test(options.output)) {
554
+ return null;
555
+ }
556
+ const hashMatch = options.output.match(TX_HASH_REGEX);
557
+ if (!hashMatch) {
558
+ return null;
559
+ }
560
+ const horizonUrl = resolveHorizonUrl(options.network);
561
+ const salt = await fetchCreateContractSalt(horizonUrl, hashMatch[1], options.fetchImpl);
562
+ if (!salt) {
563
+ return null;
564
+ }
565
+ return resolveContractIdFromDeploySalt({
566
+ salt,
567
+ source: options.source,
568
+ network: options.network,
569
+ cwd: options.cwd,
570
+ allowUntestedStellarCli: options.allowUntestedStellarCli
571
+ });
572
+ }
573
+
574
+ // src/contracts/validate-source-shape.ts
575
+ function validateSourceShape(source) {
576
+ if (source.startsWith("S")) {
577
+ return new CaatingaError(
578
+ "Refusing to accept a Stellar secret key as --source.",
579
+ CaatingaErrorCode.SOURCE_IS_SECRET_KEY,
580
+ "Use a Stellar CLI identity alias instead, for example: --source alice"
581
+ );
582
+ }
583
+ if (source.trim().includes(" ")) {
584
+ return new CaatingaError(
585
+ "Refusing to accept a seed phrase as --source.",
586
+ CaatingaErrorCode.SOURCE_IS_SEED_PHRASE,
587
+ "Use a Stellar CLI identity alias instead, for example: --source alice"
588
+ );
589
+ }
590
+ if (isLikelyPublicKeySource(source)) {
591
+ return new CaatingaError(
592
+ `Public account address cannot sign transactions: ${source}`,
593
+ CaatingaErrorCode.SOURCE_IS_PUBLIC_KEY,
594
+ "Use a Stellar CLI identity with a secret key. Example: stellar keys generate alice --fund --network testnet, then --source alice"
595
+ );
596
+ }
597
+ if (source.startsWith("G")) {
598
+ return new CaatingaError(
599
+ "Refusing to accept a public account address as --source.",
600
+ CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
601
+ "Use a Stellar CLI identity alias, not a public address. Example: --source alice"
602
+ );
603
+ }
604
+ return void 0;
605
+ }
606
+
459
607
  // src/contracts/resolve-contract.ts
460
608
  var import_node_path4 = __toESM(require("path"), 1);
461
609
  function resolveContract(config, contractName, cwd = process.cwd()) {
@@ -478,24 +626,98 @@ function resolveContract(config, contractName, cwd = process.cwd()) {
478
626
  // src/contracts/wasm.ts
479
627
  var import_node_crypto = require("crypto");
480
628
  var import_promises4 = require("fs/promises");
481
- async function assertWasmExists(wasmPath) {
629
+ var import_node_path5 = __toESM(require("path"), 1);
630
+ var LEGACY_RUST_WASM_TARGET = "wasm32-unknown-unknown";
631
+ var CURRENT_RUST_WASM_TARGET = "wasm32v1-none";
632
+ function toCurrentWasmTargetPath(wasmPath) {
633
+ if (!wasmPath.includes(LEGACY_RUST_WASM_TARGET)) {
634
+ return wasmPath;
635
+ }
636
+ return wasmPath.replaceAll(LEGACY_RUST_WASM_TARGET, CURRENT_RUST_WASM_TARGET);
637
+ }
638
+ function wasmNotFoundError(configuredWasmPath, options) {
639
+ const migratedPath = options?.migratedPath;
640
+ const hint = migratedPath === void 0 ? "Run caatinga build before deploy or generate." : [
641
+ "Run caatinga build before deploy or generate.",
642
+ `Soroban builds use the "${CURRENT_RUST_WASM_TARGET}" target.`,
643
+ `Update wasm in caatinga.config.ts to "${toConfigRelativeWasmPath(migratedPath)}" or an equivalent path under target/${CURRENT_RUST_WASM_TARGET}/release/.`
644
+ ].join(" ");
645
+ return new CaatingaError(
646
+ `WASM output was not found at ${configuredWasmPath}.`,
647
+ CaatingaErrorCode.ARTIFACT_NOT_FOUND,
648
+ hint
649
+ );
650
+ }
651
+ function toConfigRelativeWasmPath(absoluteWasmPath) {
652
+ const relative = import_node_path5.default.relative(process.cwd(), absoluteWasmPath);
653
+ return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(import_node_path5.default.sep).join("/")}`;
654
+ }
655
+ async function resolveWasmArtifactPath(configuredWasmPath) {
482
656
  try {
483
- await (0, import_promises4.access)(wasmPath);
657
+ await (0, import_promises4.access)(configuredWasmPath);
658
+ return configuredWasmPath;
484
659
  } catch {
485
- throw new CaatingaError(
486
- `WASM output was not found at ${wasmPath}.`,
487
- CaatingaErrorCode.ARTIFACT_NOT_FOUND,
488
- "Run caatinga build before deploy or generate."
489
- );
660
+ const currentTargetPath = toCurrentWasmTargetPath(configuredWasmPath);
661
+ if (currentTargetPath === configuredWasmPath) {
662
+ throw wasmNotFoundError(configuredWasmPath);
663
+ }
664
+ try {
665
+ await (0, import_promises4.access)(currentTargetPath);
666
+ return currentTargetPath;
667
+ } catch {
668
+ throw wasmNotFoundError(configuredWasmPath, { migratedPath: currentTargetPath });
669
+ }
490
670
  }
491
671
  }
492
672
  async function hashWasm(wasmPath) {
493
673
  const bytes = await (0, import_promises4.readFile)(wasmPath);
494
674
  return (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
495
675
  }
676
+ async function getNewestMtimeInDirectory(directory) {
677
+ try {
678
+ await (0, import_promises4.access)(directory);
679
+ } catch {
680
+ return void 0;
681
+ }
682
+ let newest = 0;
683
+ async function walk(dir) {
684
+ const entries = await (0, import_promises4.readdir)(dir, { withFileTypes: true });
685
+ for (const entry of entries) {
686
+ const entryPath = import_node_path5.default.join(dir, entry.name);
687
+ if (entry.isDirectory()) {
688
+ await walk(entryPath);
689
+ continue;
690
+ }
691
+ if (!entry.isFile()) {
692
+ continue;
693
+ }
694
+ const fileStat = await (0, import_promises4.stat)(entryPath);
695
+ newest = Math.max(newest, fileStat.mtimeMs);
696
+ }
697
+ }
698
+ await walk(directory);
699
+ return newest > 0 ? newest : void 0;
700
+ }
701
+ async function isWasmOlderThanSources(input) {
702
+ const srcDir = import_node_path5.default.join(input.contractPath, "src");
703
+ const newestSourceMtime = await getNewestMtimeInDirectory(srcDir);
704
+ if (newestSourceMtime === void 0) {
705
+ return false;
706
+ }
707
+ let wasmStat;
708
+ try {
709
+ wasmStat = await (0, import_promises4.stat)(input.wasmPath);
710
+ } catch {
711
+ return false;
712
+ }
713
+ return wasmStat.mtimeMs < newestSourceMtime;
714
+ }
715
+ function formatStaleWasmWarning(contractName) {
716
+ return `WASM for "${contractName}" may be stale: contract sources under src/ are newer than the WASM file. Run \`caatinga build\` before deploy.`;
717
+ }
496
718
 
497
719
  // src/contracts/build-contract.ts
498
- var RUST_WASM_TARGET = "wasm32-unknown-unknown";
720
+ var RUST_WASM_TARGET = "wasm32v1-none";
499
721
  var MISSING_WASM_TARGET_HINT_SUBSTRINGS = [
500
722
  "not installed",
501
723
  "not found",
@@ -537,100 +759,24 @@ async function buildContract(options) {
537
759
  throw new CaatingaError(
538
760
  `Required Rust wasm target "${RUST_WASM_TARGET}" is missing.`,
539
761
  CaatingaErrorCode.RUST_TARGET_NOT_FOUND,
540
- `Run \`rustup target add ${RUST_WASM_TARGET}\` and retry the build.`,
762
+ `Run \`rustup target add ${RUST_WASM_TARGET}\` and retry.`,
541
763
  error
542
764
  );
543
765
  }
544
766
  throw error;
545
767
  }
546
- await assertWasmExists(contract.wasmPath);
768
+ const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
547
769
  return {
548
- contract,
770
+ contract: {
771
+ ...contract,
772
+ wasmPath
773
+ },
549
774
  output: result.all || result.stdout
550
775
  };
551
776
  }
552
777
 
553
778
  // src/contracts/deploy-contract.ts
554
- var import_node_path5 = __toESM(require("path"), 1);
555
-
556
- // src/stellar-cli/recover-deploy-contract-id.ts
557
- var TX_HASH_REGEX = /Transaction hash is ([a-f0-9]{64})/i;
558
- var DEPLOY_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
559
- var HORIZON_URL_BY_PASSPHRASE = {
560
- "Test SDF Network ; September 2015": "https://horizon-testnet.stellar.org",
561
- "Public Global Stellar Network ; September 2015": "https://horizon.stellar.org"
562
- };
563
- function isLikelyPublicKeySource(source) {
564
- return /^G[A-Z2-7]{55}$/.test(source);
565
- }
566
- function decimalSaltToHex(salt) {
567
- return BigInt(salt).toString(16).padStart(64, "0");
568
- }
569
- function resolveHorizonUrl(network) {
570
- const horizonUrl = HORIZON_URL_BY_PASSPHRASE[network.networkPassphrase];
571
- if (!horizonUrl) {
572
- throw new CaatingaError(
573
- `No Horizon URL mapping for network passphrase "${network.networkPassphrase}".`,
574
- CaatingaErrorCode.NETWORK_NOT_FOUND,
575
- "Use testnet or mainnet, or extend Caatinga network metadata."
576
- );
577
- }
578
- return horizonUrl;
579
- }
580
- async function fetchCreateContractSalt(horizonUrl, transactionHash, fetchImpl = fetch) {
581
- const response = await fetchImpl(`${horizonUrl}/transactions/${transactionHash}/operations`);
582
- if (!response.ok) {
583
- return null;
584
- }
585
- const body = await response.json();
586
- const operation = body._embedded?.records?.find(
587
- (record) => record.transaction_successful === true && record.type === "invoke_host_function" && record.function === "HostFunctionTypeHostFunctionTypeCreateContract" && typeof record.salt === "string"
588
- );
589
- return operation?.salt ?? null;
590
- }
591
- async function resolveContractIdFromDeploySalt(options) {
592
- const saltHex = decimalSaltToHex(options.salt);
593
- const result = await runCommand("stellar", [
594
- "contract",
595
- "id",
596
- "wasm",
597
- "--salt",
598
- saltHex,
599
- "--source-account",
600
- options.source,
601
- "--rpc-url",
602
- options.network.rpcUrl,
603
- "--network-passphrase",
604
- options.network.networkPassphrase
605
- ], {
606
- cwd: options.cwd,
607
- allowUntestedStellarCli: options.allowUntestedStellarCli,
608
- skipStellarVersionCheck: true
609
- });
610
- return parseContractId(result.all || `${result.stdout}
611
- ${result.stderr}`);
612
- }
613
- async function tryRecoverContractIdFromDeployFailure(options) {
614
- if (!DEPLOY_SIGNING_FAILURE_REGEX.test(options.output)) {
615
- return null;
616
- }
617
- const hashMatch = options.output.match(TX_HASH_REGEX);
618
- if (!hashMatch) {
619
- return null;
620
- }
621
- const horizonUrl = resolveHorizonUrl(options.network);
622
- const salt = await fetchCreateContractSalt(horizonUrl, hashMatch[1], options.fetchImpl);
623
- if (!salt) {
624
- return null;
625
- }
626
- return resolveContractIdFromDeploySalt({
627
- salt,
628
- source: options.source,
629
- network: options.network,
630
- cwd: options.cwd,
631
- allowUntestedStellarCli: options.allowUntestedStellarCli
632
- });
633
- }
779
+ var import_node_path6 = __toESM(require("path"), 1);
634
780
 
635
781
  // src/contracts/dependency-graph.ts
636
782
  function buildDependencyGraph(contracts) {
@@ -681,19 +827,9 @@ function assertSafeSourceAccount(source) {
681
827
  "Pass a Stellar CLI identity alias, for example: --source alice"
682
828
  );
683
829
  }
684
- if (source.startsWith("S") || source.trim().includes(" ")) {
685
- throw new CaatingaError(
686
- "Refusing to accept a likely secret key or seed phrase as --source.",
687
- CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
688
- "Use a Stellar CLI identity alias instead, for example: --source alice"
689
- );
690
- }
691
- if (isLikelyPublicKeySource(source)) {
692
- throw new CaatingaError(
693
- `Public account address cannot sign transactions: ${source}`,
694
- CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
695
- "Use a Stellar CLI identity with a secret key. Example: stellar keys generate alice --fund --network testnet, then --source alice"
696
- );
830
+ const unsafeSource = validateSourceShape(source);
831
+ if (unsafeSource) {
832
+ throw unsafeSource;
697
833
  }
698
834
  return source;
699
835
  }
@@ -721,17 +857,32 @@ async function deployContract(options) {
721
857
  await checkBinary("stellar", "Install Stellar CLI before running caatinga deploy.", {
722
858
  allowUntestedStellarCli: options.allowUntestedStellarCli
723
859
  });
724
- await assertWasmExists(contract.wasmPath);
860
+ const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
861
+ const contractWithWasm = {
862
+ ...contract,
863
+ wasmPath
864
+ };
865
+ let staleWasmWarning;
866
+ if (options.checkStaleWasm !== false) {
867
+ const stale = await isWasmOlderThanSources({
868
+ wasmPath,
869
+ contractPath: contract.sourcePath
870
+ });
871
+ if (stale) {
872
+ staleWasmWarning = formatStaleWasmWarning(contract.name);
873
+ }
874
+ }
725
875
  const artifactsBefore = await readArtifacts(cwd);
726
876
  const existing = artifactsBefore.networks[network.name]?.contracts[contract.name];
727
877
  if (existing?.contractId && !options.force) {
728
878
  return {
729
- contract,
879
+ contract: contractWithWasm,
730
880
  network,
731
881
  contractId: existing.contractId,
732
- artifactsPath: import_node_path5.default.resolve(cwd, "caatinga.artifacts.json"),
882
+ artifactsPath: import_node_path6.default.resolve(cwd, "caatinga.artifacts.json"),
733
883
  output: "",
734
- skipped: true
884
+ skipped: true,
885
+ staleWasmWarning
735
886
  };
736
887
  }
737
888
  const rawDeployArgs = contract.config.deployArgs;
@@ -762,13 +913,10 @@ async function deployContract(options) {
762
913
  "contract",
763
914
  "deploy",
764
915
  "--wasm",
765
- contract.wasmPath,
916
+ wasmPath,
766
917
  "--source-account",
767
918
  source,
768
- "--rpc-url",
769
- network.config.rpcUrl,
770
- "--network-passphrase",
771
- network.config.networkPassphrase,
919
+ ...buildStellarNetworkArgs(network),
772
920
  ...constructorArgs
773
921
  ];
774
922
  let output = "";
@@ -804,7 +952,7 @@ ${error.hint ?? ""}`,
804
952
  `Contract ID: ${contractId}`
805
953
  ].filter(Boolean).join("\n");
806
954
  }
807
- const wasmHash = await hashWasm(contract.wasmPath);
955
+ const wasmHash = await hashWasm(wasmPath);
808
956
  const dependencyGraph = buildDependencyGraph(options.config.contracts);
809
957
  const dependencies = options.dependencies ?? contract.config.dependsOn;
810
958
  const nextArtifacts = updateArtifact(
@@ -824,12 +972,13 @@ ${error.hint ?? ""}`,
824
972
  );
825
973
  const artifactsPath = await writeArtifacts(nextArtifacts, cwd);
826
974
  return {
827
- contract,
975
+ contract: contractWithWasm,
828
976
  network,
829
977
  contractId,
830
978
  artifactsPath,
831
979
  output,
832
- skipped: false
980
+ skipped: false,
981
+ staleWasmWarning
833
982
  };
834
983
  }
835
984
 
@@ -884,6 +1033,53 @@ function resolveDeployOrder(input) {
884
1033
  }
885
1034
  }
886
1035
 
1036
+ // src/contracts/verify-dependency-contract.ts
1037
+ async function verifyDependencyContract(options) {
1038
+ try {
1039
+ await runCommand("stellar", [
1040
+ "contract",
1041
+ "info",
1042
+ "interface",
1043
+ "--contract-id",
1044
+ options.contractId,
1045
+ ...buildStellarNetworkArgs(options.network)
1046
+ ], {
1047
+ cwd: options.cwd,
1048
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
1049
+ failureCode: CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND
1050
+ });
1051
+ } catch (error) {
1052
+ if (error instanceof CaatingaError && error.code === CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND) {
1053
+ throw new CaatingaError(
1054
+ `Dependency "${options.dependencyName}" is not deployed on "${options.network.name}" (contract ID ${options.contractId}).`,
1055
+ CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND,
1056
+ "Deploy the dependency on this network, fix caatinga.artifacts.json, or omit --verify-deps.",
1057
+ error.cause
1058
+ );
1059
+ }
1060
+ throw error;
1061
+ }
1062
+ }
1063
+ async function verifyDependencyContracts(options) {
1064
+ for (const dependencyName of options.dependencies) {
1065
+ const contractArtifact = options.artifacts.networks[options.network.name]?.contracts[dependencyName];
1066
+ if (!contractArtifact?.contractId) {
1067
+ throw new CaatingaError(
1068
+ `No dependency artifact found for "${dependencyName}" on "${options.network.name}".`,
1069
+ CaatingaErrorCode.CONTRACT_DEPENDENCY_ARTIFACT_NOT_FOUND,
1070
+ "Deploy the dependency first or run deploy without --no-deps."
1071
+ );
1072
+ }
1073
+ await verifyDependencyContract({
1074
+ dependencyName,
1075
+ contractId: contractArtifact.contractId,
1076
+ network: options.network,
1077
+ cwd: options.cwd,
1078
+ allowUntestedStellarCli: options.allowUntestedStellarCli
1079
+ });
1080
+ }
1081
+ }
1082
+
887
1083
  // src/contracts/deploy-contract-graph.ts
888
1084
  async function deployContractGraph(options) {
889
1085
  const cwd = options.cwd ?? process.cwd();
@@ -894,17 +1090,33 @@ async function deployContractGraph(options) {
894
1090
  includeDependencies: options.includeDependencies
895
1091
  });
896
1092
  const deployedContracts = [];
1093
+ const skippedContracts = [];
1094
+ const staleWasmWarnings = [];
897
1095
  for (const contractName of order) {
898
1096
  const artifacts = await readArtifacts(cwd);
899
1097
  const existing = artifacts.networks[network.name]?.contracts[contractName];
900
1098
  const contractConfig = options.config.contracts[contractName];
1099
+ if (options.verifyDeps && contractConfig.dependsOn.length > 0) {
1100
+ await verifyDependencyContracts({
1101
+ dependencies: contractConfig.dependsOn,
1102
+ artifacts,
1103
+ network,
1104
+ cwd,
1105
+ allowUntestedStellarCli: options.allowUntestedStellarCli
1106
+ });
1107
+ }
901
1108
  const resolvedDeployArgs = resolveDeployArgs({
902
1109
  deployArgs: contractConfig.deployArgs,
903
1110
  artifacts,
904
1111
  network: network.name
905
1112
  });
906
1113
  if (existing?.contractId && !options.force) {
907
- deployedContracts.push({ name: contractName, contractId: existing.contractId });
1114
+ skippedContracts.push({
1115
+ name: contractName,
1116
+ contractId: existing.contractId,
1117
+ network: network.name,
1118
+ reason: "already-deployed"
1119
+ });
908
1120
  continue;
909
1121
  }
910
1122
  const result = await deployContract({
@@ -915,20 +1127,38 @@ async function deployContractGraph(options) {
915
1127
  cwd,
916
1128
  allowUntestedStellarCli: options.allowUntestedStellarCli,
917
1129
  force: options.force,
1130
+ checkStaleWasm: options.checkStaleWasm,
918
1131
  resolvedDeployArgs,
919
1132
  dependencies: contractConfig.dependsOn
920
1133
  });
921
- deployedContracts.push({ name: contractName, contractId: result.contractId });
1134
+ if (result.staleWasmWarning) {
1135
+ staleWasmWarnings.push({
1136
+ contract: contractName,
1137
+ message: result.staleWasmWarning
1138
+ });
1139
+ }
1140
+ if (result.skipped) {
1141
+ skippedContracts.push({
1142
+ name: contractName,
1143
+ contractId: result.contractId,
1144
+ network: network.name,
1145
+ reason: "already-deployed"
1146
+ });
1147
+ } else {
1148
+ deployedContracts.push({ name: contractName, contractId: result.contractId });
1149
+ }
922
1150
  }
923
1151
  return {
924
1152
  network,
925
- deployedContracts
1153
+ deployedContracts,
1154
+ skippedContracts,
1155
+ staleWasmWarnings
926
1156
  };
927
1157
  }
928
1158
 
929
1159
  // src/contracts/generate-bindings.ts
930
1160
  var import_promises5 = require("fs/promises");
931
- var import_node_path6 = __toESM(require("path"), 1);
1161
+ var import_node_path7 = __toESM(require("path"), 1);
932
1162
  async function generateBindings(options) {
933
1163
  const cwd = options.cwd ?? process.cwd();
934
1164
  const network = resolveNetwork(options.config, options.networkName);
@@ -944,7 +1174,7 @@ async function generateBindings(options) {
944
1174
  await checkBinary("stellar", "Install Stellar CLI before running caatinga generate.", {
945
1175
  allowUntestedStellarCli: options.allowUntestedStellarCli
946
1176
  });
947
- const outputDir = import_node_path6.default.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
1177
+ const outputDir = import_node_path7.default.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
948
1178
  await (0, import_promises5.mkdir)(outputDir, { recursive: true });
949
1179
  const result = await runCommand("stellar", [
950
1180
  "contract",
@@ -955,10 +1185,7 @@ async function generateBindings(options) {
955
1185
  "--output-dir",
956
1186
  outputDir,
957
1187
  "--overwrite",
958
- "--rpc-url",
959
- network.config.rpcUrl,
960
- "--network-passphrase",
961
- network.config.networkPassphrase
1188
+ ...buildStellarNetworkArgs(network)
962
1189
  ], {
963
1190
  cwd,
964
1191
  allowUntestedStellarCli: options.allowUntestedStellarCli,
@@ -973,6 +1200,7 @@ async function generateBindings(options) {
973
1200
  }
974
1201
 
975
1202
  // src/contracts/invoke-contract.ts
1203
+ var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
976
1204
  function parseInvokeTarget(target) {
977
1205
  const [contractName, method, extra] = target.split(".");
978
1206
  if (!contractName || !method || extra) {
@@ -1001,25 +1229,43 @@ async function invokeContract(options) {
1001
1229
  await checkBinary("stellar", "Install Stellar CLI before running caatinga invoke.", {
1002
1230
  allowUntestedStellarCli: options.allowUntestedStellarCli
1003
1231
  });
1004
- const result = await runCommand("stellar", [
1005
- "contract",
1006
- "invoke",
1007
- "--id",
1008
- contractArtifact.contractId,
1009
- "--source-account",
1010
- source,
1011
- "--rpc-url",
1012
- network.config.rpcUrl,
1013
- "--network-passphrase",
1014
- network.config.networkPassphrase,
1015
- "--",
1016
- target.method,
1017
- ...options.args ?? []
1018
- ], {
1019
- cwd,
1020
- allowUntestedStellarCli: options.allowUntestedStellarCli,
1021
- failureCode: CaatingaErrorCode.INVOKE_FAILED
1022
- });
1232
+ let result;
1233
+ try {
1234
+ result = await runCommand("stellar", [
1235
+ "contract",
1236
+ "invoke",
1237
+ "--id",
1238
+ contractArtifact.contractId,
1239
+ "--source-account",
1240
+ source,
1241
+ ...buildStellarNetworkArgs(network),
1242
+ "--",
1243
+ target.method,
1244
+ ...options.args ?? []
1245
+ ], {
1246
+ cwd,
1247
+ allowUntestedStellarCli: options.allowUntestedStellarCli,
1248
+ failureCode: CaatingaErrorCode.INVOKE_FAILED
1249
+ });
1250
+ } catch (error) {
1251
+ if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && INVOKE_SIGNING_FAILURE_REGEX.test(`${error.message}
1252
+ ${error.hint ?? ""}`)) {
1253
+ throw new CaatingaError(
1254
+ error.message,
1255
+ error.code,
1256
+ [
1257
+ "Stellar CLI could not sign the invoke transaction (xdr value invalid).",
1258
+ "Stellar CLI 22.x has a known invoke signing bug; upgrade to 23.0.0 or newer (25.2.0 recommended).",
1259
+ " stellar --version",
1260
+ "Then retry with a funded identity, for example:",
1261
+ " stellar keys generate alice --fund --network testnet",
1262
+ " npx caatinga invoke counter.increment --network testnet --source alice"
1263
+ ].join("\n"),
1264
+ error
1265
+ );
1266
+ }
1267
+ throw error;
1268
+ }
1023
1269
  return {
1024
1270
  target,
1025
1271
  network,
@@ -1029,7 +1275,7 @@ async function invokeContract(options) {
1029
1275
 
1030
1276
  // src/templates/create-project-from-template.ts
1031
1277
  var import_promises6 = require("fs/promises");
1032
- var import_node_path7 = __toESM(require("path"), 1);
1278
+ var import_node_path8 = __toESM(require("path"), 1);
1033
1279
  var import_zod6 = require("zod");
1034
1280
 
1035
1281
  // src/templates/template-manifest.schema.ts
@@ -1099,8 +1345,8 @@ function formatTemplateCompatibilityHint(issue) {
1099
1345
 
1100
1346
  // src/templates/create-project-from-template.ts
1101
1347
  async function createProjectFromTemplate(options) {
1102
- const targetDir = import_node_path7.default.resolve(options.targetDir);
1103
- const templateDir = import_node_path7.default.resolve(options.templateDir);
1348
+ const targetDir = import_node_path8.default.resolve(options.targetDir);
1349
+ const templateDir = import_node_path8.default.resolve(options.templateDir);
1104
1350
  try {
1105
1351
  await (0, import_promises6.stat)(templateDir);
1106
1352
  } catch {
@@ -1122,7 +1368,7 @@ async function createProjectFromTemplate(options) {
1122
1368
  return { targetDir, template: manifest };
1123
1369
  }
1124
1370
  async function readTemplateManifest(templateDir) {
1125
- const manifestPath = import_node_path7.default.join(templateDir, "caatinga.template.json");
1371
+ const manifestPath = import_node_path8.default.join(templateDir, "caatinga.template.json");
1126
1372
  try {
1127
1373
  const rawManifest = await (0, import_promises6.readFile)(manifestPath, "utf8");
1128
1374
  const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
@@ -1159,7 +1405,7 @@ async function readTemplateManifest(templateDir) {
1159
1405
  async function replaceTemplateVariables(dir, projectName) {
1160
1406
  const entries = await (0, import_promises6.readdir)(dir);
1161
1407
  await Promise.all(entries.map(async (entry) => {
1162
- const entryPath = import_node_path7.default.join(dir, entry);
1408
+ const entryPath = import_node_path8.default.join(dir, entry);
1163
1409
  const entryStat = await (0, import_promises6.stat)(entryPath);
1164
1410
  if (entryStat.isDirectory()) {
1165
1411
  await replaceTemplateVariables(entryPath, projectName);
@@ -1182,7 +1428,7 @@ function isTextTemplateFile(filePath) {
1182
1428
  ".tsx",
1183
1429
  ".css",
1184
1430
  ".html"
1185
- ].includes(import_node_path7.default.extname(filePath));
1431
+ ].includes(import_node_path8.default.extname(filePath));
1186
1432
  }
1187
1433
 
1188
1434
  // src/ci/is-transient-testnet-smoke-failure.ts
@@ -1241,5 +1487,6 @@ function isTransientTestnetSmokeFailure(logText) {
1241
1487
  runCommand,
1242
1488
  toCaatingaError,
1243
1489
  updateArtifact,
1490
+ validateSourceShape,
1244
1491
  writeArtifacts
1245
1492
  });