Key Gotchas & Architectural Rules
A curated list of critical rules, boundaries, and gotchas to keep in mind when working with Caatinga.
Critical Rules
--sourcemust be a CLI identity alias: Always use an alias (e.g.alice). Never pass a public key (G...), secret key (S...), or seed phrase.- Deploy auto-generates bindings: Pass
--no-generateif you wish to skip binding generation during CI deploys. - Full graph deploy auto-runs
wire+sync-env: Pass--no-wireor--no-sync-envto disable post-deploy hooks and environment synchronization. As of 3.9.2,sync-envno longer wipes the target env file — only the keys mapped infrontend.envare rewritten; unrelated assignments, comments, and blank lines are left untouched, and new keys are appended. (Before 3.9.2 it rebuilt the whole file, silently destroying other variables like API keys.) - Browser invoke is single-invoker: Multi-signer transactions (
signAuthEntry) require custom application code and throwCAATINGA_MULTI_AUTH_REQUIRED. - ZK on mainnet is blocked by default: Dev ceremony artifacts require
--allow-dev-ceremonyto build, deploy or invoke on mainnet. As of 3.10.3, mainnet is detected by network name or by the public passphrase, so renaming the network (e.g.pubnet) does not bypass the block. - Fresh machine setup: Install Node 22+, Rust, and Stellar CLI manually, then verify with
npx ctg doctor. - Errors are public API: Machine-readable
CAATINGA_*error codes must be parsed by scripts instead of parsing raw stdout/stderr text messages. read()vsinvoke():read()is a read-only simulation (no key signing, no network transaction);invoke()signs and submits an on-chain transaction.- Wallet adapters must reject on dismissal: Wallet prompts should reject on user cancellation, never leaving promises pending indefinitely.
caatinga.artifacts.jsonis git-versioned: Commit your artifacts file to version control alongside your application code.- Binding freshness is tracked automatically: Managed via
.caatinga-bindings.jsonmarkers (fresh,stale,missing,unknown). ctg doctordeploy coverage is advisory: Un-deployed contracts produce warnings but do not block CI exit code even with--strict.- Stellar CLI compatibility: Hard floor is 23.0.0; last tested is 28.0.0. Newer versions output advisory warnings.
buildRoot: When specified, runs a singlestellar contract buildfrom the Cargo workspace root rather than per-contract crate builds.buildFeatures: Cargo features passed directly tostellar contract build. Combine with--no-default-featuresto override defaults.postDeploysource override: Per-hooksourceis validated viaassertSafeSourceAccount(rejectsS...,G..., seed phrases).postDeployexpect: Verified with the Expect DSL: a plain string or{ matcher, value? }withequals,reachable,isNull,isArray,minLength,maxLength,contains,matches,jsonEquals.frontend.envmaps source → env var name: Each key is a contract name (itscontractId),<contract>.wasmHash, or one ofnetwork,rpcUrl,networkPassphrase; each value is the env var to write (e.g.{ token: "VITE_TOKEN_ID" }). Env sync runs automatically only on a full-graph deploy when bothfrontend.envFileandfrontend.envare set; otherwise runctg sync-env.doctor --strict: Fails on environment drift (--strict-env) and stale bindings (--strict-bindings).ctg regression: Local execution recipe mirroring CI:pnpm test → build → deploy --if-changed → generate → smoke(the test step runspnpm testeven in npm projects; skip it with--skip-test).- Alias resolution: Method arguments accept
${source.address}or CLI identity aliases (length ≥ 3). identity export/import: Exports your whole Stellar config directory as a base64 tarball — not encrypted. Treat the output as key material forCAATINGA_CI_STELLAR_CONFIG_B64. Prior to 3.9.2, the archive was also left behind, world-readable, inos.tmpdir(); ifidentity export/importever ran on a machine before upgrading, check for and delete/tmp/caatinga-stellar-*.tar.gzand rotate any exposed keys. See CLI Identity Reference.- Config graph validation:
${contracts.*.contractId}placeholders indeployArgsmust be declared independsOn; validated at config load time. ctg upgradevsdeploy --upgrade: In-place preservescontractIdand invokesupgrade(); redeploy (deploy --upgrade) creates a new contract instance.read --summary/--quiet: Truncates large array payloads for clean terminal inspection on shared networks.ctg generaterequiresfrontend.bindingsOutput: Every--minimalscaffold starts without it.doctor/statusonly recommendgenerateonce the field is configured; the error prints the exact snippet to paste.- Concurrent
deploy/upgradeare safe as of 3.9.2: Artifact writes tocaatinga.artifacts.jsonare serialized with a lockfile and re-read inside the lock, so two processes running at once can no longer drop each other’scontractId. parseContractIdprefers labeled output: As of 3.9.2 it favors the last explicitly labeled contract-ID line, then the last standalone ID line, then the last bare match — and only matches the base32 alphabet (A-Z2-7) real strkeys use — so a diagnostic line printed before the real result can no longer be mistaken for the deployed contract ID.deployretries un-indexed WASM: If the RPC hasn’t yet indexed a just-uploaded WASM hash,deployretries with backoff instead of failing immediately withCAATINGA_DEPLOY_FAILED.- Mainnet confirmation cannot be turned off in config: As of 3.10.1,
requireConfirmation: falseis ignored on mainnet (by network name or by the public passphrase). In CI, pass--yesor setCAATINGA_ASSUME_YES=true; the[MAINNET GUARDRAIL]audit line is still printed. sourcePathin artifacts is relative: As of 3.10.1 bothdeployandupgradestore the relative contract path. Artifacts written by an olderctg upgrademay still contain an absolute local path until the contract is upgraded or deployed again.- Rust 1.91.1+, never 1.91.0:
stellar contract buildrefuses exactly 1.91.0 (and the 1.81–1.83 lines) withuse a rust version other than 1.81, 1.82, 1.83 or 1.91.0. As of 3.10.2,ctg doctorfails the Rust check for these and for anything older than 1.91.1. Runrustup update stable. - Source identity is required on mainnet:
deploy,invokeandupgradealways require--source. As of 3.10.3,ctg readandctg smokealso refuse the built-inalicefallback on mainnet: pass--source <alias>or setCAATINGA_SOURCE, otherwise they fail withCAATINGA_SOURCE_ACCOUNT_REQUIRED. - Mainnet uses your
rpcUrl, not the Stellar CLI’smainnetentry: The Stellar CLI’s built-inmainnetnetwork has no RPC URL (“Bring Your Own”). Caatinga 3.10.3+ always passes the configuredrpcUrlandnetworkPassphraseexplicitly on mainnet. On 3.10.2 or earlier, mainnet commands fail withInvalid URL Bring Your Own: …; upgrade instead of adding a localstellar network add mainnet. postDeployRead-only configs are wired: As of 3.12.0,ctg wireand full-graphctg deployrun hooks when onlypostDeployReadis configured. Earlier versions printed “No postDeploy hooks configured” and ran nothing.- New projects start on artifacts schema v2: As of 3.12.0,
ctg initandctg zk initalways writeversion: 2. Existing v1 files are read as-is;ctg migrate artifactsupgrades them. ctg statusis local,ctg inspectis on-chain:statusreads artifacts and binding markers only (no RPC). Usectg inspect <contract> --network <net>to check that a recorded contract is reachable.- Reads do not need a wallet: As of 3.12.0,
client.read()/simulate()work for visitors without a wallet; onlyinvoke()requires one.