This runbook captures the Stage 2 testnet deployment path for the Soroban Smart Block stack. It is written so a team member can redeploy, debug, or upgrade the testnet setup from a clean checkout.
Install and verify these tools before starting:
- Rust stable toolchain with the WebAssembly target:
rustup target add wasm32-unknown-unknown. - Stellar CLI 23.x or newer:
stellar --version. - Node.js 20 LTS or newer:
node --versionandnpm --version. - A Stellar testnet deployer/admin account.
- Network access to the Stellar testnet RPC endpoint.
Use a dedicated testnet secret for deployment work. Never reuse a mainnet secret in local shells, CI logs, or screenshots.
Create a local environment file and keep secrets out of git:
cp .env.example .envSet or map these values to the names used by the contract, indexer, and frontend packages:
STELLAR_NETWORK=testnet
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
STELLAR_PASSPHRASE="Test SDF Network ; September 2015"
STELLAR_ADMIN_SECRET=<testnet-secret-key>
STELLAR_ADMIN_PUBLIC=<testnet-public-key>
CONTRACT_ID=<filled-after-deploy>Install dependencies and build the contract package:
npm install
cargo build --release --target wasm32-unknown-unknownIf the contract lives in a workspace package, run the Cargo command from that package directory. Confirm that a release WASM artifact exists before deployment:
find . -path "*target/wasm32-unknown-unknown/release/*.wasm"Configure the CLI and fund the deployer account if needed:
stellar network add testnet --rpc-url "$STELLAR_RPC_URL" --network-passphrase "$STELLAR_PASSPHRASE"
stellar keys add deployer --secret-key "$STELLAR_ADMIN_SECRET"
stellar keys fund deployer --network testnetDeploy the WASM artifact:
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/soroban_smart_block.wasm \
--source deployer \
--network testnetCopy the returned contract id:
export CONTRACT_ID=<returned-contract-id>Run the project initialization entrypoint with the testnet admin account. Replace function and argument names with the concrete names used by the current contract if they differ.
stellar contract invoke \
--id "$CONTRACT_ID" \
--source deployer \
--network testnet \
-- \
initialize \
--admin "$STELLAR_ADMIN_PUBLIC"Verify initialized state with a read-only call, for example:
stellar contract invoke --id "$CONTRACT_ID" --source deployer --network testnet -- get_adminThe admin account must have enough testnet XLM for initialization, upgrades, and operational calls.
stellar keys fund deployer --network testnet
stellar keys address deployerIf a separate admin key is used, fund that key as well.
Create the indexer environment file and point it at the deployed contract:
cd indexer
cp .env.example .envSet:
DATABASE_URL=<postgres-connection-string>
STELLAR_NETWORK=testnet
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
STELLAR_PASSPHRASE="Test SDF Network ; September 2015"
CONTRACT_ID=<deployed-contract-id>
START_LEDGER=<deployment-ledger-or-earlier>Install dependencies, run migrations, and start the daemon:
npm install
npm run migrate
npm run startLeave the daemon running and confirm it advances through ledgers without repeatedly retrying the same event range.
Create the frontend environment file:
cd frontend
cp .env.example .env.localSet:
NEXT_PUBLIC_STELLAR_NETWORK=testnet
NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org
NEXT_PUBLIC_CONTRACT_ID=<deployed-contract-id>
NEXT_PUBLIC_INDEXER_URL=<indexer-api-url>Install, build, and serve:
npm install
npm run build
npm run startFor local validation during development, use npm run dev and verify that the app can read the deployed contract id and display indexed testnet data.
- Contract WASM builds from a clean checkout.
- Contract id is recorded in the shared testnet environment.
- Initialization succeeds exactly once.
- Admin account is funded and can submit operational transactions.
- Indexer connects to the testnet RPC endpoint.
- Database migrations complete successfully.
- Indexer daemon processes ledgers beyond the deployment ledger.
- Frontend builds with the testnet contract id.
- Frontend reaches the indexer API.
- A second person can follow this document without private context.
Re-add the network and confirm the passphrase is testnet.
Fund the testnet key again with stellar keys fund deployer --network testnet.
Confirm the Rust target is installed and rebuild with cargo build --release --target wasm32-unknown-unknown.
Check that CONTRACT_ID, STELLAR_NETWORK, STELLAR_RPC_URL, and STELLAR_PASSPHRASE all point to testnet. A mainnet passphrase with a testnet RPC endpoint will fail.
Check the database migration state, START_LEDGER, and any persisted cursor table. Restart only after confirming the cursor is not ahead of the testnet ledger.
Confirm the frontend uses the same contract id as the indexer and that browser-accessible API URLs are not private Docker hostnames.
After contract code changes, rebuild the WASM artifact:
cargo build --release --target wasm32-unknown-unknownUpload the new WASM and invoke the project upgrade entrypoint if the contract exposes one:
stellar contract upload \
--wasm target/wasm32-unknown-unknown/release/soroban_smart_block.wasm \
--source deployer \
--network testnet
stellar contract invoke \
--id "$CONTRACT_ID" \
--source deployer \
--network testnet \
-- \
upgrade \
--wasm-hash <uploaded-wasm-hash>After the upgrade, re-run smoke checks, restart the indexer if event schemas changed, rebuild the frontend if generated bindings changed, and record the new WASM hash, contract id, deployer, and deployment ledger in release notes.