On this pageNavigation ▾

Add Caatinga to an Existing Project

Caatinga is not a template generator. ctg init is one way to start, but most projects already have Rust contracts, a Cargo workspace, a frontend, and a deploy script that copies contract IDs into .env by hand. Caatinga replaces that script — your code stays where it is.

You add two files at the project root:

  • caatinga.config.ts — what to build, the deploy graph, networks, and where the frontend lives.
  • caatinga.artifacts.json — the deployed state per network, written by Caatinga from then on.

Nothing is copied into your repository except generated bindings and the env file you point it at.

This guide was run end to end on testnet with @caatinga/cli 3.12.0 against a two-contract Cargo workspace. The stellar-album dApp adopted Caatinga the same way: seven contracts, its own React frontend, and its bootstrap.sh reduced to a single caatinga deploy.

Starting point

A typical Soroban project before Caatinga:

my-dapp/
├── Cargo.toml              # [workspace] members = ["contracts/*"]
├── contracts/
│   ├── token/
│   └── vault/              # constructor takes the token contract address
├── frontend/               # your app (Vite, Next.js, …)
└── deploy.sh               # stellar contract deploy … && echo "VITE_TOKEN_ID=…" > frontend/.env.local

Step 1: Install the CLI

From the project root (add a root package.json with npm init -y if you don’t have one):

npm install -D @caatinga/cli @caatinga/core @stellar/stellar-sdk@^16.0.1
  • @caatinga/core provides defineConfig for the config file.
  • @stellar/stellar-sdk pins the SDK used by ctg generate. Without it, generation runs whatever npx resolves (currently 17.x, outside the tested 16.x line).

Step 2: Write caatinga.config.ts

Describe what you already have. Paths are relative to the project root.

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

export default defineConfig({
  project: "my-dapp",
  defaultNetwork: "testnet",
  // Your Cargo workspace root: one `stellar contract build` builds every member.
  buildRoot: ".",
  contracts: {
    token: {
      path: "./contracts/token",
      wasm: "./target/wasm32v1-none/release/token.wasm",
    },
    vault: {
      path: "./contracts/vault",
      wasm: "./target/wasm32v1-none/release/vault.wasm",
      dependsOn: ["token"],
      // Constructor args; the placeholder is resolved from the artifacts after `token` deploys.
      deployArgs: { token: "${contracts.token.contractId}" },
    },
  },
  networks: {
    testnet: {
      rpcUrl: "https://soroban-testnet.stellar.org",
      networkPassphrase: "Test SDF Network ; September 2015",
    },
  },
  frontend: {
    bindingsOutput: "./frontend/src/contracts",
    envFile: "./frontend/.env.local",
    // source → env var name
    env: {
      token: "VITE_TOKEN_ID",
      vault: "VITE_VAULT_ID",
      network: "VITE_NETWORK",
    },
  },
});

What maps to what:

Your existing setup In caatinga.config.ts
Cargo workspace root buildRoot: "." (omit it to build each contract crate on its own)
A contract’s WASM output contracts.<name>.wasm (under target/wasm32v1-none/release/)
Deploy order and constructor args dependsOn + deployArgs with ${contracts.<name>.contractId} or ${source.address}
Admin calls after deploy (set_minter, …) postDeploy hooks, run by ctg wire
IDs copied into .env frontend.envFile + frontend.env (<contract>, <contract>.wasmHash, network, rpcUrl, networkPassphrase)

frontend.framework defaults to "vite-react" (the only accepted value), so you can omit it. Nothing requires your app to use React, Vite, or @caatinga/client: bindings and the env file are plain files any frontend can import.

Step 3: Create the artifacts file

No command creates caatinga.artifacts.json in an existing directory (ctg init scaffolds a new project instead), and without it ctg doctor reports Status: blocked and ctg deploy fails with CAATINGA_ARTIFACT_NOT_FOUND. Create it once with an empty network map — this is the same file ctg init --minimal writes:

echo '{ "project": "my-dapp", "version": 2, "networks": {} }' > caatinga.artifacts.json

From here on, Caatinga owns this file: don’t edit it by hand, and commit it.

ctg doctor still suggests npx ctg init when the artifacts file is missing. In an existing project, create the file as above instead.

Step 4: Verify and build

npx ctg doctor
npx ctg build

With buildRoot set, ctg build runs a single stellar contract build from the workspace root and then checks each configured wasm path. Doctor makes no RPC calls; pass --source <alias> to also check that the identity exists in your Stellar CLI keystore.

Step 5: Deploy the graph

npx ctg deploy --network testnet --source alice

Caatinga deploys in dependency order (token, then vault with the resolved token ID), records both in caatinga.artifacts.json, generates bindings into frontend/src/contracts/<name>/, runs postDeploy hooks, and writes the env file:

VITE_TOKEN_ID=CDW7…6K2M
VITE_VAULT_ID=CCBL…E3V6
VITE_NETWORK=testnet

Then confirm the state:

npx ctg status --network testnet          # local artifacts + binding freshness
npx ctg read vault.token --network testnet # on-chain read: returns the token contract ID
npx ctg inspect vault --network testnet    # On-chain: reachable, Local WASM: matches artifact

Generating bindings adds buffer (a browser polyfill the bindings need) to the nearest package.json above bindingsOutput — here frontend/package.json. Review that diff before committing.

Step 6: Retire the old script

Replace the hand-written deploy script with Caatinga commands, for example in package.json:

{
  "scripts": {
    "contracts:build": "ctg build",
    "contracts:deploy": "ctg deploy --network testnet --source alice",
    "contracts:status": "ctg status --network testnet"
  }
}

stellar-album kept its bootstrap.sh only as a deprecation shim that execs caatinga deploy.

Commit caatinga.config.ts, caatinga.artifacts.json, and the generated bindings. Whether to commit the env file is up to you — ctg sync-env --network <name> rewrites it from the artifacts at any time, keeping unrelated variables.

Contracts already deployed?

Caatinga does not import contract IDs deployed by other tools: there is no command to adopt an existing deployment, and the artifacts file should not be edited by hand. On testnet, the simplest path is a fresh ctg deploy that Caatinga tracks from then on. For mainnet contracts you need to keep, plan the switch deliberately — future upgrades and redeploys are recorded in caatinga.artifacts.json history only for deployments Caatinga made.

Next steps