This document covers end-to-end deployment of smile4money contracts to Stellar testnet and mainnet.
Every CI build on the master branch uploads the compiled WASM binaries as a downloadable artifact. This allows you to verify that a deployed contract matches a specific commit's source code.
- Navigate to the Actions tab on GitHub.
- Select the CI run for the commit you want to verify.
- In the Artifacts section, download the
wasm-<commit-sha>archive. - Extract the archive to get the
.wasmfiles.
Compare the WASM hash from the artifact against the deployed contract:
# Download and extract the artifact for the target commit
unzip wasm-<sha>.zip -d wasm-artifact
# Compute the hash of the local artifact
sha256sum wasm-artifact/*.wasm
# On-chain: use stellar contract inspect to get the contract's WASM hash
stellar contract inspect \
--id <CONTRACT_ID> \
--network mainnet \
--rpc-url https://soroban-mainnet.stellar.org \
--network-passphrase "Public Global Stellar Network ; September 2015"The WASM hash displayed by stellar contract inspect should match the sha256sum output for the corresponding artifact file, confirming the deployed bytecode matches the source at that commit.
- Rust toolchain (1.70+) with
wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown
- Stellar CLI — install from the official docs
- Stellar deployer account funded with XLM (testnet XLM is free via Friendbot; mainnet requires real XLM)
gitcheckout of the repository at the commit to deploy
stellar keys generate deployer --network testnetThis creates a local keypair named deployer and registers it for the testnet network. To view the public address:
stellar keys address deployerIf the account has not been used before, it needs a minimum XLM balance. The deploy script funds it automatically via Friendbot, but you can also fund it manually:
curl -sf "https://friendbot.stellar.org?addr=$(stellar keys address deployer)"Expected output:
{"message": "Account created!", "hash": "..."}./scripts/deploy_testnet.shThe script performs these steps in order:
- Verifies the
stellarCLI is installed and thedeployeridentity exists. - Funds the deployer via Friendbot (no-op if already funded).
- Builds both contracts (
escrow.wasm,oracle.wasm) in release mode. - Deploys the escrow contract to testnet.
- Deploys the oracle contract to testnet.
- Initializes the oracle contract (admin = deployer address).
- Initializes the escrow contract (oracle = oracle contract address, admin = deployer address).
- Writes
CONTRACT_ESCROWandCONTRACT_ORACLEto.env.
Expected output:
Deployer: GABCDEF123...
Funding deployer account via friendbot...
Building contracts...
Deploying escrow contract...
Escrow contract: CC123...
Deploying oracle contract...
Oracle contract: CC456...
Initializing oracle contract...
Initializing escrow contract...
Deployment complete.
Escrow: CC123...
Oracle: CC456...
Contract IDs written to .env
Check that contract IDs were written to .env:
grep -E "^(CONTRACT_ESCROW|CONTRACT_ORACLE)=" .envVerify the contracts are live and responsive:
# Check escrow contract is queryable (should return match count 0)
stellar contract invoke \
--id "$(grep CONTRACT_ESCROW .env | cut -d= -f2)" \
--source deployer \
--network testnet \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015" \
-- get_match \
--match_id 0
# Check oracle contract is queryable
stellar contract invoke \
--id "$(grep CONTRACT_ORACLE .env | cut -d= -f2)" \
--source deployer \
--network testnet \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015" \
-- has_result \
--match_id 0If the contracts are deployed correctly, has_result returns false and get_match returns Error::MatchNotFound (the contract is live and rejecting invalid queries properly).
After deployment, populate the remaining .env fields:
STELLAR_NETWORK=testnet
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
CONTRACT_ESCROW=<contract-id-from-deploy>
CONTRACT_ORACLE=<contract-id-from-deploy>
LICHESS_API_TOKEN=<your-lichess-api-token>
CHESSDOTCOM_API_KEY=<your-chessdotcom-api-key>
VITE_STELLAR_NETWORK=testnet
VITE_STELLAR_RPC_URL=https://soroban-testnet.stellar.orgThese values are used by the off-chain oracle service and the frontend.
Mainnet deployment follows the same steps but with additional precautions because transactions are irreversible and consume real XLM.
- All CI jobs pass on the commit being deployed (test, clippy, fmt, build).
- Security audit (
cargo audit) reports zero unresolved advisories. - Contracts have been live on testnet for at least one full test cycle.
- A dedicated deployer key is used — never a personal or shared key.
- The deployer account is funded with sufficient XLM to cover deploy + init fees (approximately 10–20 XLM).
- The commit to deploy has been reviewed and approved by at least one other team member.
- A rollback plan exists (redeploying previous contract IDs).
- The
.envfile from the previous testnet deploy is backed up separately (not overwritten).
stellar keys generate deployer --network mainnetSend real XLM to the deployer address:
stellar keys address deployer
# Send XLM to the output address from a funded Stellar account or exchangeThe account needs enough for:
- Escrow contract deploy fee
- Oracle contract deploy fee
- Two initialization invocations
- Minimum account balance (~1 XLM)
10–20 XLM is a comfortable buffer.
./scripts/deploy_mainnet.shThe script is identical to the testnet version except:
- Targets
mainnetnetwork and RPC endpoints. - Does not call Friendbot (no Friendbot exists for mainnet).
- Prompts for confirmation before proceeding.
- Updates all
STELLAR_NETWORKandVITE_STELLAR_NETWORKfields in.envtomainnet.
Expected output:
Deployer: GXYZ...
Network: mainnet (PUBLIC — real XLM will be spent)
Continue with mainnet deployment? [y/N] y
Building contracts...
Deploying escrow contract...
Escrow contract: CC789...
Deploying oracle contract...
Oracle contract: CC012...
Initializing oracle contract...
Initializing escrow contract...
Mainnet deployment complete.
Escrow: CC789...
Oracle: CC012...
Contract IDs written to .env
Run the same verification as testnet (step 3 above), but use mainnet RPC and passphrase:
MAINNET_RPC="https://soroban-mainnet.stellar.org"
MAINNET_PASSPHRASE="Public Global Stellar Network ; September 2015"
stellar contract invoke \
--id "$CONTRACT_ESCROW" \
--source deployer \
--network mainnet \
--rpc-url "$MAINNET_RPC" \
--network-passphrase "$MAINNET_PASSPHRASE" \
-- get_match \
--match_id 0
stellar contract invoke \
--id "$CONTRACT_ORACLE" \
--source deployer \
--network mainnet \
--rpc-url "$MAINNET_RPC" \
--network-passphrase "$MAINNET_PASSPHRASE" \
-- has_result \
--match_id 0Additionally:
- Record contract IDs in a secure, durable location (e.g., a password manager, team wiki, or a GitHub release).
- Verify the admin and oracle addresses stored on-chain are correct with
stellar contract inspect. - Run a smoke test: create a test match, deposit stake, and cancel it to verify the full flow.
- Update the frontend configuration with the new mainnet contract IDs and network.
The Stellar CLI is not installed or not in PATH.
Fix: Install from https://developers.stellar.org/docs/tools/developer-tools/cli/install-cli
The deployer keypair has not been created yet.
Fix:
stellar keys generate deployer --network testnetThe deployer account has no XLM balance.
Fix (testnet): The deploy script funds via Friendbot automatically. If it fails, run manually:
curl -sf "https://friendbot.stellar.org?addr=$(stellar keys address deployer)"Fix (mainnet): Send real XLM to the deployer address from a funded account.
The WASM build failed or produced output in an unexpected location.
Fix: Run the build separately to see errors:
cargo build --target wasm32-unknown-unknown --releaseCommon causes:
- Missing
wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown - Rust toolchain too old:
rustup update - Compilation errors in contract code
One of the contracts was already initialized. This happens if the deploy script was run twice without deploying new contracts.
Fix: Deploy fresh contracts (the script deploys new instances each run). If you want to re-use existing contracts, skip initialization and just update .env with the existing IDs.
The contract invocation succeeded but the response format was unexpected. This usually means the contract is live and responding — check that the function name and arguments match the contract interface.
Fix: Verify you are using the correct function name and parameter types. See docs/api-reference.md for the full contract API.
Network congestion or RPC endpoint issues.
Fix: Retry the command. If the problem persists, verify the RPC endpoint is healthy:
curl -s <rpc-url> | head -20You can also increase timeouts with the --timeout flag on stellar commands.