Skip to content

Config

Caatinga projects use caatinga.config.ts.

ts
import { defineConfig } from "@caatinga/core";

export default defineConfig({
  project: "my-dapp",
  defaultNetwork: "testnet",
  contracts: {
    counter: {
      path: "./contracts/counter",
      wasm: "./contracts/counter/target/wasm32v1-none/release/counter.wasm",
    },
  },
  networks: {
    testnet: {
      rpcUrl: "https://soroban-testnet.stellar.org",
      networkPassphrase: "Test SDF Network ; September 2015",
    },
  },
  frontend: {
    framework: "vite-react",
    bindingsOutput: "./src/contracts/generated",
  },
});

Field reference

caatinga.config.ts has no version field (unlike caatinga.artifacts.json); do not look for one.

Root config:

FieldTypeRequiredDefaultNotes
projectstring (min 1)yes
defaultNetworkstring (min 1)no"testnet"
buildRootstring (min 1)noCargo workspace root for a single stellar contract build
contractsRecord<string, ContractConfig>yesat least one entry
networksRecord<string, NetworkConfig>yesat least one entry
frontendFrontendConfignooptional frontend configuration (see below)
zkZkConfignoZK circuit configuration (see below)

ContractConfig (each value in contracts):

FieldTypeRequiredDefaultNotes
pathstring (min 1)yescontract source directory
wasmstring (min 1)yescompiled WASM path
dependsOnstring[]no[]contract names deployed first
deployArgsRecord<string, string | number | boolean>no{}constructor args; supports placeholders

FrontendConfig (optional root frontend field):

FieldTypeRequiredDefaultNotes
framework"vite-react"no"vite-react"Official templates are Vite + React only; no Next.js or Astro adapter yet.
bindingsOutputstring (min 1)yespath for generated bindings
envFilestring (min 1)nofrontend env file written by caatinga sync-env
envRecord<string, string>nomaps config contract keys (or rpcUrl / networkPassphrase) to env var names

postDeploy (optional root field):

FieldTypeRequiredNotes
postDeployarraynoadmin-signed invokes run after full deploy

Each postDeploy entry:

FieldTypeRequiredNotes
contractstring (min 1)yesconfigured contract name
methodstring (min 1)yesSoroban method to invoke
argsRecord<string, string | number | boolean>nosupports the same placeholders as deployArgs

NetworkConfig (each value in networks):

FieldTypeRequiredNotes
rpcUrlstring (valid URL)yes
networkPassphrasestring (min 1)yes

ZkConfig (optional root zk field):

FieldTypeRequiredNotes
zk.circuitsRecord<string, ZkCircuitConfig>yesat least one circuit entry

ZkCircuitConfig (each value in zk.circuits):

FieldTypeRequiredNotes
pathstring (min 1)yesdirectory containing .circom files
protocol"groth16"yesonly Groth16 supported today
curve"bls12381"yesonly BLS12-381 supported today
verifierContractstringnocontract name for on-chain verification

Artifacts

Artifacts are network-scoped so counter can have different contract IDs on testnet and mainnet. Schema v2 is current; v1 files are still readable. Run caatinga migrate artifacts to bump the file version without redeploying.

Multi-frontend: one caatinga.artifacts.json per Caatinga project root. Multiple apps (web, admin, mobile wrapper) should import the same artifacts file and generated bindings — do not fork artifacts per frontend.

Multi-environment (staging vs production on the same network) is not supported yet. Use git branches, separate Caatinga projects, or wait for a future environments dimension in v1.0+.

Top-level shape: project (string), version (1 or 2), and networks (Record<network, { contracts, dependencyGraph }>). New projects initialize with version: 2.

json
{
  "project": "my-dapp",
  "version": 2,
  "networks": {
    "testnet": {
      "contracts": {},
      "dependencyGraph": {}
    }
  }
}

After a deploy, each contract is recorded under networks.<network>.contracts.<name> as a ContractArtifact:

json
{
  "project": "my-dapp",
  "version": 2,
  "networks": {
    "testnet": {
      "contracts": {
        "counter": {
          "contractId": "C...",
          "wasmHash": "...",
          "deployedAt": "2026-01-01T00:00:00.000Z",
          "sourcePath": "./contracts/counter",
          "wasmPath": "./contracts/counter/target/wasm32v1-none/release/counter.wasm",
          "dependencies": [],
          "resolvedDeployArgs": {}
        }
      },
      "dependencyGraph": {}
    }
  }
}

ContractArtifact fields:

FieldTypeRequiredDefaultNotes
contractIdstring (min 1)yesdeployed on-chain ID
wasmHashstring (min 1)yeshash of the deployed WASM
deployedAtISO 8601 datetime stringyes
sourcePathstring (min 1)yes
wasmPathstring (min 1)yes
dependenciesstring[]no[]resolved dependency contract names
resolvedDeployArgsRecord<string, string | number | boolean>no{}deploy args after placeholder resolution

Multi-contract dependencies

dependsOn lists contracts that must deploy before the current contract. deployArgs may use ${contracts.<name>.contractId} placeholders, resolved from caatinga.artifacts.json after dependencies deploy:

ts
contracts: {
  token: {
    path: "./contracts/token",
    wasm: "./contracts/token/target/wasm32v1-none/release/token.wasm"
  },
  vault: {
    path: "./contracts/vault",
    wasm: "./contracts/vault/target/wasm32v1-none/release/vault.wasm",
    dependsOn: ["token"],
    deployArgs: {
      tokenContractId: "${contracts.token.contractId}"
    }
  }
}

How resolution works

  1. Deploy order — topological sort (resolveDeployOrder): a depth-first walk over dependsOn, marking each contract visiting then visited.
    • Re-entering a visiting node throws CAATINGA_CONTRACT_DEPENDENCY_CYCLE; the message lists the path, e.g. a -> b -> a.
    • A dependsOn entry that is not a configured contract throws CAATINGA_CONTRACT_DEPENDENCY_NOT_FOUND.
  2. Placeholder resolution (resolveDeployArgs): a deployArgs value that is a string containing ${ must match exactly one of:
    • ${contracts.<name>.contractId} — resolved from caatinga.artifacts.json
    • ${source.address} — resolved from stellar keys address <source> at deploy/wire time There is no ${env.*} and no $(...) shell interpolation. Deploy args are data passed to the Stellar CLI, not a second templating language (see ADR 0005 and ADR 0006).
    • A ${...} value that does not match throws CAATINGA_DEPLOY_ARG_PLACEHOLDER_INVALID.
    • The contractId is read from artifacts.networks[<network>].contracts[<name>].contractId; when absent it throws CAATINGA_CONTRACT_DEPENDENCY_ARTIFACT_NOT_FOUND (deploy the dependency first).
    • A placeholder still unresolved at deploy time throws CAATINGA_DEPLOY_ARG_PLACEHOLDER_UNRESOLVED.
  3. CLI flag derivation (toSnakeCaseFlag / formatConstructorCliArgs): resolved args are passed to stellar contract deploy after a -- separator. Each key is converted camelCase → snake*case (insert *before each uppercase letter, strip a leading\_, lowercase). For example tokenContractIdbecomes--token_contract_id.

End-to-end: with deployArgs: { tokenContractId: "${contracts.token.contractId}" }, token deploys first, its contractId is recorded in caatinga.artifacts.json, and the vault deploy appends -- --token_contract_id C<token-id>.