Configuration
Every Stellar Scaffold project is configured by one file at its root: scaffold.yml. It says where your contracts and generated clients live, which Stellar networks you build for, which accounts exist on each, and which contracts to deploy or connect to on each network.
version: 2
project:
contracts-dir: contracts
clients-dir: app-lib/clients
networks:
local:
accounts: [me]
testnet:
accounts: [testnet-user]
allow-deploy: true
contracts:
guess-the-number:
type: workspace
source: guess-the-number
args:
admin: ${account.me}
after-deploy: [reset]
networks:
local:
testnet:
args:
admin: ${account.testnet-user}
Machine-local settings and secrets stay out of this file. They go in .env, which is gitignored and also read by your frontend.
Projects created before scaffold.yml version 2 use environments.toml instead. build and watch still read it as long as the project has an environments.toml and no version 2 scaffold.yml. See Legacy: environments.toml below.
Choosing a network
build and watch work against one network at a time. They pick it in this order:
--network <name>, or theSTELLAR_NETWORKenvironment variable- The default set with
stellar network use <name> local
The name must be declared under networks:. New projects set STELLAR_NETWORK=local in .env; switch it to testnet to build against testnet. Keep the PUBLIC_STELLAR_* values in .env pointed at the same network, since those are what your frontend connects to.
Generated clients are bound to the network they were built for: its passphrase and RPC URL are written into <clients-dir>/index.ts. Rebuild after switching networks.
STELLAR_SCAFFOLD_ENV is ignored with a version 2 scaffold.yml. The CLI warns if it is set.
version
Required, and must be 2. The CLI checks it before anything else, so a file written for a different schema version gets one clear error instead of a list of unknown keys.
project
Where things live and which package manager builds them. All keys are optional.
| Key | Default | Meaning |
|---|---|---|
contracts-dir | contracts | Where your Rust contract crates live |
clients-dir | app-lib/clients | Where generated clients are written: one package per contract in <clients-dir>/<name>/, plus <clients-dir>/index.ts, which your app imports as @stellar-scaffold/app-lib/clients |
package-manager | npm | Installs and builds the generated clients: npm, pnpm, yarn, bun or deno, optionally pinned to a version (pnpm@9.6.0) that doctor checks. init records the one you choose, with its installed version |
Everything in clients-dir is regenerated on each build, so hand edits are overwritten. To customize a client, import it into your own code instead.
networks
Each key is a network name you choose. Names may contain letters, digits, - and _, and must start with a letter or digit.
networks:
local:
accounts: [me, alice]
default-account: me
testnet:
accounts: [testnet-user]
allow-deploy: true
# A second testnet setup with its own accounts, inheriting testnet's RPC
preview:
extends: testnet
accounts: [preview-user]
mainnet:
rpc-url: https://your-rpc-provider.example
rpc-headers:
Authorization: "Bearer ${env.MAINNET_RPC_TOKEN}"
accounts: [mainnet-user]
A network with no settings can be written as a bare testnet:, which is the same as testnet: {}.
| Key | Default | Meaning |
|---|---|---|
rpc-url | stellar-cli's URL for local, testnet and futurenet | RPC endpoint. Required for mainnet and for any custom name, unless inherited through extends |
network-passphrase | stellar-cli's passphrase for the built-in names | Required for custom names, unless inherited |
rpc-headers | none | Map of HTTP headers sent with every RPC request |
accounts | none | Account aliases Scaffold creates on this network, funding them where it can (Friendbot or the local container) |
default-account | first entry in accounts | Which account signs deploys when a contract sets no signer. Must be listed in accounts |
start-container | true only for the local (standalone) passphrase | Whether build starts a stellar/quickstart container. Set false if a local chain is already running at rpc-url |
allow-deploy | false for the testnet, futurenet and mainnet passphrases; true otherwise | Whether Scaffold may deploy workspace contracts here |
allow-http | true only for the local (standalone) passphrase | Whether generated clients may use a plain http:// rpc-url. config check warns when it is set on a public network |
extends | none | Another declared network to inherit settings from |
allow-deploy, allow-http and start-container defaults come from the network's passphrase, not its name. A custom network pointed at testnet is treated like testnet, and a custom network with the standalone passphrase gets a local container.
extends
A network that extends another starts from the parent's resolved settings and applies its own on top. Scalars override, maps (such as rpc-headers) merge, and lists (such as accounts) replace rather than append. An explicit allow-deploy is inherited; an unset one is recomputed from the child's own passphrase. Chains may be at most three hops deep and may not loop.
Use extends when you need a second configuration on the same chain, for example a preview deployment on testnet with its own accounts.
Interpolation in network settings
rpc-url, network-passphrase and rpc-headers values may use ${env.NAME} to read an environment variable at build time, so secrets such as RPC tokens stay in .env. See Interpolation.
contracts
Each key is the client name: the name of the generated package in <clients-dir>/<name>/ and, in camelCase, the name exported from index.ts (guess-the-number becomes guessTheNumber). Client names are independent of crate names.
contracts:
my-token:
type: workspace
source: fungible-token # the crate's package name
signer: admin
args:
owner: ${account.admin}
initial_supply: 1000000000000000000000000
after-deploy: [unpause]
networks:
local:
testnet:
args:
owner: ${account.testnet-admin}
signer: testnet-admin
mainnet:
type: contract
source: CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC
Keys directly under a contract are defaults for every network it lists. Entries under its networks: override them, using the same merge rules as extends. A per-network entry that changes type must also set source, and the other way round.
A contract only exists on the networks it lists. Building any other network skips it: it is not built, not deployed, and not exported from index.ts. A contract with no networks: is never built, and config check warns about it.
| Key | Meaning |
|---|---|
type | What source refers to. Required. See Contract types |
source | The crate, address, asset or Wasm, in the format type expects. Required |
args | Constructor arguments, as a map from __constructor parameter name to value |
signer | Account that deploys the contract and runs after-deploy. Defaults to the network's default-account |
after-deploy | List of contract methods to call, with no arguments, right after deploying |
after-deploy-script | Path to an executable to run after deploying (not yet supported by build) |
from-network | Copy the Wasm of a contract deployed on another network and deploy it here (not yet supported by build) |
client | Whether to generate a TypeScript client. Defaults to true. With false the contract is still built and deployed, for example as a dependency of other contracts. Only valid at the top level of a contract, so a client is the same on every network |
networks | Per-network entries. Only valid at the top level of a contract |
signer, args, after-deploy and after-deploy-script only apply when Scaffold deploys the contract, which means type: workspace (or from-network). Setting them on a contract that only references an existing deployment is an error.
Contract types
type | source format | What Scaffold does |
|---|---|---|
workspace | A crate's package name in contracts-dir | Builds, deploys and generates a client |
contract | A deployed contract address (C…) | Generates a client bound to that address |
registry | [namespace/]name@version from the Stellar Registry | Generates a client bound to the registered deployment |
asset | CODE:ISSUER, or native | Generates a client for the asset's Stellar Asset Contract |
wasm-file | Path to a local .wasm file | Generates a types-only client, with no contract ID |
wasm-hash | A 64-character hex Wasm hash installed on the network | Generates a types-only client |
wasm-registry | [namespace/]name@version of a Wasm in the Stellar Registry | Generates a types-only client |
build and watch currently support only workspace and contract. The other types pass config check but fail at build time with uses type: <type>, which build does not support yet. The same applies to from-network and after-deploy-script.
registry and wasm-registry sources without an @version are accepted with a warning, because the thing they point at can change under you.
A workspace contract can only be deployed to a network with deploys allowed. To use the same contract on testnet or mainnet, either set allow-deploy: true on that network, or override the entry to point at a deployment you made separately:
networks:
mainnet:
type: contract
source: C... # the deployed contract's ID
allow-deploy: true on a network with the mainnet passphrase is allowed, with a warning.
Constructor arguments
args maps each __constructor parameter, by name, to a value. Values are checked against the contract's spec by stellar-cli when it deploys, so write them as you would on the command line: strings, numbers and booleans as YAML scalars, vectors as lists, and structs or maps as maps.
args:
name: ExampleToken
decimals: 7
admin: ${account.me}
initial_supply: 1000000000000000000000000 # 128-bit values keep full precision
metadata: null # omitted, so an Option parameter becomes None
Integers wider than 64 bits are read exactly, so i128, u128 and 256-bit values do not need quoting. null leaves the argument out. No shell is involved: values are passed straight to stellar-cli, so $(…) is just text.
after-deploy
A list of method names called, in order, with no arguments, signed by the contract's signer. They run only when the contract was actually deployed or upgraded on this build. A build that finds the Wasm unchanged skips them.
after-deploy: [reset, unpause]
Deploying and redeploying
When a workspace contract's Wasm changes, the next build upgrades the existing deployment if the contract supports it, or redeploys it otherwise. Changing only args or after-deploy does not trigger a redeploy. Run stellar scaffold clean to start fresh.
If one contract fails to deploy, the build carries on with the rest, exports what it can, and then exits with an error.
extensions
Extensions run in the order listed, on every network. Each key is an extension name; its value is passed to the extension as its config, which is left out when the value is empty. Keys inside the value are passed exactly as written, so use whatever spelling the extension expects.
extensions:
reporter:
warn_size_kb: 128
my-ext:
Interpolation
String values can reference other values with ${namespace.name}.
| Reference | Value | Allowed in |
|---|---|---|
${env.NAME} | Environment variable NAME at build time; an error if unset | Network settings and args |
${env.NAME:-fallback} | NAME, or fallback when unset | Network settings and args |
${account.NAME} | Address of account NAME, which must be listed in the network's accounts | args |
${network.name}, ${network.rpc-url}, ${network.passphrase} | The selected network's resolved values | args |
Write $${ for a literal ${. The namespaces contract, wasm-hash and registry are reserved for future use and currently rejected.
Validating your config
stellar scaffold config check # validate the whole file
stellar scaffold config check --network testnet # also check that testnet is declared
stellar scaffold config show --network testnet # print the fully resolved config for testnet
config check reports every problem at once, with the line, a short code such as unknown-account or deploy-not-allowed, and a suggested fix. It makes no network calls, so it is safe to run in CI. --strict also fails on warnings, and --json prints diagnostics as JSON.
config show applies extends, fills in built-in defaults, merges per-network contract overrides and substitutes ${env.…} and ${network.…}, so you can see exactly what build will use. ${account.…} references stay as written, since the accounts may not exist yet.
build, watch and stellar scaffold doctor run the same checks.
Environment variables
| Variable | Effect |
|---|---|
STELLAR_NETWORK | Network to build for, same as --network |
STELLAR_SCAFFOLD_ENV | Ignored, with a warning. Only read by projects still on environments.toml |
Anything referenced by ${env.…} | Read at build time |
Legacy: environments.toml
Projects with an environments.toml and no version 2 scaffold.yml keep their networks, accounts and contracts in environments.toml, keyed by environment (development, testing, staging, production) and selected with STELLAR_SCAFFOLD_ENV.
Contracts are read from contracts/ and clients are written to app-lib/clients/, unless a version 1 scaffold.yml beside environments.toml moves them. Only its config: section is read:
version: 1
config:
contracts_dir: contracts
clients_dir: app-lib/clients
[development]
network = { name = "local", run_locally = true }
accounts = ["me"]
[development.contracts.guess_the_number]
client = true
constructor_args = "--admin me"
after_deploy = "reset"
[production.contracts.guess_the_number]
id = "C..."
build and watch still read this format, and --network is ignored for it. To move to version 2:
- Set
version: 2. If your oldscaffold.ymlhas aconfig:section, replace it withproject:, renamingcontracts_dir/clients_dirtocontracts-dir/clients-dir. - Turn each environment into an entry under
networks:.run_locallybecomesstart-container; an explicitdefault = trueaccount becomesdefault-account. - Turn each contract into an entry under
contracts:withtype: workspaceand its crate name assource, ortype: contractand theidassource, and list the networks it belongs on.client = falsebecomesclient: false. - Rewrite
constructor_argsas anargsmap,$(stellar keys address x)as${account.x}, and aSTELLAR_ACCOUNT=prefix assigner. - Rewrite
after_deployas a list of method names. Calls that take arguments are not supported byafter-deployyet. - Move
extensionsinto the top-levelextensions:map. - Replace
STELLAR_SCAFFOLD_ENVin.envwithSTELLAR_NETWORK, then deleteenvironments.toml.
Run stellar scaffold config check when you are done.