@caatinga/core 0.2.0 → 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/LICENSE +1 -1
- package/README.md +89 -15
- package/dist/browser-DUcSR5D3.d.cts +173 -0
- package/dist/browser-DUcSR5D3.d.ts +173 -0
- package/dist/browser.cjs +106 -0
- package/dist/browser.d.cts +2 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +10 -0
- package/dist/chunk-GMABXVEY.js +78 -0
- package/dist/index.cjs +472 -85
- package/dist/index.d.cts +50 -177
- package/dist/index.d.ts +50 -177
- package/dist/index.js +474 -155
- package/package.json +7 -2
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",
|
|
@@ -102,13 +104,18 @@ var CaatingaErrorCode = {
|
|
|
102
104
|
XDR_SUBMIT_FAILED: "CAATINGA_XDR_SUBMIT_FAILED",
|
|
103
105
|
XDR_RESULT_FAILED: "CAATINGA_XDR_RESULT_FAILED",
|
|
104
106
|
WALLET_NOT_CONNECTED: "CAATINGA_WALLET_NOT_CONNECTED",
|
|
107
|
+
WALLET_TIMEOUT: "CAATINGA_WALLET_TIMEOUT",
|
|
105
108
|
SOURCE_ACCOUNT_REQUIRED: "CAATINGA_SOURCE_ACCOUNT_REQUIRED",
|
|
109
|
+
SOURCE_IS_SECRET_KEY: "CAATINGA_SOURCE_IS_SECRET_KEY",
|
|
110
|
+
SOURCE_IS_SEED_PHRASE: "CAATINGA_SOURCE_IS_SEED_PHRASE",
|
|
111
|
+
SOURCE_IS_PUBLIC_KEY: "CAATINGA_SOURCE_IS_PUBLIC_KEY",
|
|
106
112
|
UNSAFE_SOURCE_ACCOUNT: "CAATINGA_UNSAFE_SOURCE_ACCOUNT",
|
|
107
113
|
INVOKE_TARGET_INVALID: "CAATINGA_INVOKE_TARGET_INVALID",
|
|
108
114
|
TEMPLATE_NOT_FOUND: "CAATINGA_TEMPLATE_NOT_FOUND",
|
|
109
115
|
INVALID_TEMPLATE_MANIFEST: "CAATINGA_INVALID_TEMPLATE_MANIFEST",
|
|
110
116
|
TEMPLATE_MANIFEST_NOT_FOUND: "CAATINGA_TEMPLATE_MANIFEST_NOT_FOUND",
|
|
111
|
-
TEMPLATE_INCOMPATIBLE: "CAATINGA_TEMPLATE_INCOMPATIBLE"
|
|
117
|
+
TEMPLATE_INCOMPATIBLE: "CAATINGA_TEMPLATE_INCOMPATIBLE",
|
|
118
|
+
DOCTOR_PARTIAL_DEPLOY: "CAATINGA_DOCTOR_PARTIAL_DEPLOY"
|
|
112
119
|
};
|
|
113
120
|
|
|
114
121
|
// src/errors/CaatingaError.ts
|
|
@@ -135,7 +142,7 @@ function toCaatingaError(error) {
|
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
// src/version.ts
|
|
138
|
-
var CAATINGA_CORE_VERSION = "0.2.
|
|
145
|
+
var CAATINGA_CORE_VERSION = "0.2.2";
|
|
139
146
|
|
|
140
147
|
// src/config/config.schema.ts
|
|
141
148
|
var import_zod = require("zod");
|
|
@@ -323,7 +330,7 @@ var import_execa = require("execa");
|
|
|
323
330
|
|
|
324
331
|
// src/stellar-cli/version.ts
|
|
325
332
|
var import_semver = __toESM(require("semver"), 1);
|
|
326
|
-
var STELLAR_CLI_MIN_VERSION = "
|
|
333
|
+
var STELLAR_CLI_MIN_VERSION = "23.0.0";
|
|
327
334
|
var STELLAR_CLI_TESTED_MAX_VERSION = "25.2.0";
|
|
328
335
|
var STELLAR_CLI_SEMVER_REGEX = /\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?)\b/;
|
|
329
336
|
function parseStellarCliVersion(output) {
|
|
@@ -456,6 +463,146 @@ function parseContractId(output) {
|
|
|
456
463
|
return match[0];
|
|
457
464
|
}
|
|
458
465
|
|
|
466
|
+
// src/stellar-cli/build-stellar-network-args.ts
|
|
467
|
+
function matchesWellKnownNetwork(name, config) {
|
|
468
|
+
const known = WELL_KNOWN_NETWORKS[name];
|
|
469
|
+
if (!known) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
return known.rpcUrl === config.rpcUrl && known.networkPassphrase === config.networkPassphrase;
|
|
473
|
+
}
|
|
474
|
+
function buildRpcNetworkArgs(config) {
|
|
475
|
+
return [
|
|
476
|
+
"--rpc-url",
|
|
477
|
+
config.rpcUrl,
|
|
478
|
+
"--network-passphrase",
|
|
479
|
+
config.networkPassphrase
|
|
480
|
+
];
|
|
481
|
+
}
|
|
482
|
+
function buildStellarNetworkArgsFromConfig(config) {
|
|
483
|
+
for (const [name, known] of Object.entries(WELL_KNOWN_NETWORKS)) {
|
|
484
|
+
if (known.rpcUrl === config.rpcUrl && known.networkPassphrase === config.networkPassphrase) {
|
|
485
|
+
return ["--network", name];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return buildRpcNetworkArgs(config);
|
|
489
|
+
}
|
|
490
|
+
function buildStellarNetworkArgs(network) {
|
|
491
|
+
if (matchesWellKnownNetwork(network.name, network.config)) {
|
|
492
|
+
return ["--network", network.name];
|
|
493
|
+
}
|
|
494
|
+
return buildStellarNetworkArgsFromConfig(network.config);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/stellar-cli/recover-deploy-contract-id.ts
|
|
498
|
+
var TX_HASH_REGEX = /Transaction hash is ([a-f0-9]{64})/i;
|
|
499
|
+
var DEPLOY_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
|
|
500
|
+
var HORIZON_URL_BY_PASSPHRASE = {
|
|
501
|
+
"Test SDF Network ; September 2015": "https://horizon-testnet.stellar.org",
|
|
502
|
+
"Public Global Stellar Network ; September 2015": "https://horizon.stellar.org"
|
|
503
|
+
};
|
|
504
|
+
function isLikelyPublicKeySource(source) {
|
|
505
|
+
return /^G[A-Z2-7]{55}$/.test(source);
|
|
506
|
+
}
|
|
507
|
+
function decimalSaltToHex(salt) {
|
|
508
|
+
return BigInt(salt).toString(16).padStart(64, "0");
|
|
509
|
+
}
|
|
510
|
+
function resolveHorizonUrl(network) {
|
|
511
|
+
const horizonUrl = HORIZON_URL_BY_PASSPHRASE[network.networkPassphrase];
|
|
512
|
+
if (!horizonUrl) {
|
|
513
|
+
throw new CaatingaError(
|
|
514
|
+
`No Horizon URL mapping for network passphrase "${network.networkPassphrase}".`,
|
|
515
|
+
CaatingaErrorCode.NETWORK_NOT_FOUND,
|
|
516
|
+
"Use testnet or mainnet, or extend Caatinga network metadata."
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
return horizonUrl;
|
|
520
|
+
}
|
|
521
|
+
async function fetchCreateContractSalt(horizonUrl, transactionHash, fetchImpl = fetch) {
|
|
522
|
+
const response = await fetchImpl(`${horizonUrl}/transactions/${transactionHash}/operations`);
|
|
523
|
+
if (!response.ok) {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
const body = await response.json();
|
|
527
|
+
const operation = body._embedded?.records?.find(
|
|
528
|
+
(record) => record.transaction_successful === true && record.type === "invoke_host_function" && record.function === "HostFunctionTypeHostFunctionTypeCreateContract" && typeof record.salt === "string"
|
|
529
|
+
);
|
|
530
|
+
return operation?.salt ?? null;
|
|
531
|
+
}
|
|
532
|
+
async function resolveContractIdFromDeploySalt(options) {
|
|
533
|
+
const saltHex = decimalSaltToHex(options.salt);
|
|
534
|
+
const result = await runCommand("stellar", [
|
|
535
|
+
"contract",
|
|
536
|
+
"id",
|
|
537
|
+
"wasm",
|
|
538
|
+
"--salt",
|
|
539
|
+
saltHex,
|
|
540
|
+
"--source-account",
|
|
541
|
+
options.source,
|
|
542
|
+
...buildStellarNetworkArgsFromConfig(options.network)
|
|
543
|
+
], {
|
|
544
|
+
cwd: options.cwd,
|
|
545
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli,
|
|
546
|
+
skipStellarVersionCheck: true
|
|
547
|
+
});
|
|
548
|
+
return parseContractId(result.all || `${result.stdout}
|
|
549
|
+
${result.stderr}`);
|
|
550
|
+
}
|
|
551
|
+
async function tryRecoverContractIdFromDeployFailure(options) {
|
|
552
|
+
if (!DEPLOY_SIGNING_FAILURE_REGEX.test(options.output)) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
const hashMatch = options.output.match(TX_HASH_REGEX);
|
|
556
|
+
if (!hashMatch) {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
const horizonUrl = resolveHorizonUrl(options.network);
|
|
560
|
+
const salt = await fetchCreateContractSalt(horizonUrl, hashMatch[1], options.fetchImpl);
|
|
561
|
+
if (!salt) {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
return resolveContractIdFromDeploySalt({
|
|
565
|
+
salt,
|
|
566
|
+
source: options.source,
|
|
567
|
+
network: options.network,
|
|
568
|
+
cwd: options.cwd,
|
|
569
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/contracts/validate-source-shape.ts
|
|
574
|
+
function validateSourceShape(source) {
|
|
575
|
+
if (source.startsWith("S")) {
|
|
576
|
+
return new CaatingaError(
|
|
577
|
+
"Refusing to accept a Stellar secret key as --source.",
|
|
578
|
+
CaatingaErrorCode.SOURCE_IS_SECRET_KEY,
|
|
579
|
+
"Use a Stellar CLI identity alias instead, for example: --source alice"
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
if (source.trim().includes(" ")) {
|
|
583
|
+
return new CaatingaError(
|
|
584
|
+
"Refusing to accept a seed phrase as --source.",
|
|
585
|
+
CaatingaErrorCode.SOURCE_IS_SEED_PHRASE,
|
|
586
|
+
"Use a Stellar CLI identity alias instead, for example: --source alice"
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
if (isLikelyPublicKeySource(source)) {
|
|
590
|
+
return new CaatingaError(
|
|
591
|
+
`Public account address cannot sign transactions: ${source}`,
|
|
592
|
+
CaatingaErrorCode.SOURCE_IS_PUBLIC_KEY,
|
|
593
|
+
"Use a Stellar CLI identity with a secret key. Example: stellar keys generate alice --fund --network testnet, then --source alice"
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
if (source.startsWith("G")) {
|
|
597
|
+
return new CaatingaError(
|
|
598
|
+
"Refusing to accept a public account address as --source.",
|
|
599
|
+
CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
|
|
600
|
+
"Use a Stellar CLI identity alias, not a public address. Example: --source alice"
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
return void 0;
|
|
604
|
+
}
|
|
605
|
+
|
|
459
606
|
// src/contracts/resolve-contract.ts
|
|
460
607
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
461
608
|
function resolveContract(config, contractName, cwd = process.cwd()) {
|
|
@@ -478,24 +625,98 @@ function resolveContract(config, contractName, cwd = process.cwd()) {
|
|
|
478
625
|
// src/contracts/wasm.ts
|
|
479
626
|
var import_node_crypto = require("crypto");
|
|
480
627
|
var import_promises4 = require("fs/promises");
|
|
481
|
-
|
|
628
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
629
|
+
var LEGACY_RUST_WASM_TARGET = "wasm32-unknown-unknown";
|
|
630
|
+
var CURRENT_RUST_WASM_TARGET = "wasm32v1-none";
|
|
631
|
+
function toCurrentWasmTargetPath(wasmPath) {
|
|
632
|
+
if (!wasmPath.includes(LEGACY_RUST_WASM_TARGET)) {
|
|
633
|
+
return wasmPath;
|
|
634
|
+
}
|
|
635
|
+
return wasmPath.replaceAll(LEGACY_RUST_WASM_TARGET, CURRENT_RUST_WASM_TARGET);
|
|
636
|
+
}
|
|
637
|
+
function wasmNotFoundError(configuredWasmPath, options) {
|
|
638
|
+
const migratedPath = options?.migratedPath;
|
|
639
|
+
const hint = migratedPath === void 0 ? "Run caatinga build before deploy or generate." : [
|
|
640
|
+
"Run caatinga build before deploy or generate.",
|
|
641
|
+
`Soroban builds use the "${CURRENT_RUST_WASM_TARGET}" target.`,
|
|
642
|
+
`Update wasm in caatinga.config.ts to "${toConfigRelativeWasmPath(migratedPath)}" or an equivalent path under target/${CURRENT_RUST_WASM_TARGET}/release/.`
|
|
643
|
+
].join(" ");
|
|
644
|
+
return new CaatingaError(
|
|
645
|
+
`WASM output was not found at ${configuredWasmPath}.`,
|
|
646
|
+
CaatingaErrorCode.ARTIFACT_NOT_FOUND,
|
|
647
|
+
hint
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
function toConfigRelativeWasmPath(absoluteWasmPath) {
|
|
651
|
+
const relative = import_node_path5.default.relative(process.cwd(), absoluteWasmPath);
|
|
652
|
+
return relative.startsWith("..") ? absoluteWasmPath : `./${relative.split(import_node_path5.default.sep).join("/")}`;
|
|
653
|
+
}
|
|
654
|
+
async function resolveWasmArtifactPath(configuredWasmPath) {
|
|
482
655
|
try {
|
|
483
|
-
await (0, import_promises4.access)(
|
|
656
|
+
await (0, import_promises4.access)(configuredWasmPath);
|
|
657
|
+
return configuredWasmPath;
|
|
484
658
|
} catch {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
659
|
+
const currentTargetPath = toCurrentWasmTargetPath(configuredWasmPath);
|
|
660
|
+
if (currentTargetPath === configuredWasmPath) {
|
|
661
|
+
throw wasmNotFoundError(configuredWasmPath);
|
|
662
|
+
}
|
|
663
|
+
try {
|
|
664
|
+
await (0, import_promises4.access)(currentTargetPath);
|
|
665
|
+
return currentTargetPath;
|
|
666
|
+
} catch {
|
|
667
|
+
throw wasmNotFoundError(configuredWasmPath, { migratedPath: currentTargetPath });
|
|
668
|
+
}
|
|
490
669
|
}
|
|
491
670
|
}
|
|
492
671
|
async function hashWasm(wasmPath) {
|
|
493
672
|
const bytes = await (0, import_promises4.readFile)(wasmPath);
|
|
494
673
|
return (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
|
|
495
674
|
}
|
|
675
|
+
async function getNewestMtimeInDirectory(directory) {
|
|
676
|
+
try {
|
|
677
|
+
await (0, import_promises4.access)(directory);
|
|
678
|
+
} catch {
|
|
679
|
+
return void 0;
|
|
680
|
+
}
|
|
681
|
+
let newest = 0;
|
|
682
|
+
async function walk(dir) {
|
|
683
|
+
const entries = await (0, import_promises4.readdir)(dir, { withFileTypes: true });
|
|
684
|
+
for (const entry of entries) {
|
|
685
|
+
const entryPath = import_node_path5.default.join(dir, entry.name);
|
|
686
|
+
if (entry.isDirectory()) {
|
|
687
|
+
await walk(entryPath);
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (!entry.isFile()) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const fileStat = await (0, import_promises4.stat)(entryPath);
|
|
694
|
+
newest = Math.max(newest, fileStat.mtimeMs);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
await walk(directory);
|
|
698
|
+
return newest > 0 ? newest : void 0;
|
|
699
|
+
}
|
|
700
|
+
async function isWasmOlderThanSources(input) {
|
|
701
|
+
const srcDir = import_node_path5.default.join(input.contractPath, "src");
|
|
702
|
+
const newestSourceMtime = await getNewestMtimeInDirectory(srcDir);
|
|
703
|
+
if (newestSourceMtime === void 0) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
let wasmStat;
|
|
707
|
+
try {
|
|
708
|
+
wasmStat = await (0, import_promises4.stat)(input.wasmPath);
|
|
709
|
+
} catch {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
return wasmStat.mtimeMs < newestSourceMtime;
|
|
713
|
+
}
|
|
714
|
+
function formatStaleWasmWarning(contractName) {
|
|
715
|
+
return `WASM for "${contractName}" may be stale: contract sources under src/ are newer than the WASM file. Run \`caatinga build\` before deploy.`;
|
|
716
|
+
}
|
|
496
717
|
|
|
497
718
|
// src/contracts/build-contract.ts
|
|
498
|
-
var RUST_WASM_TARGET = "
|
|
719
|
+
var RUST_WASM_TARGET = "wasm32v1-none";
|
|
499
720
|
var MISSING_WASM_TARGET_HINT_SUBSTRINGS = [
|
|
500
721
|
"not installed",
|
|
501
722
|
"not found",
|
|
@@ -537,21 +758,24 @@ async function buildContract(options) {
|
|
|
537
758
|
throw new CaatingaError(
|
|
538
759
|
`Required Rust wasm target "${RUST_WASM_TARGET}" is missing.`,
|
|
539
760
|
CaatingaErrorCode.RUST_TARGET_NOT_FOUND,
|
|
540
|
-
`Run \`rustup target add ${RUST_WASM_TARGET}\` and retry
|
|
761
|
+
`Run \`rustup target add ${RUST_WASM_TARGET}\` and retry.`,
|
|
541
762
|
error
|
|
542
763
|
);
|
|
543
764
|
}
|
|
544
765
|
throw error;
|
|
545
766
|
}
|
|
546
|
-
await
|
|
767
|
+
const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
|
|
547
768
|
return {
|
|
548
|
-
contract
|
|
769
|
+
contract: {
|
|
770
|
+
...contract,
|
|
771
|
+
wasmPath
|
|
772
|
+
},
|
|
549
773
|
output: result.all || result.stdout
|
|
550
774
|
};
|
|
551
775
|
}
|
|
552
776
|
|
|
553
777
|
// src/contracts/deploy-contract.ts
|
|
554
|
-
var
|
|
778
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
555
779
|
|
|
556
780
|
// src/contracts/dependency-graph.ts
|
|
557
781
|
function buildDependencyGraph(contracts) {
|
|
@@ -599,15 +823,12 @@ function assertSafeSourceAccount(source) {
|
|
|
599
823
|
throw new CaatingaError(
|
|
600
824
|
"A source account or Stellar CLI identity is required.",
|
|
601
825
|
CaatingaErrorCode.SOURCE_ACCOUNT_REQUIRED,
|
|
602
|
-
"Pass
|
|
826
|
+
"Pass a Stellar CLI identity alias, for example: --source alice"
|
|
603
827
|
);
|
|
604
828
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
|
|
609
|
-
"Use a Stellar CLI identity alias or public account address instead."
|
|
610
|
-
);
|
|
829
|
+
const unsafeSource = validateSourceShape(source);
|
|
830
|
+
if (unsafeSource) {
|
|
831
|
+
throw unsafeSource;
|
|
611
832
|
}
|
|
612
833
|
return source;
|
|
613
834
|
}
|
|
@@ -635,17 +856,32 @@ async function deployContract(options) {
|
|
|
635
856
|
await checkBinary("stellar", "Install Stellar CLI before running caatinga deploy.", {
|
|
636
857
|
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
637
858
|
});
|
|
638
|
-
await
|
|
859
|
+
const wasmPath = await resolveWasmArtifactPath(contract.wasmPath);
|
|
860
|
+
const contractWithWasm = {
|
|
861
|
+
...contract,
|
|
862
|
+
wasmPath
|
|
863
|
+
};
|
|
864
|
+
let staleWasmWarning;
|
|
865
|
+
if (options.checkStaleWasm !== false) {
|
|
866
|
+
const stale = await isWasmOlderThanSources({
|
|
867
|
+
wasmPath,
|
|
868
|
+
contractPath: contract.sourcePath
|
|
869
|
+
});
|
|
870
|
+
if (stale) {
|
|
871
|
+
staleWasmWarning = formatStaleWasmWarning(contract.name);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
639
874
|
const artifactsBefore = await readArtifacts(cwd);
|
|
640
875
|
const existing = artifactsBefore.networks[network.name]?.contracts[contract.name];
|
|
641
876
|
if (existing?.contractId && !options.force) {
|
|
642
877
|
return {
|
|
643
|
-
contract,
|
|
878
|
+
contract: contractWithWasm,
|
|
644
879
|
network,
|
|
645
880
|
contractId: existing.contractId,
|
|
646
|
-
artifactsPath:
|
|
881
|
+
artifactsPath: import_node_path6.default.resolve(cwd, "caatinga.artifacts.json"),
|
|
647
882
|
output: "",
|
|
648
|
-
skipped: true
|
|
883
|
+
skipped: true,
|
|
884
|
+
staleWasmWarning
|
|
649
885
|
};
|
|
650
886
|
}
|
|
651
887
|
const rawDeployArgs = contract.config.deployArgs;
|
|
@@ -676,24 +912,46 @@ async function deployContract(options) {
|
|
|
676
912
|
"contract",
|
|
677
913
|
"deploy",
|
|
678
914
|
"--wasm",
|
|
679
|
-
|
|
915
|
+
wasmPath,
|
|
680
916
|
"--source-account",
|
|
681
917
|
source,
|
|
682
|
-
|
|
683
|
-
network.config.rpcUrl,
|
|
684
|
-
"--network-passphrase",
|
|
685
|
-
network.config.networkPassphrase,
|
|
918
|
+
...buildStellarNetworkArgs(network),
|
|
686
919
|
...constructorArgs
|
|
687
920
|
];
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
921
|
+
let output = "";
|
|
922
|
+
let contractId;
|
|
923
|
+
try {
|
|
924
|
+
const result = await runCommand("stellar", stellarArgs, {
|
|
925
|
+
cwd,
|
|
926
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli,
|
|
927
|
+
failureCode: CaatingaErrorCode.DEPLOY_FAILED
|
|
928
|
+
});
|
|
929
|
+
output = result.all || `${result.stdout}
|
|
694
930
|
${result.stderr}`;
|
|
695
|
-
|
|
696
|
-
|
|
931
|
+
contractId = parseContractId(output);
|
|
932
|
+
} catch (error) {
|
|
933
|
+
if (!(error instanceof CaatingaError) || error.code !== CaatingaErrorCode.DEPLOY_FAILED) {
|
|
934
|
+
throw error;
|
|
935
|
+
}
|
|
936
|
+
const recoveredContractId = await tryRecoverContractIdFromDeployFailure({
|
|
937
|
+
output: `${error.message}
|
|
938
|
+
${error.hint ?? ""}`,
|
|
939
|
+
source,
|
|
940
|
+
network: network.config,
|
|
941
|
+
cwd,
|
|
942
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
943
|
+
});
|
|
944
|
+
if (!recoveredContractId) {
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
contractId = recoveredContractId;
|
|
948
|
+
output = [
|
|
949
|
+
error.hint ?? "",
|
|
950
|
+
"Caatinga recovered the contract ID from the on-chain deploy transaction.",
|
|
951
|
+
`Contract ID: ${contractId}`
|
|
952
|
+
].filter(Boolean).join("\n");
|
|
953
|
+
}
|
|
954
|
+
const wasmHash = await hashWasm(wasmPath);
|
|
697
955
|
const dependencyGraph = buildDependencyGraph(options.config.contracts);
|
|
698
956
|
const dependencies = options.dependencies ?? contract.config.dependsOn;
|
|
699
957
|
const nextArtifacts = updateArtifact(
|
|
@@ -713,12 +971,13 @@ ${result.stderr}`;
|
|
|
713
971
|
);
|
|
714
972
|
const artifactsPath = await writeArtifacts(nextArtifacts, cwd);
|
|
715
973
|
return {
|
|
716
|
-
contract,
|
|
974
|
+
contract: contractWithWasm,
|
|
717
975
|
network,
|
|
718
976
|
contractId,
|
|
719
977
|
artifactsPath,
|
|
720
978
|
output,
|
|
721
|
-
skipped: false
|
|
979
|
+
skipped: false,
|
|
980
|
+
staleWasmWarning
|
|
722
981
|
};
|
|
723
982
|
}
|
|
724
983
|
|
|
@@ -773,6 +1032,53 @@ function resolveDeployOrder(input) {
|
|
|
773
1032
|
}
|
|
774
1033
|
}
|
|
775
1034
|
|
|
1035
|
+
// src/contracts/verify-dependency-contract.ts
|
|
1036
|
+
async function verifyDependencyContract(options) {
|
|
1037
|
+
try {
|
|
1038
|
+
await runCommand("stellar", [
|
|
1039
|
+
"contract",
|
|
1040
|
+
"info",
|
|
1041
|
+
"interface",
|
|
1042
|
+
"--contract-id",
|
|
1043
|
+
options.contractId,
|
|
1044
|
+
...buildStellarNetworkArgs(options.network)
|
|
1045
|
+
], {
|
|
1046
|
+
cwd: options.cwd,
|
|
1047
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli,
|
|
1048
|
+
failureCode: CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND
|
|
1049
|
+
});
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
if (error instanceof CaatingaError && error.code === CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND) {
|
|
1052
|
+
throw new CaatingaError(
|
|
1053
|
+
`Dependency "${options.dependencyName}" is not deployed on "${options.network.name}" (contract ID ${options.contractId}).`,
|
|
1054
|
+
CaatingaErrorCode.DEPENDENCY_CONTRACT_NOT_FOUND,
|
|
1055
|
+
"Deploy the dependency on this network, fix caatinga.artifacts.json, or omit --verify-deps.",
|
|
1056
|
+
error.cause
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async function verifyDependencyContracts(options) {
|
|
1063
|
+
for (const dependencyName of options.dependencies) {
|
|
1064
|
+
const contractArtifact = options.artifacts.networks[options.network.name]?.contracts[dependencyName];
|
|
1065
|
+
if (!contractArtifact?.contractId) {
|
|
1066
|
+
throw new CaatingaError(
|
|
1067
|
+
`No dependency artifact found for "${dependencyName}" on "${options.network.name}".`,
|
|
1068
|
+
CaatingaErrorCode.CONTRACT_DEPENDENCY_ARTIFACT_NOT_FOUND,
|
|
1069
|
+
"Deploy the dependency first or run deploy without --no-deps."
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
await verifyDependencyContract({
|
|
1073
|
+
dependencyName,
|
|
1074
|
+
contractId: contractArtifact.contractId,
|
|
1075
|
+
network: options.network,
|
|
1076
|
+
cwd: options.cwd,
|
|
1077
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
776
1082
|
// src/contracts/deploy-contract-graph.ts
|
|
777
1083
|
async function deployContractGraph(options) {
|
|
778
1084
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -783,17 +1089,33 @@ async function deployContractGraph(options) {
|
|
|
783
1089
|
includeDependencies: options.includeDependencies
|
|
784
1090
|
});
|
|
785
1091
|
const deployedContracts = [];
|
|
1092
|
+
const skippedContracts = [];
|
|
1093
|
+
const staleWasmWarnings = [];
|
|
786
1094
|
for (const contractName of order) {
|
|
787
1095
|
const artifacts = await readArtifacts(cwd);
|
|
788
1096
|
const existing = artifacts.networks[network.name]?.contracts[contractName];
|
|
789
1097
|
const contractConfig = options.config.contracts[contractName];
|
|
1098
|
+
if (options.verifyDeps && contractConfig.dependsOn.length > 0) {
|
|
1099
|
+
await verifyDependencyContracts({
|
|
1100
|
+
dependencies: contractConfig.dependsOn,
|
|
1101
|
+
artifacts,
|
|
1102
|
+
network,
|
|
1103
|
+
cwd,
|
|
1104
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
790
1107
|
const resolvedDeployArgs = resolveDeployArgs({
|
|
791
1108
|
deployArgs: contractConfig.deployArgs,
|
|
792
1109
|
artifacts,
|
|
793
1110
|
network: network.name
|
|
794
1111
|
});
|
|
795
1112
|
if (existing?.contractId && !options.force) {
|
|
796
|
-
|
|
1113
|
+
skippedContracts.push({
|
|
1114
|
+
name: contractName,
|
|
1115
|
+
contractId: existing.contractId,
|
|
1116
|
+
network: network.name,
|
|
1117
|
+
reason: "already-deployed"
|
|
1118
|
+
});
|
|
797
1119
|
continue;
|
|
798
1120
|
}
|
|
799
1121
|
const result = await deployContract({
|
|
@@ -804,20 +1126,38 @@ async function deployContractGraph(options) {
|
|
|
804
1126
|
cwd,
|
|
805
1127
|
allowUntestedStellarCli: options.allowUntestedStellarCli,
|
|
806
1128
|
force: options.force,
|
|
1129
|
+
checkStaleWasm: options.checkStaleWasm,
|
|
807
1130
|
resolvedDeployArgs,
|
|
808
1131
|
dependencies: contractConfig.dependsOn
|
|
809
1132
|
});
|
|
810
|
-
|
|
1133
|
+
if (result.staleWasmWarning) {
|
|
1134
|
+
staleWasmWarnings.push({
|
|
1135
|
+
contract: contractName,
|
|
1136
|
+
message: result.staleWasmWarning
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
if (result.skipped) {
|
|
1140
|
+
skippedContracts.push({
|
|
1141
|
+
name: contractName,
|
|
1142
|
+
contractId: result.contractId,
|
|
1143
|
+
network: network.name,
|
|
1144
|
+
reason: "already-deployed"
|
|
1145
|
+
});
|
|
1146
|
+
} else {
|
|
1147
|
+
deployedContracts.push({ name: contractName, contractId: result.contractId });
|
|
1148
|
+
}
|
|
811
1149
|
}
|
|
812
1150
|
return {
|
|
813
1151
|
network,
|
|
814
|
-
deployedContracts
|
|
1152
|
+
deployedContracts,
|
|
1153
|
+
skippedContracts,
|
|
1154
|
+
staleWasmWarnings
|
|
815
1155
|
};
|
|
816
1156
|
}
|
|
817
1157
|
|
|
818
1158
|
// src/contracts/generate-bindings.ts
|
|
819
1159
|
var import_promises5 = require("fs/promises");
|
|
820
|
-
var
|
|
1160
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
821
1161
|
async function generateBindings(options) {
|
|
822
1162
|
const cwd = options.cwd ?? process.cwd();
|
|
823
1163
|
const network = resolveNetwork(options.config, options.networkName);
|
|
@@ -833,7 +1173,7 @@ async function generateBindings(options) {
|
|
|
833
1173
|
await checkBinary("stellar", "Install Stellar CLI before running caatinga generate.", {
|
|
834
1174
|
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
835
1175
|
});
|
|
836
|
-
const outputDir =
|
|
1176
|
+
const outputDir = import_node_path7.default.resolve(cwd, options.config.frontend.bindingsOutput, options.contractName);
|
|
837
1177
|
await (0, import_promises5.mkdir)(outputDir, { recursive: true });
|
|
838
1178
|
const result = await runCommand("stellar", [
|
|
839
1179
|
"contract",
|
|
@@ -844,10 +1184,7 @@ async function generateBindings(options) {
|
|
|
844
1184
|
"--output-dir",
|
|
845
1185
|
outputDir,
|
|
846
1186
|
"--overwrite",
|
|
847
|
-
|
|
848
|
-
network.config.rpcUrl,
|
|
849
|
-
"--network-passphrase",
|
|
850
|
-
network.config.networkPassphrase
|
|
1187
|
+
...buildStellarNetworkArgs(network)
|
|
851
1188
|
], {
|
|
852
1189
|
cwd,
|
|
853
1190
|
allowUntestedStellarCli: options.allowUntestedStellarCli,
|
|
@@ -862,6 +1199,7 @@ async function generateBindings(options) {
|
|
|
862
1199
|
}
|
|
863
1200
|
|
|
864
1201
|
// src/contracts/invoke-contract.ts
|
|
1202
|
+
var INVOKE_SIGNING_FAILURE_REGEX = /xdr processing error: xdr value invalid/i;
|
|
865
1203
|
function parseInvokeTarget(target) {
|
|
866
1204
|
const [contractName, method, extra] = target.split(".");
|
|
867
1205
|
if (!contractName || !method || extra) {
|
|
@@ -890,25 +1228,43 @@ async function invokeContract(options) {
|
|
|
890
1228
|
await checkBinary("stellar", "Install Stellar CLI before running caatinga invoke.", {
|
|
891
1229
|
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
892
1230
|
});
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
"
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
})
|
|
1231
|
+
let result;
|
|
1232
|
+
try {
|
|
1233
|
+
result = await runCommand("stellar", [
|
|
1234
|
+
"contract",
|
|
1235
|
+
"invoke",
|
|
1236
|
+
"--id",
|
|
1237
|
+
contractArtifact.contractId,
|
|
1238
|
+
"--source-account",
|
|
1239
|
+
source,
|
|
1240
|
+
...buildStellarNetworkArgs(network),
|
|
1241
|
+
"--",
|
|
1242
|
+
target.method,
|
|
1243
|
+
...options.args ?? []
|
|
1244
|
+
], {
|
|
1245
|
+
cwd,
|
|
1246
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli,
|
|
1247
|
+
failureCode: CaatingaErrorCode.INVOKE_FAILED
|
|
1248
|
+
});
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
if (error instanceof CaatingaError && error.code === CaatingaErrorCode.INVOKE_FAILED && INVOKE_SIGNING_FAILURE_REGEX.test(`${error.message}
|
|
1251
|
+
${error.hint ?? ""}`)) {
|
|
1252
|
+
throw new CaatingaError(
|
|
1253
|
+
error.message,
|
|
1254
|
+
error.code,
|
|
1255
|
+
[
|
|
1256
|
+
"Stellar CLI could not sign the invoke transaction (xdr value invalid).",
|
|
1257
|
+
"Stellar CLI 22.x has a known invoke signing bug; upgrade to 23.0.0 or newer (25.2.0 recommended).",
|
|
1258
|
+
" stellar --version",
|
|
1259
|
+
"Then retry with a funded identity, for example:",
|
|
1260
|
+
" stellar keys generate alice --fund --network testnet",
|
|
1261
|
+
" npx caatinga invoke counter.increment --network testnet --source alice"
|
|
1262
|
+
].join("\n"),
|
|
1263
|
+
error
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
throw error;
|
|
1267
|
+
}
|
|
912
1268
|
return {
|
|
913
1269
|
target,
|
|
914
1270
|
network,
|
|
@@ -918,7 +1274,7 @@ async function invokeContract(options) {
|
|
|
918
1274
|
|
|
919
1275
|
// src/templates/create-project-from-template.ts
|
|
920
1276
|
var import_promises6 = require("fs/promises");
|
|
921
|
-
var
|
|
1277
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
922
1278
|
var import_zod6 = require("zod");
|
|
923
1279
|
|
|
924
1280
|
// src/templates/template-manifest.schema.ts
|
|
@@ -946,14 +1302,50 @@ var TemplateManifestSchema = import_zod5.z.object({
|
|
|
946
1302
|
artifacts: import_zod5.z.string().default("caatinga.artifacts.json")
|
|
947
1303
|
})
|
|
948
1304
|
});
|
|
1305
|
+
function defaultCompatibleCoreRange(coreVersion = CAATINGA_CORE_VERSION) {
|
|
1306
|
+
const version = import_semver2.default.valid(import_semver2.default.coerce(coreVersion));
|
|
1307
|
+
if (!version) {
|
|
1308
|
+
throw new Error(`Invalid core version: ${coreVersion}`);
|
|
1309
|
+
}
|
|
1310
|
+
return `^${version}`;
|
|
1311
|
+
}
|
|
949
1312
|
function isCoreVersionCompatible(range, coreVersion = CAATINGA_CORE_VERSION) {
|
|
950
1313
|
return import_semver2.default.satisfies(coreVersion, range);
|
|
951
1314
|
}
|
|
1315
|
+
function getTemplateCompatibilityIssue(manifest, coreVersion = CAATINGA_CORE_VERSION) {
|
|
1316
|
+
if (manifest.caatinga.templateVersion !== CURRENT_TEMPLATE_VERSION) {
|
|
1317
|
+
return {
|
|
1318
|
+
kind: "template-version",
|
|
1319
|
+
expected: CURRENT_TEMPLATE_VERSION,
|
|
1320
|
+
actual: manifest.caatinga.templateVersion
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore, coreVersion)) {
|
|
1324
|
+
return {
|
|
1325
|
+
kind: "core-range",
|
|
1326
|
+
requiredRange: manifest.caatinga.compatibleCore,
|
|
1327
|
+
runningVersion: coreVersion
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
function formatTemplateCompatibilityMessage(issue) {
|
|
1333
|
+
if (issue.kind === "template-version") {
|
|
1334
|
+
return `Template manifest version ${issue.actual} is not supported; Caatinga requires templateVersion ${issue.expected}.`;
|
|
1335
|
+
}
|
|
1336
|
+
return `Template requires @caatinga/core ${issue.requiredRange} but running ${issue.runningVersion}.`;
|
|
1337
|
+
}
|
|
1338
|
+
function formatTemplateCompatibilityHint(issue) {
|
|
1339
|
+
if (issue.kind === "template-version") {
|
|
1340
|
+
return "Use a template built for this Caatinga release or upgrade Caatinga.";
|
|
1341
|
+
}
|
|
1342
|
+
return `Use a template with compatibleCore ${defaultCompatibleCoreRange(issue.runningVersion)} or install a matching Caatinga version.`;
|
|
1343
|
+
}
|
|
952
1344
|
|
|
953
1345
|
// src/templates/create-project-from-template.ts
|
|
954
1346
|
async function createProjectFromTemplate(options) {
|
|
955
|
-
const targetDir =
|
|
956
|
-
const templateDir =
|
|
1347
|
+
const targetDir = import_node_path8.default.resolve(options.targetDir);
|
|
1348
|
+
const templateDir = import_node_path8.default.resolve(options.templateDir);
|
|
957
1349
|
try {
|
|
958
1350
|
await (0, import_promises6.stat)(templateDir);
|
|
959
1351
|
} catch {
|
|
@@ -975,22 +1367,16 @@ async function createProjectFromTemplate(options) {
|
|
|
975
1367
|
return { targetDir, template: manifest };
|
|
976
1368
|
}
|
|
977
1369
|
async function readTemplateManifest(templateDir) {
|
|
978
|
-
const manifestPath =
|
|
1370
|
+
const manifestPath = import_node_path8.default.join(templateDir, "caatinga.template.json");
|
|
979
1371
|
try {
|
|
980
1372
|
const rawManifest = await (0, import_promises6.readFile)(manifestPath, "utf8");
|
|
981
1373
|
const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
"Template is not compatible with this Caatinga version.",
|
|
985
|
-
CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
|
|
986
|
-
"Use a compatible template version or upgrade Caatinga."
|
|
987
|
-
);
|
|
988
|
-
}
|
|
989
|
-
if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore)) {
|
|
1374
|
+
const compatibilityIssue = getTemplateCompatibilityIssue(manifest);
|
|
1375
|
+
if (compatibilityIssue) {
|
|
990
1376
|
throw new CaatingaError(
|
|
991
|
-
|
|
1377
|
+
formatTemplateCompatibilityMessage(compatibilityIssue),
|
|
992
1378
|
CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
|
|
993
|
-
|
|
1379
|
+
formatTemplateCompatibilityHint(compatibilityIssue)
|
|
994
1380
|
);
|
|
995
1381
|
}
|
|
996
1382
|
return manifest;
|
|
@@ -1018,7 +1404,7 @@ async function readTemplateManifest(templateDir) {
|
|
|
1018
1404
|
async function replaceTemplateVariables(dir, projectName) {
|
|
1019
1405
|
const entries = await (0, import_promises6.readdir)(dir);
|
|
1020
1406
|
await Promise.all(entries.map(async (entry) => {
|
|
1021
|
-
const entryPath =
|
|
1407
|
+
const entryPath = import_node_path8.default.join(dir, entry);
|
|
1022
1408
|
const entryStat = await (0, import_promises6.stat)(entryPath);
|
|
1023
1409
|
if (entryStat.isDirectory()) {
|
|
1024
1410
|
await replaceTemplateVariables(entryPath, projectName);
|
|
@@ -1041,7 +1427,7 @@ function isTextTemplateFile(filePath) {
|
|
|
1041
1427
|
".tsx",
|
|
1042
1428
|
".css",
|
|
1043
1429
|
".html"
|
|
1044
|
-
].includes(
|
|
1430
|
+
].includes(import_node_path8.default.extname(filePath));
|
|
1045
1431
|
}
|
|
1046
1432
|
|
|
1047
1433
|
// src/ci/is-transient-testnet-smoke-failure.ts
|
|
@@ -1100,5 +1486,6 @@ function isTransientTestnetSmokeFailure(logText) {
|
|
|
1100
1486
|
runCommand,
|
|
1101
1487
|
toCaatingaError,
|
|
1102
1488
|
updateArtifact,
|
|
1489
|
+
validateSourceShape,
|
|
1103
1490
|
writeArtifacts
|
|
1104
1491
|
});
|