@caatinga/core 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +89 -15
- package/dist/index.cjs +161 -20
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +161 -20
- package/package.json +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,33 +1,107 @@
|
|
|
1
1
|
# @caatinga/core
|
|
2
2
|
|
|
3
|
-
Core config, artifacts, command orchestration, and error primitives for Caatinga.
|
|
3
|
+
Core config, artifacts, command orchestration, and error primitives for the Caatinga / Stellar Soroban toolkit.
|
|
4
4
|
|
|
5
|
-
## Supported
|
|
5
|
+
## Supported use
|
|
6
6
|
|
|
7
|
-
`@caatinga/core` is primarily an internal package for
|
|
7
|
+
`@caatinga/core` is primarily an internal package for `@caatinga/cli` and official templates. Direct use beyond the documented config and template surface is advanced and less stable than the CLI contract.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Most applications should install `@caatinga/cli` or `@caatinga/client` instead of depending on core directly.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- config loading and validation
|
|
13
|
-
- artifact resolution and update helpers
|
|
14
|
-
- shared command orchestration primitives
|
|
15
|
-
- public `CAATINGA_*` error foundations used by higher-level packages
|
|
11
|
+
## What is in this package
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
### Config and networks
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
- `defineConfig` for `caatinga.config.ts` in generated projects
|
|
16
|
+
- `loadConfig`, `CaatingaConfigSchema`, and related types
|
|
17
|
+
- `WELL_KNOWN_NETWORKS`, `resolveNetwork`
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
### Artifacts
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
- `readArtifacts`, `writeArtifacts`, `createInitialArtifacts`, `updateArtifact`
|
|
22
|
+
- `CaatingaArtifactsSchema` and artifact types (`caatinga.artifacts.json`, schema version `1`)
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
### Contracts and Stellar CLI orchestration
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
- `buildContract`, `deployContract`, `deployContractGraph`
|
|
27
|
+
- `buildDependencyGraph`, `resolveDeployOrder`, `resolveDeployArgs`
|
|
28
|
+
- `generateBindings`, `invokeContract`, `parseInvokeTarget`
|
|
29
|
+
- `resolveContract`, `parseContractId`
|
|
30
|
+
- Stellar CLI version constants and guards (`STELLAR_CLI_MIN_VERSION`, `STELLAR_CLI_TESTED_MAX_VERSION`, `assertSupportedStellarCliVersion`)
|
|
31
|
+
|
|
32
|
+
### Templates
|
|
33
|
+
|
|
34
|
+
- `createProjectFromTemplate` and `TemplateManifestSchema`
|
|
35
|
+
- compatibility checks against `CAATINGA_CORE_VERSION` and template manifests
|
|
36
|
+
|
|
37
|
+
### Shell and errors
|
|
38
|
+
|
|
39
|
+
- `runCommand`, `checkBinary`
|
|
40
|
+
- `CaatingaError`, `CaatingaErrorCode`, `toCaatingaError`
|
|
41
|
+
- `CAATINGA_CORE_VERSION`
|
|
42
|
+
|
|
43
|
+
## Intended config surface
|
|
44
|
+
|
|
45
|
+
Generated projects import `defineConfig` from `@caatinga/core`:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { defineConfig } from "@caatinga/core";
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
project: "my-dapp",
|
|
52
|
+
defaultNetwork: "testnet",
|
|
53
|
+
contracts: {
|
|
54
|
+
counter: {
|
|
55
|
+
path: "./contracts/counter",
|
|
56
|
+
wasm: "./contracts/counter/target/wasm32-unknown-unknown/release/counter.wasm"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
networks: {
|
|
60
|
+
testnet: {
|
|
61
|
+
rpcUrl: "https://soroban-testnet.stellar.org",
|
|
62
|
+
networkPassphrase: "Test SDF Network ; September 2015"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
frontend: {
|
|
66
|
+
framework: "vite-react",
|
|
67
|
+
bindingsOutput: "./contracts/generated"
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Multi-contract projects may declare `dependsOn` and `${contracts.<name>.contractId}` placeholders in `deployArgs`; `deployContractGraph` resolves and deploys dependencies before dependents.
|
|
73
|
+
|
|
74
|
+
## Consumer guidance
|
|
75
|
+
|
|
76
|
+
| Goal | Package |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| End-user CLI workflow | `@caatinga/cli` |
|
|
79
|
+
| Browser / Node client over generated bindings | `@caatinga/client` |
|
|
80
|
+
| `caatinga.config.ts` in a Caatinga project | `defineConfig` from `@caatinga/core` |
|
|
81
|
+
| Custom tooling on deploy graphs, artifacts, or Stellar CLI orchestration | `@caatinga/core` (advanced; track releases closely) |
|
|
82
|
+
|
|
83
|
+
## Error codes
|
|
84
|
+
|
|
85
|
+
Core owns the canonical `CAATINGA_*` enum used by CLI, client, and templates. Automation should match on codes, not message text.
|
|
86
|
+
|
|
87
|
+
Common codes surfaced through core-backed commands:
|
|
88
|
+
|
|
89
|
+
- config and artifacts: `CAATINGA_CONFIG_NOT_FOUND`, `CAATINGA_INVALID_CONFIG`, `CAATINGA_ARTIFACT_NOT_FOUND`, `CAATINGA_ARTIFACT_INVALID`
|
|
90
|
+
- Stellar CLI: `CAATINGA_STELLAR_CLI_NOT_FOUND`, `CAATINGA_UNSUPPORTED_CLI_VERSION`, `CAATINGA_UNTESTED_CLI_VERSION`
|
|
91
|
+
- contracts: `CAATINGA_BUILD_FAILED`, `CAATINGA_DEPLOY_FAILED`, `CAATINGA_BINDINGS_FAILED`, `CAATINGA_INVOKE_FAILED`
|
|
92
|
+
- dependencies: `CAATINGA_CONTRACT_DEPENDENCY_NOT_FOUND`, `CAATINGA_CONTRACT_DEPENDENCY_CYCLE`, `CAATINGA_DEPLOY_ARG_PLACEHOLDER_UNRESOLVED`
|
|
93
|
+
- templates: `CAATINGA_TEMPLATE_MANIFEST_NOT_FOUND`, `CAATINGA_TEMPLATE_INCOMPATIBLE`
|
|
94
|
+
|
|
95
|
+
Full table: [docs/errors.md](https://github.com/Dione-b/caatinga/blob/main/docs/errors.md)
|
|
96
|
+
|
|
97
|
+
## Stability posture
|
|
98
|
+
|
|
99
|
+
Being published does not make every export a first-class end-user contract. Stable consumer surfaces are:
|
|
28
100
|
|
|
29
101
|
- the documented CLI workflow in `@caatinga/cli`
|
|
30
102
|
- the documented `@caatinga/client` APIs
|
|
31
103
|
- the narrow `@caatinga/core` config/template surface used by generated projects, including `defineConfig`
|
|
32
104
|
|
|
33
105
|
Direct `@caatinga/core` usage should be treated as advanced integration with a narrower support posture than the CLI package.
|
|
106
|
+
|
|
107
|
+
Further reference: [architecture](https://github.com/Dione-b/caatinga/blob/main/docs/architecture.md), [config](https://github.com/Dione-b/caatinga/blob/main/docs/config.md), [templates](https://github.com/Dione-b/caatinga/blob/main/docs/templates.md).
|
package/dist/index.cjs
CHANGED
|
@@ -135,7 +135,7 @@ function toCaatingaError(error) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// src/version.ts
|
|
138
|
-
var CAATINGA_CORE_VERSION = "0.2.
|
|
138
|
+
var CAATINGA_CORE_VERSION = "0.2.1";
|
|
139
139
|
|
|
140
140
|
// src/config/config.schema.ts
|
|
141
141
|
var import_zod = require("zod");
|
|
@@ -553,6 +553,85 @@ async function buildContract(options) {
|
|
|
553
553
|
// src/contracts/deploy-contract.ts
|
|
554
554
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
555
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
|
+
}
|
|
634
|
+
|
|
556
635
|
// src/contracts/dependency-graph.ts
|
|
557
636
|
function buildDependencyGraph(contracts) {
|
|
558
637
|
const graph = {};
|
|
@@ -599,14 +678,21 @@ function assertSafeSourceAccount(source) {
|
|
|
599
678
|
throw new CaatingaError(
|
|
600
679
|
"A source account or Stellar CLI identity is required.",
|
|
601
680
|
CaatingaErrorCode.SOURCE_ACCOUNT_REQUIRED,
|
|
602
|
-
"Pass
|
|
681
|
+
"Pass a Stellar CLI identity alias, for example: --source alice"
|
|
603
682
|
);
|
|
604
683
|
}
|
|
605
684
|
if (source.startsWith("S") || source.trim().includes(" ")) {
|
|
606
685
|
throw new CaatingaError(
|
|
607
686
|
"Refusing to accept a likely secret key or seed phrase as --source.",
|
|
608
687
|
CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
|
|
609
|
-
"Use a Stellar CLI identity alias
|
|
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"
|
|
610
696
|
);
|
|
611
697
|
}
|
|
612
698
|
return source;
|
|
@@ -685,14 +771,39 @@ async function deployContract(options) {
|
|
|
685
771
|
network.config.networkPassphrase,
|
|
686
772
|
...constructorArgs
|
|
687
773
|
];
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
774
|
+
let output = "";
|
|
775
|
+
let contractId;
|
|
776
|
+
try {
|
|
777
|
+
const result = await runCommand("stellar", stellarArgs, {
|
|
778
|
+
cwd,
|
|
779
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli,
|
|
780
|
+
failureCode: CaatingaErrorCode.DEPLOY_FAILED
|
|
781
|
+
});
|
|
782
|
+
output = result.all || `${result.stdout}
|
|
694
783
|
${result.stderr}`;
|
|
695
|
-
|
|
784
|
+
contractId = parseContractId(output);
|
|
785
|
+
} catch (error) {
|
|
786
|
+
if (!(error instanceof CaatingaError) || error.code !== CaatingaErrorCode.DEPLOY_FAILED) {
|
|
787
|
+
throw error;
|
|
788
|
+
}
|
|
789
|
+
const recoveredContractId = await tryRecoverContractIdFromDeployFailure({
|
|
790
|
+
output: `${error.message}
|
|
791
|
+
${error.hint ?? ""}`,
|
|
792
|
+
source,
|
|
793
|
+
network: network.config,
|
|
794
|
+
cwd,
|
|
795
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
796
|
+
});
|
|
797
|
+
if (!recoveredContractId) {
|
|
798
|
+
throw error;
|
|
799
|
+
}
|
|
800
|
+
contractId = recoveredContractId;
|
|
801
|
+
output = [
|
|
802
|
+
error.hint ?? "",
|
|
803
|
+
"Caatinga recovered the contract ID from the on-chain deploy transaction.",
|
|
804
|
+
`Contract ID: ${contractId}`
|
|
805
|
+
].filter(Boolean).join("\n");
|
|
806
|
+
}
|
|
696
807
|
const wasmHash = await hashWasm(contract.wasmPath);
|
|
697
808
|
const dependencyGraph = buildDependencyGraph(options.config.contracts);
|
|
698
809
|
const dependencies = options.dependencies ?? contract.config.dependsOn;
|
|
@@ -946,9 +1057,45 @@ var TemplateManifestSchema = import_zod5.z.object({
|
|
|
946
1057
|
artifacts: import_zod5.z.string().default("caatinga.artifacts.json")
|
|
947
1058
|
})
|
|
948
1059
|
});
|
|
1060
|
+
function defaultCompatibleCoreRange(coreVersion = CAATINGA_CORE_VERSION) {
|
|
1061
|
+
const version = import_semver2.default.valid(import_semver2.default.coerce(coreVersion));
|
|
1062
|
+
if (!version) {
|
|
1063
|
+
throw new Error(`Invalid core version: ${coreVersion}`);
|
|
1064
|
+
}
|
|
1065
|
+
return `^${version}`;
|
|
1066
|
+
}
|
|
949
1067
|
function isCoreVersionCompatible(range, coreVersion = CAATINGA_CORE_VERSION) {
|
|
950
1068
|
return import_semver2.default.satisfies(coreVersion, range);
|
|
951
1069
|
}
|
|
1070
|
+
function getTemplateCompatibilityIssue(manifest, coreVersion = CAATINGA_CORE_VERSION) {
|
|
1071
|
+
if (manifest.caatinga.templateVersion !== CURRENT_TEMPLATE_VERSION) {
|
|
1072
|
+
return {
|
|
1073
|
+
kind: "template-version",
|
|
1074
|
+
expected: CURRENT_TEMPLATE_VERSION,
|
|
1075
|
+
actual: manifest.caatinga.templateVersion
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore, coreVersion)) {
|
|
1079
|
+
return {
|
|
1080
|
+
kind: "core-range",
|
|
1081
|
+
requiredRange: manifest.caatinga.compatibleCore,
|
|
1082
|
+
runningVersion: coreVersion
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
function formatTemplateCompatibilityMessage(issue) {
|
|
1088
|
+
if (issue.kind === "template-version") {
|
|
1089
|
+
return `Template manifest version ${issue.actual} is not supported; Caatinga requires templateVersion ${issue.expected}.`;
|
|
1090
|
+
}
|
|
1091
|
+
return `Template requires @caatinga/core ${issue.requiredRange} but running ${issue.runningVersion}.`;
|
|
1092
|
+
}
|
|
1093
|
+
function formatTemplateCompatibilityHint(issue) {
|
|
1094
|
+
if (issue.kind === "template-version") {
|
|
1095
|
+
return "Use a template built for this Caatinga release or upgrade Caatinga.";
|
|
1096
|
+
}
|
|
1097
|
+
return `Use a template with compatibleCore ${defaultCompatibleCoreRange(issue.runningVersion)} or install a matching Caatinga version.`;
|
|
1098
|
+
}
|
|
952
1099
|
|
|
953
1100
|
// src/templates/create-project-from-template.ts
|
|
954
1101
|
async function createProjectFromTemplate(options) {
|
|
@@ -979,18 +1126,12 @@ async function readTemplateManifest(templateDir) {
|
|
|
979
1126
|
try {
|
|
980
1127
|
const rawManifest = await (0, import_promises6.readFile)(manifestPath, "utf8");
|
|
981
1128
|
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)) {
|
|
1129
|
+
const compatibilityIssue = getTemplateCompatibilityIssue(manifest);
|
|
1130
|
+
if (compatibilityIssue) {
|
|
990
1131
|
throw new CaatingaError(
|
|
991
|
-
|
|
1132
|
+
formatTemplateCompatibilityMessage(compatibilityIssue),
|
|
992
1133
|
CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
|
|
993
|
-
|
|
1134
|
+
formatTemplateCompatibilityHint(compatibilityIssue)
|
|
994
1135
|
);
|
|
995
1136
|
}
|
|
996
1137
|
return manifest;
|
package/dist/index.d.cts
CHANGED
|
@@ -52,7 +52,7 @@ declare class CaatingaError extends Error {
|
|
|
52
52
|
}
|
|
53
53
|
declare function toCaatingaError(error: unknown): CaatingaError;
|
|
54
54
|
|
|
55
|
-
declare const CAATINGA_CORE_VERSION = "0.2.
|
|
55
|
+
declare const CAATINGA_CORE_VERSION = "0.2.1";
|
|
56
56
|
|
|
57
57
|
declare const ContractConfigSchema: z.ZodObject<{
|
|
58
58
|
path: z.ZodString;
|
package/dist/index.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ declare class CaatingaError extends Error {
|
|
|
52
52
|
}
|
|
53
53
|
declare function toCaatingaError(error: unknown): CaatingaError;
|
|
54
54
|
|
|
55
|
-
declare const CAATINGA_CORE_VERSION = "0.2.
|
|
55
|
+
declare const CAATINGA_CORE_VERSION = "0.2.1";
|
|
56
56
|
|
|
57
57
|
declare const ContractConfigSchema: z.ZodObject<{
|
|
58
58
|
path: z.ZodString;
|
package/dist/index.js
CHANGED
|
@@ -66,7 +66,7 @@ function toCaatingaError(error) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// src/version.ts
|
|
69
|
-
var CAATINGA_CORE_VERSION = "0.2.
|
|
69
|
+
var CAATINGA_CORE_VERSION = "0.2.1";
|
|
70
70
|
|
|
71
71
|
// src/config/config.schema.ts
|
|
72
72
|
import { z } from "zod";
|
|
@@ -483,6 +483,85 @@ async function buildContract(options) {
|
|
|
483
483
|
// src/contracts/deploy-contract.ts
|
|
484
484
|
import path5 from "path";
|
|
485
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
|
+
}
|
|
564
|
+
|
|
486
565
|
// src/contracts/dependency-graph.ts
|
|
487
566
|
function buildDependencyGraph(contracts) {
|
|
488
567
|
const graph = {};
|
|
@@ -529,14 +608,21 @@ function assertSafeSourceAccount(source) {
|
|
|
529
608
|
throw new CaatingaError(
|
|
530
609
|
"A source account or Stellar CLI identity is required.",
|
|
531
610
|
CaatingaErrorCode.SOURCE_ACCOUNT_REQUIRED,
|
|
532
|
-
"Pass
|
|
611
|
+
"Pass a Stellar CLI identity alias, for example: --source alice"
|
|
533
612
|
);
|
|
534
613
|
}
|
|
535
614
|
if (source.startsWith("S") || source.trim().includes(" ")) {
|
|
536
615
|
throw new CaatingaError(
|
|
537
616
|
"Refusing to accept a likely secret key or seed phrase as --source.",
|
|
538
617
|
CaatingaErrorCode.UNSAFE_SOURCE_ACCOUNT,
|
|
539
|
-
"Use a Stellar CLI identity alias
|
|
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"
|
|
540
626
|
);
|
|
541
627
|
}
|
|
542
628
|
return source;
|
|
@@ -615,14 +701,39 @@ async function deployContract(options) {
|
|
|
615
701
|
network.config.networkPassphrase,
|
|
616
702
|
...constructorArgs
|
|
617
703
|
];
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
704
|
+
let output = "";
|
|
705
|
+
let contractId;
|
|
706
|
+
try {
|
|
707
|
+
const result = await runCommand("stellar", stellarArgs, {
|
|
708
|
+
cwd,
|
|
709
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli,
|
|
710
|
+
failureCode: CaatingaErrorCode.DEPLOY_FAILED
|
|
711
|
+
});
|
|
712
|
+
output = result.all || `${result.stdout}
|
|
624
713
|
${result.stderr}`;
|
|
625
|
-
|
|
714
|
+
contractId = parseContractId(output);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
if (!(error instanceof CaatingaError) || error.code !== CaatingaErrorCode.DEPLOY_FAILED) {
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
719
|
+
const recoveredContractId = await tryRecoverContractIdFromDeployFailure({
|
|
720
|
+
output: `${error.message}
|
|
721
|
+
${error.hint ?? ""}`,
|
|
722
|
+
source,
|
|
723
|
+
network: network.config,
|
|
724
|
+
cwd,
|
|
725
|
+
allowUntestedStellarCli: options.allowUntestedStellarCli
|
|
726
|
+
});
|
|
727
|
+
if (!recoveredContractId) {
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
contractId = recoveredContractId;
|
|
731
|
+
output = [
|
|
732
|
+
error.hint ?? "",
|
|
733
|
+
"Caatinga recovered the contract ID from the on-chain deploy transaction.",
|
|
734
|
+
`Contract ID: ${contractId}`
|
|
735
|
+
].filter(Boolean).join("\n");
|
|
736
|
+
}
|
|
626
737
|
const wasmHash = await hashWasm(contract.wasmPath);
|
|
627
738
|
const dependencyGraph = buildDependencyGraph(options.config.contracts);
|
|
628
739
|
const dependencies = options.dependencies ?? contract.config.dependsOn;
|
|
@@ -876,9 +987,45 @@ var TemplateManifestSchema = z5.object({
|
|
|
876
987
|
artifacts: z5.string().default("caatinga.artifacts.json")
|
|
877
988
|
})
|
|
878
989
|
});
|
|
990
|
+
function defaultCompatibleCoreRange(coreVersion = CAATINGA_CORE_VERSION) {
|
|
991
|
+
const version = semver2.valid(semver2.coerce(coreVersion));
|
|
992
|
+
if (!version) {
|
|
993
|
+
throw new Error(`Invalid core version: ${coreVersion}`);
|
|
994
|
+
}
|
|
995
|
+
return `^${version}`;
|
|
996
|
+
}
|
|
879
997
|
function isCoreVersionCompatible(range, coreVersion = CAATINGA_CORE_VERSION) {
|
|
880
998
|
return semver2.satisfies(coreVersion, range);
|
|
881
999
|
}
|
|
1000
|
+
function getTemplateCompatibilityIssue(manifest, coreVersion = CAATINGA_CORE_VERSION) {
|
|
1001
|
+
if (manifest.caatinga.templateVersion !== CURRENT_TEMPLATE_VERSION) {
|
|
1002
|
+
return {
|
|
1003
|
+
kind: "template-version",
|
|
1004
|
+
expected: CURRENT_TEMPLATE_VERSION,
|
|
1005
|
+
actual: manifest.caatinga.templateVersion
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore, coreVersion)) {
|
|
1009
|
+
return {
|
|
1010
|
+
kind: "core-range",
|
|
1011
|
+
requiredRange: manifest.caatinga.compatibleCore,
|
|
1012
|
+
runningVersion: coreVersion
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
function formatTemplateCompatibilityMessage(issue) {
|
|
1018
|
+
if (issue.kind === "template-version") {
|
|
1019
|
+
return `Template manifest version ${issue.actual} is not supported; Caatinga requires templateVersion ${issue.expected}.`;
|
|
1020
|
+
}
|
|
1021
|
+
return `Template requires @caatinga/core ${issue.requiredRange} but running ${issue.runningVersion}.`;
|
|
1022
|
+
}
|
|
1023
|
+
function formatTemplateCompatibilityHint(issue) {
|
|
1024
|
+
if (issue.kind === "template-version") {
|
|
1025
|
+
return "Use a template built for this Caatinga release or upgrade Caatinga.";
|
|
1026
|
+
}
|
|
1027
|
+
return `Use a template with compatibleCore ${defaultCompatibleCoreRange(issue.runningVersion)} or install a matching Caatinga version.`;
|
|
1028
|
+
}
|
|
882
1029
|
|
|
883
1030
|
// src/templates/create-project-from-template.ts
|
|
884
1031
|
async function createProjectFromTemplate(options) {
|
|
@@ -909,18 +1056,12 @@ async function readTemplateManifest(templateDir) {
|
|
|
909
1056
|
try {
|
|
910
1057
|
const rawManifest = await readFile3(manifestPath, "utf8");
|
|
911
1058
|
const manifest = TemplateManifestSchema.parse(JSON.parse(rawManifest));
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
"Template is not compatible with this Caatinga version.",
|
|
915
|
-
CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
|
|
916
|
-
"Use a compatible template version or upgrade Caatinga."
|
|
917
|
-
);
|
|
918
|
-
}
|
|
919
|
-
if (!isCoreVersionCompatible(manifest.caatinga.compatibleCore)) {
|
|
1059
|
+
const compatibilityIssue = getTemplateCompatibilityIssue(manifest);
|
|
1060
|
+
if (compatibilityIssue) {
|
|
920
1061
|
throw new CaatingaError(
|
|
921
|
-
|
|
1062
|
+
formatTemplateCompatibilityMessage(compatibilityIssue),
|
|
922
1063
|
CaatingaErrorCode.TEMPLATE_INCOMPATIBLE,
|
|
923
|
-
|
|
1064
|
+
formatTemplateCompatibilityHint(compatibilityIssue)
|
|
924
1065
|
);
|
|
925
1066
|
}
|
|
926
1067
|
return manifest;
|