The deployment suite turns the repository's contract configuration into an auditable, dependency-ordered Stellar deployment. It is the canonical path for testnet, standalone, and mainnet deployments.
The suite:
- validates every local WASM before submitting a transaction;
- uploads each artifact explicitly and requires the returned WASM hash to equal the local SHA-256;
- deploys contracts in dependency order;
- distinguishes constructor contracts from contracts that require a separate
initializeinvocation; - writes a resumable checkpoint after every successful remote operation;
- verifies deployed code hashes, contract metadata, registry links, and configured read calls;
- updates
deploy-manifest.jsonandfrontend/.envatomically only after every verification check passes; and - emits a machine-readable verification report for CI and audit retention.
No secret key, seed phrase, or RPC authentication header is accepted by the configuration schema or written to a manifest. Signing remains delegated to a Stellar CLI identity.
- Python 3.10 or newer (standard library only)
- Stellar CLI
- Rust and the repository's configured WASM target
- A funded Stellar identity for the selected network
The current Stellar CLI command and option reference is maintained in the Stellar CLI manual.
Create and fund an identity, then use the existing operator entry point:
bash scripts/setup-identity.sh veritoken-dev
bash scripts/deploy.sh veritoken-devscripts/deploy.sh still builds and validates the release artifacts first. It
then delegates upload, deployment, registration, and verification to the Python
orchestrator.
The default development config is
deployment/config.testnet.json. Its metadata values are valid contract
fixtures rather than rejected zero-value or empty placeholders.
deployment/examples/ contains a runnable,
single-asset-type bundle for each asset type (invoice, property, carbon
credit) — a smaller starting point than the combined testnet config above
when you only need one asset type. See
deployment/examples/README.md for the
common configuration points across bundles and how to adapt one to a real
environment and contract ID set.
Print and validate the operation plan without contacting Stellar:
python3 scripts/deployment_cli.py plan \
--config deployment/config.testnet.json \
--network testnetDeploy prebuilt artifacts:
DEPLOY_SKIP_BUILD=1 bash scripts/deploy.sh veritoken-devResume an interrupted deployment:
DEPLOY_RESUME=1 bash scripts/deploy.sh veritoken-devRe-run verification without redeploying:
bash scripts/verify-deployment.sh veritoken-devThe Python entry point exposes the same operations directly:
python3 scripts/deployment_cli.py deploy --help
python3 scripts/deployment_cli.py verify --help
python3 scripts/deployment_cli.py validate-manifest --help
python3 scripts/deployment_cli.py simulate-upgrade --helpSoroban contracts here are immutable — an "upgrade" is a full
snapshot-and-redeploy (see docs/incident-response.md
§4), which is expensive to get wrong. simulate-upgrade models the outcome
of a candidate upgrade offline, before you spend a transaction on it:
python3 scripts/deployment_cli.py simulate-upgrade \
--manifest deploy-manifest.json \
--contract compliance_engine \
--new-artifact target/wasm32-unknown-unknown/release/compliance_engine.wasm \
--to-schema-version 2It diffs the candidate WASM's exported function interface against the
currently deployed artifact (recorded in --manifest) and flags any
function that's present today but missing from the new build as a critical
risk — that's a breaking change for the frontend, SDK, or any other
contract that calls it. If --to-schema-version is given, it also checks
that value against the contract's own sequential-migration rule
(to_version == current + 1, enforced by migrate_schema on-chain).
By default the schema-version check is skipped (there's no live state to
compare against without a network call). Pass --identity to additionally
perform a read-only schema_version invoke against the deployed
contract and validate sequencing against live state — this never mutates
anything on-chain:
python3 scripts/deployment_cli.py simulate-upgrade \
--manifest deploy-manifest.json \
--contract compliance_engine \
--new-artifact target/wasm32-unknown-unknown/release/compliance_engine.wasm \
--to-schema-version 2 \
--identity veritoken-dev --network testnetThe command exits non-zero and writes a JSON report (default
upgrade-simulation-report.json, override with --report) whenever a
critical risk is found — wire it into a pre-upgrade checklist or CI step
the same way verify is used after a deploy.
A deployment config has a profile and a set of contract declarations:
{
"schema_version": 1,
"profile": "development",
"contracts": [
{
"name": "kyc_registry",
"artifact": "${wasm_dir}/kyc_registry.wasm",
"env_key": "VITE_KYC_REGISTRY_ID",
"dependencies": [],
"deployment_mode": "initialize",
"initialize_function": "initialize",
"initialize_args": {
"admin": "${admin}"
},
"declared_metadata": {
"package": "kyc-registry"
},
"health_check": {
"function": "verifier_count",
"expectation": "integer"
}
}
]
}Supported templates are:
${admin}and${source_account}: the public address resolved from the selected Stellar identity;${contract.NAME}: the contract ID produced for a named dependency; and${wasm_dir}: the artifact directory passed to the CLI.
Templates can appear in nested constructor or initializer metadata. Unknown templates fail before the affected operation is submitted.
deployment_mode: "constructor" passes constructor_args after the Stellar
CLI -- separator. A successful deploy is immediately considered initialized.
deployment_mode: "initialize" deploys the WASM instance with no constructor
arguments and then invokes initialize_function with initialize_args.
Checkpoints distinguish uploaded, deployed, and initialized stages, so an
initializer failure can be resumed without deploying another instance.
This distinction is required by the current suite: the KYC registry and compliance engine expose initializer functions, while the asset contracts use deploy-time constructors.
The orchestrator topologically sorts contract declarations. Unknown dependencies, self-dependencies, and cycles are rejected before a deployment transaction. Resolved dependency IDs are stored in each contract record and checked again during verification.
Mainnet aliases (mainnet and public) require a configuration whose
profile is production. Placeholder markers such as <release-tag>,
PLACEHOLDER, TBD, and replace-me are rejected before Stellar CLI is
called.
Start from deployment/config.mainnet.example.json, expand it with the asset
contracts required for the release, replace every placeholder, review the
result, and print the plan:
cp deployment/config.mainnet.example.json deployment/config.mainnet.json
python3 scripts/deployment_cli.py plan \
--config deployment/config.mainnet.json \
--network mainnetThe unedited example intentionally fails the mainnet placeholder gate.
A deployment proceeds through these phases:
- Parse the config and validate the dependency graph.
- Preflight every artifact's path, WASM magic, SHA-256, and local contract metadata before the first network mutation.
- Resolve the public source account.
- Upload each WASM and compare the network-returned hash with the local hash.
- Deploy and initialize contracts in dependency order.
- Verify each local artifact, deployed code hash, local/deployed contract metadata, dependency registry link, and configured health call.
- Atomically publish the canonical manifest and merge contract IDs into
frontend/.env.
The previous canonical manifest and frontend environment remain untouched until phase 6 passes in full.
The in-progress checkpoint is deploy-manifest.partial.json by default. It is
rewritten atomically after each successful upload, deployment, or initializer.
On failure:
- the previous
deploy-manifest.jsonremains canonical; frontend/.envremains unchanged;- the verification report is retained when verification ran; and
- the partial checkpoint records exactly how far the new run progressed.
Resume only with the same identity, public source address, network, config hash, and local artifacts:
DEPLOY_RESUME=1 bash scripts/deploy.sh veritoken-devBefore reusing a deployed checkpoint record, the orchestrator queries its on-chain WASM hash. A changed identity, config, artifact, metadata hash, network, or deployed code hash aborts the resume instead of mixing runs.
deploy-manifest.json includes:
- schema version, deployment ID, generation, and timestamps;
- previous manifest SHA-256 for registry lineage;
- network selector and public source account;
- config path, profile, and hash;
- local artifact path, size, SHA-256, and contract metadata hash;
- uploaded WASM hash and deployed contract ID;
- constructor or initializer mode and resolved dependency IDs;
- completed lifecycle stage and operation timestamps;
- canonical contract-name-to-ID registry; and
- compatibility keys consumed by the existing drift checker.
The manifest deliberately excludes secret material. Contract IDs, public account addresses, WASM hashes, and transaction-independent metadata are public deployment evidence.
deployment-verification-report.json records one result per check with the
expected value, actual value, pass/fail status, deployment ID, manifest hash,
network, and aggregate totals.
Verification fails if any of these diverge:
- local artifact SHA-256 versus the manifest;
- uploaded/deployed WASM hash versus the local artifact;
- local versus deployed contract metadata hash;
- a dependency's recorded ID versus the canonical registry;
- a configured health call's output contract; or
- the existence and format of a referenced artifact.
The verifier uses stellar contract info hash for deployed code identity and
stellar contract info meta for declared contract metadata. It does not infer
success from a responsive contract ID alone.
| Variable | Purpose |
|---|---|
STELLAR_NETWORK |
Stellar CLI network alias; default testnet |
STELLAR_RPC_URL |
Custom RPC URL; requires STELLAR_NETWORK_PASSPHRASE |
STELLAR_NETWORK_PASSPHRASE |
Passphrase paired with a custom RPC URL |
STELLAR_BIN |
Alternate Stellar CLI executable |
PYTHON_BIN |
Alternate Python 3 executable |
WASM_DIR |
Release artifact directory |
DEPLOY_CONFIG |
Deployment config path |
DEPLOY_MANIFEST |
Canonical manifest path |
DEPLOY_VERIFICATION_REPORT |
Verification report path |
FRONTEND_ENV_FILE |
Frontend environment file to merge |
DEPLOY_SKIP_BUILD=1 |
Reuse already-built artifacts |
DEPLOY_RESUME=1 |
Resume the matching partial checkpoint |
The suite uses only Python's standard library:
PYTHONPATH=scripts python3 -m unittest discover \
-s scripts/deployment/tests -vFixture tests cover dependency ordering, constructor and initializer paths, atomic registry publication, interrupted-run recovery, missing artifacts, remote code-hash drift, metadata mismatch, mainnet gating, template resolution, and CLI argument encoding.