Step-by-step procedures for deploying, verifying, and rolling back MyFans Soroban contracts on Stellar.
- Prerequisites
- One-time identity setup
- Build and validate WASM artifacts
- Deploy to testnet
- Deploy to mainnet
- Post-deploy verification
- Wire contract IDs into the backend and frontend
- Rollback procedure
- CI dry-run (non-interactive)
- Troubleshooting
- Checklist
| Tool | Minimum version | Install |
|---|---|---|
| Rust (stable) | 1.75+ | rustup update stable |
wasm32-unknown-unknown target |
— | rustup target add wasm32-unknown-unknown |
stellar-cli |
latest stable | cargo install --locked stellar-cli |
xxd or od |
any | pre-installed on Linux/macOS |
| Node.js | 18+ | for backend env wiring |
Verify:
stellar --version
rustc --version
rustup target list --installed | grep wasm32The deploy script requires a named Stellar identity (--source). Create it once per machine/environment.
# Generate a new key pair and fund it via friendbot
stellar keys generate myfans-deployer --network testnet --fund
# Confirm the public key
stellar keys public-key myfans-deployerAuto-generation and friendbot funding are disabled on mainnet. You must supply a pre-funded account.
# Import an existing secret key
stellar keys add myfans-deployer-mainnet --secret-key "<SECRET_KEY>"
# Confirm
stellar keys public-key myfans-deployer-mainnetSecurity: Never commit secret keys. Store them in your CI secret manager (e.g. GitHub Actions secrets, AWS Secrets Manager) and import them at deploy time.
# In the CI job, import the key from a secret before calling the deploy script
stellar keys add myfans-deployer --secret-key "$STELLAR_SECRET_KEY"Always build and validate before deploying. The --dry-run flag does this without submitting any transactions.
# From repository root
./contract/scripts/deploy.sh \
--network testnet \
--source myfans-deployer \
--dry-runExpected output:
[deploy] *** DRY-RUN MODE — no transactions will be submitted ***
[deploy] network=testnet
[deploy] building contracts
[deploy] validating WASM artifacts
[deploy] verified: target/wasm32-unknown-unknown/release/myfans_token.wasm
[deploy] verified: target/wasm32-unknown-unknown/release/creator_registry.wasm
[deploy] verified: target/wasm32-unknown-unknown/release/subscription.wasm
[deploy] verified: target/wasm32-unknown-unknown/release/content_access.wasm
[deploy] verified: target/wasm32-unknown-unknown/release/earnings.wasm
[deploy] dry-run passed — build and config are valid
If any artifact is missing or invalid the script exits non-zero with a clear error.
./contract/scripts/deploy.sh \
--network testnet \
--source myfans-deployer \
--no-fund \
--out contract/deployed-testnet.json \
--env-out contract/.env.deployed-testnetThe script:
- Adds the
testnetnetwork profile to the local stellar CLI config (idempotent). - Builds all five contracts (
myfans-token,creator-registry,subscription,content-access,earnings). - Verifies each WASM binary (magic bytes check).
- Deploys contracts in dependency order (token first, then registry, then subscription/content-access which depend on token, then earnings).
- Initializes each contract with the deployer as admin.
- Runs smoke tests (view calls) to confirm each contract responds correctly.
- Writes
deployed-testnet.jsonand.env.deployed-testnet.
| File | Contents |
|---|---|
contract/deployed-testnet.json |
Contract IDs, network metadata, smoke-test results |
contract/.env.deployed-testnet |
Shell-sourceable env vars for backend/frontend wiring |
Both files are gitignored. Copy the relevant values into your environment's secret manager or .env files.
⚠️ Mainnet deploys are irreversible. Always run a full testnet deploy and verification first.
./contract/scripts/deploy.sh \
--network mainnet \
--source myfans-deployer-mainnet \
--non-interactive \
--no-fund \
--out contract/deployed-mainnet.json \
--env-out contract/.env.deployed-mainnetAdditional mainnet precautions:
- Confirm the deployer account has sufficient XLM for all deploy + init transactions (estimate ~5–10 XLM per contract).
- Use
--non-interactiveto prevent any interactive prompts in production pipelines. - Record the output JSON in a secure artifact store immediately after deploy.
The deploy script runs smoke tests automatically. You can also verify manually:
# Source the deployed env
source contract/.env.deployed-testnet
# Check token admin
stellar contract invoke \
--id "$CONTRACT_ID_MYFANS_TOKEN" \
--network testnet \
--source myfans-deployer \
--send no \
-- admin
# Check subscription is not paused
stellar contract invoke \
--id "$CONTRACT_ID_SUBSCRIPTION" \
--network testnet \
--source myfans-deployer \
--send no \
-- is-paused
# Check content-access has-access (should return false for a new account)
stellar contract invoke \
--id "$CONTRACT_ID_CONTENT_ACCESS" \
--network testnet \
--source myfans-deployer \
--send no \
-- has-access \
--buyer "$(stellar keys public-key myfans-deployer)" \
--creator "$(stellar keys public-key myfans-deployer)" \
--content-id 1
# Check earnings admin
stellar contract invoke \
--id "$CONTRACT_ID_EARNINGS" \
--network testnet \
--source myfans-deployer \
--send no \
-- adminAll commands should return without error. is-paused should return false; has-access should return false.
After a successful deploy, copy the contract IDs from the output env file into your application environments.
# Copy canonical IDs from the deploy output
source contract/.env.deployed-testnet
# Add to backend/.env (or your secret manager)
echo "CONTRACT_ID_MYFANS_TOKEN=$CONTRACT_ID_MYFANS_TOKEN" >> backend/.env
echo "CONTRACT_ID_CREATOR_REGISTRY=$CONTRACT_ID_CREATOR_REGISTRY" >> backend/.env
echo "CONTRACT_ID_SUBSCRIPTION=$CONTRACT_ID_SUBSCRIPTION" >> backend/.env
echo "CONTRACT_ID_CONTENT_ACCESS=$CONTRACT_ID_CONTENT_ACCESS" >> backend/.env
echo "CONTRACT_ID_EARNINGS=$CONTRACT_ID_EARNINGS" >> backend/.env
echo "STELLAR_NETWORK=$STELLAR_NETWORK" >> backend/.env
echo "SOROBAN_RPC_URL=$STELLAR_RPC_URL" >> backend/.envSee contract/docs/DEPLOYED_ENV.md for the full variable reference and legacy alias mapping.
source contract/.env.deployed-testnet
echo "NEXT_PUBLIC_SUBSCRIPTION_CONTRACT_ID=$CONTRACT_ID_SUBSCRIPTION" >> frontend/.env.local
echo "NEXT_PUBLIC_MYFANS_TOKEN_CONTRACT_ID=$CONTRACT_ID_MYFANS_TOKEN" >> frontend/.env.local
echo "NEXT_PUBLIC_CREATOR_REGISTRY_CONTRACT_ID=$CONTRACT_ID_CREATOR_REGISTRY" >> frontend/.env.local
echo "NEXT_PUBLIC_CONTENT_ACCESS_CONTRACT_ID=$CONTRACT_ID_CONTENT_ACCESS" >> frontend/.env.local
echo "NEXT_PUBLIC_EARNINGS_CONTRACT_ID=$CONTRACT_ID_EARNINGS" >> frontend/.env.local
echo "NEXT_PUBLIC_STELLAR_NETWORK=$STELLAR_NETWORK" >> frontend/.env.localSoroban contracts are immutable once deployed — you cannot modify or delete a deployed contract. "Rollback" means deploying a new version and updating the contract IDs in your application config.
-
Identify the previous known-good WASM. Check git history or your artifact store for the last passing CI build's WASM artifacts.
-
Build the previous version:
git checkout <previous-good-commit> -- contract/ ./contract/scripts/deploy.sh --network testnet --source myfans-deployer --dry-run
-
Deploy the previous version (same as a normal deploy — this creates new contract instances):
./contract/scripts/deploy.sh \ --network testnet \ --source myfans-deployer \ --no-fund \ --out contract/deployed-rollback.json \ --env-out contract/.env.deployed-rollback
-
Update application config with the new (rollback) contract IDs following step 7.
-
Redeploy the backend and frontend so they point to the rollback contract instances.
-
Verify using the smoke tests in step 6.
Note: Any on-chain state (subscriptions, balances, etc.) in the broken contract instances is not automatically migrated. Coordinate with the team on data migration if needed.
The GitHub Actions contract-ci.yml workflow builds and verifies WASM artifacts on every PR. For full deploy validation in CI:
- name: Import deployer identity
run: stellar keys add myfans-deployer --secret-key "${{ secrets.STELLAR_SECRET_KEY }}"
- name: Dry-run deploy (build + WASM validation only)
run: |
./contract/scripts/deploy.sh \
--network testnet \
--source myfans-deployer \
--non-interactive \
--dry-run
working-directory: .The --dry-run flag builds all contracts, validates WASM magic bytes, and exits 0 without submitting any transactions. This is safe to run on every PR.
Install stellar-cli:
cargo install --locked stellar-cliThe deploy script requires the identity to exist before running in non-interactive mode. Import it first:
stellar keys add myfans-deployer --secret-key "$STELLAR_SECRET_KEY"The build step failed or the package name is wrong. Run manually:
cargo build --release --target wasm32-unknown-unknown --manifest-path contract/Cargo.toml
ls contract/target/wasm32-unknown-unknown/release/*.wasmThe WASM file exists but is corrupt or truncated. Clean and rebuild:
cargo clean --manifest-path contract/Cargo.toml
cargo build --release --target wasm32-unknown-unknown --manifest-path contract/Cargo.tomlThe deployed contract does not expose the expected method. Confirm you built from the correct package and that the interface matches the ABI snapshot:
./contract/scripts/snapshot-abi.sh
./contract/scripts/check-interface-docs-drift.mjsFund the deployer account before deploying. Each contract deploy + contract invoke transaction costs a small amount of XLM in fees and storage rent.
This is harmless. The script adds the network profile idempotently; if it already exists the warning can be ignored.
Use this checklist for every production deploy:
Pre-deploy
-
stellar --versionshows the expected version - Deployer identity exists:
stellar keys public-key myfans-deployer-mainnet - Deployer account is funded (mainnet only)
- Dry-run passes:
./contract/scripts/deploy.sh --network mainnet --dry-run - All contract tests pass in CI
- ABI snapshots are up to date:
./contract/scripts/snapshot-abi.sh
Deploy
- Deploy script exits 0
-
deployed-mainnet.jsonwritten and archived -
.env.deployed-mainnetwritten and stored in secret manager
Post-deploy
- Smoke tests pass (token admin, subscription is-paused, content-access has-access, earnings admin)
- Backend
.envupdated with new contract IDs - Frontend
.envupdated with new contract IDs - Backend redeployed and health check passes:
GET /v1/health - Frontend redeployed and connects to correct contracts
- Contract IDs recorded in team runbook / incident log
Rollback readiness
- Previous known-good WASM artifacts archived
- Rollback procedure tested on testnet at least once