This checklist must be completed in full — and signed off by the responsible parties — before any Checkmate-Escrow deployment goes live on Stellar mainnet. Work through each section sequentially. Do not skip items; mark them [N/A] with a justification note if genuinely not applicable.
How to use this checklist
- Copy this file to a private deployment log (e.g. a confidential issue, Notion page, or internal wiki).
- Check off each item as you complete it.
- Record the signer, date, and any relevant artefact (tx hash, commit SHA, audit report link) next to each sign-off block.
- Do not proceed to the next section until the current section is fully checked.
- Pre-Launch: Security Audit
- Key Management & Access Control
- Contract Build & Verification
- Oracle Setup & Rate Limiting
- Token Allowlist Configuration
- Pre-Launch Testing
- Deployment Execution
- Post-Deployment Verification
- Post-Launch Monitoring
- Rollback Procedures
- Sign-Off
All items in this section must be complete before the deployment window opens.
- An independent security audit of
contracts/escrow/srcandcontracts/oracle/srchas been completed by a qualified third-party auditor. - The audit report has been reviewed by the lead developer and all critical and high findings have been remediated or explicitly accepted with written justification.
- Audit findings and mitigations are documented in the deployment log with a link to the audit report.
- The internal Security Audit Checklist has been completed and all items are checked.
- Formal verification checks have passed:
./scripts/check_tla.shexits 0. -
cargo deny checkpasses with no unresolved advisories. - No
TODO,FIXME, orHACKcomments remain in production contract code (contracts/). - All dependency versions in
Cargo.lockhave been reviewed for known CVEs (runcargo audit). - The Threat Model & Security document is up to date and reflects the final architecture.
Audit sign-off
| Role | Name | Date | Artefact |
|---|---|---|---|
| Lead Developer | |||
| Security Auditor | Audit report URL: |
- A dedicated deployer keypair has been generated for the mainnet deployment and is stored in a hardware security module (HSM) or hardware wallet (Ledger / Trezor).
- The deployer keypair is not the same key as the admin or oracle operational keypairs.
- The escrow admin keypair is stored in the HSM / hardware wallet with restricted access (minimum two keyholders required for use).
- The oracle operational keypair is stored in a secrets manager (e.g. AWS Secrets Manager, HashiCorp Vault) and rotated at least every 90 days. Rotation schedule is documented.
- Key access is limited to named, trusted individuals. A key-access registry is maintained and current.
- A key rotation runbook exists and has been tested on testnet. See runbook-rotation.md.
- Emergency contact list for keyholders is documented and accessible to all signers.
- Deployer keypair will be zeroed / archived after contract initialisation is confirmed.
- The release build has been produced from a clean, tagged Git commit on
main:git tag v1.0.0 && git push origin v1.0.0 ./scripts/build.sh - The WASM bytecodes are deterministically reproducible: a second build from the same commit produces byte-identical WASM files.
- WASM checksums have been recorded:
escrow.wasmSHA-256:______________________________________________________oracle.wasmSHA-256:______________________________________________________
- The checksums above match the files uploaded to mainnet (verify with
stellar contract inspect). - All CI checks pass on the tagged commit (GitHub Actions green).
- Test coverage meets or exceeds the minimums in TESTING_GUIDE.md:
- Line coverage ≥ 95 %
- Branch coverage ≥ 90 %
- Oracle service is deployed to a production-grade host (not a developer laptop).
- Oracle service environment variables are sourced from the secrets manager, not from
.envfiles committed to version control. -
LICHESS_API_TOKENis a production token with appropriate rate limits; confirm the Lichess account is in good standing. -
CHESSDOTCOM_API_KEYis a production key (if Chess.com oracle is enabled for this release). - Oracle poll interval (
ORACLE_POLL_INTERVAL_SECS) is set to a value that stays within platform API rate limits:- Lichess: free tier allows ~200 requests/minute; recommended
ORACLE_POLL_INTERVAL_SECS=30. - Chess.com: verify your tier's rate limit before setting.
- Lichess: free tier allows ~200 requests/minute; recommended
- HTTP retry logic and exponential back-off are confirmed active in oracle config.
- Reconciliation interval (
ORACLE_RECONCILIATION_INTERVAL_SECS, default 60s) is confirmed active — this is what discoversActivematches and enqueues their first verification attempt; without it no new match is ever submitted. - Dead-letter queue is configured and monitored for failed submission retries.
- Oracle service health endpoint (
GET /health) returnshealthyin production environment. - Oracle Prometheus metrics endpoint (
GET /metrics) is reachable from the Prometheus scraper.
- The token allowlist strategy for mainnet has been decided and documented:
- Allowlist active: specific tokens will be added via
add_allowed_tokenbefore launch. - Open mode: allowlist remains inactive (any token accepted). Note: this is higher risk and requires additional monitoring.
- Allowlist active: specific tokens will be added via
- If allowlist mode:
- All permitted token contract addresses have been verified on the Stellar mainnet ledger.
- XLM native asset contract address for mainnet has been confirmed.
- USDC or other stablecoin contract addresses have been confirmed with the issuing organisation.
- Test transaction using each allowed token has been executed on testnet with the exact mainnet contract addresses.
- Match timeout has been set to an appropriate value for mainnet (recommended: 518,400 ledgers / 30 days).
Complete all tests on the Stellar testnet using configuration as close to production as possible, then repeat the smoke tests on mainnet immediately after contract initialisation (before opening to users).
- Full end-to-end match lifecycle: create → deposit (both players) → submit result → payout verified.
- Draw scenario: submit draw result → both players refunded correct amounts.
- Cancel flow: cancel before both deposits → both players refunded.
- Expire flow: advance past match timeout →
expire_matchexecutes correctly. - Admin pause: pause contract → all write operations rejected → unpause → operations resume.
- Oracle key rotation: rotate oracle address → new oracle submits result → payout correct.
- Invalid token rejection: attempt
create_matchwith a token not on the allowlist →InvalidTokenerror returned. -
submit_result_with_oracle_recordstores correctgame_idon-chain; verify viaget_oracle_record. - Duplicate
game_idrejected withDuplicateGameId.
- Concurrent deposits from both players in the same ledger handled correctly.
- Verify no double-payout is possible: second
submit_resultcall on a completed match returnsMatchAlreadyCompleted.
Execute these steps in order. Record the transaction hash and contract ID for each step.
-
Step 1 — Deploy
OracleContractto mainnet:stellar contract deploy \ --wasm target/wasm32-unknown-unknown/release/oracle.wasm \ --source <DEPLOYER_KEYPAIR> \ --network mainnet
Oracle Contract ID:
_____________________________________________Deploy TX Hash:_____________________________________________ -
Step 2 — Initialize
OracleContract:stellar contract invoke \ --id $ORACLE_CONTRACT_ID \ --source <DEPLOYER_KEYPAIR> \ --network mainnet \ -- initialize \ --admin <ORACLE_ADMIN_ADDRESS> \ --deployer <DEPLOYER_ADDRESS>
Init TX Hash:
_____________________________________________ -
Step 3 — Deploy
EscrowContractto mainnet:stellar contract deploy \ --wasm target/wasm32-unknown-unknown/release/escrow.wasm \ --source <DEPLOYER_KEYPAIR> \ --network mainnet
Escrow Contract ID:
_____________________________________________Deploy TX Hash:_____________________________________________ -
Step 4 — Initialize
EscrowContract:stellar contract invoke \ --id $ESCROW_CONTRACT_ID \ --source <DEPLOYER_KEYPAIR> \ --network mainnet \ -- initialize \ --oracle $ORACLE_CONTRACT_ID \ --admin <ESCROW_ADMIN_ADDRESS> \ --deployer <DEPLOYER_ADDRESS>
Init TX Hash:
_____________________________________________ -
Step 5 — Add allowed tokens (if allowlist mode is active):
stellar contract invoke \ --id $ESCROW_CONTRACT_ID \ --source <ESCROW_ADMIN_KEYPAIR> \ --network mainnet \ -- add_allowed_token \ --token <TOKEN_CONTRACT_ADDRESS>
Confirm with
get_allowed_tokens; list matches expected tokens: ✓ -
Step 6 — Set match timeout:
stellar contract invoke \ --id $ESCROW_CONTRACT_ID \ --source <ESCROW_ADMIN_KEYPAIR> \ --network mainnet \ -- set_match_timeout \ --timeout 518400
TX Hash:
_____________________________________________ -
Step 7 — Update oracle service configuration with the live contract IDs:
CONTRACT_ESCROW=<ESCROW_CONTRACT_ID>CONTRACT_ORACLE=<ORACLE_CONTRACT_ID>STELLAR_NETWORK=mainnet- Confirm oracle service restarts cleanly and
/healthreturnshealthy.
-
Step 8 — Update frontend / event indexer with mainnet contract IDs and restart.
-
Step 9 — Archive / delete the deployer keypair as planned.
Run these checks immediately after deployment, before announcing availability to users.
-
stellar contract invoke --id $ESCROW_CONTRACT_ID -- get_contract_statereturns expected initial state (not paused, no matches). - Oracle service logs show successful connection to mainnet Stellar RPC.
- Event indexer is ingesting mainnet events (check
/healthand first few blocks). - Grafana dashboard loads and shows the correct mainnet contract IDs in the title / annotations.
- Run a live mainnet smoke test with minimal stakes (1 XLM each player):
- Create match → TX confirmed.
- Both players deposit → match becomes Active.
- Submit result via oracle → payout TX confirmed on-chain.
- Payout amounts verified correct in Stellar explorer.
- No unexpected
Paused,MatchNotFound, or token-transfer errors appear in oracle logs.
- Prometheus is scraping all production targets (oracle, event indexer, WebSocket server) — verify at http://prometheus:9090/targets.
- Grafana Contract Health dashboard is visible and all panels show data.
- Alertmanager is configured and test alert has been fired and received by the on-call channel.
- All critical and warning alert rules from
monitoring/prometheus/alerts.ymlare active. - On-call rotation is set up and the first responder has been briefed on:
- runbook-pause.md — responding to contract pause
- runbook-rotation.md — oracle key rotation
- monitoring-setup.md — dashboard usage
- A post-launch review is scheduled for 24 hours after go-live to review initial metrics.
- A full post-launch review is scheduled for 7 days after go-live.
Soroban smart contracts are immutable once deployed. There is no "undo" for a deployment. Rollback options are:
If a critical issue is detected:
- Admin calls
pauseon the escrow contract immediately. - No new matches can be created; no deposits or result submissions are accepted.
- Investigate the issue without further fund exposure.
- If the issue is resolvable (e.g. oracle bug, frontend bug), fix and unpause.
- See runbook-pause.md for the full pause runbook.
If the oracle key is compromised:
- Admin calls
update_oraclewith a new, secure oracle keypair. - Old oracle can no longer submit results.
- Deploy new oracle service with the new keypair.
- See runbook-rotation.md for the full rotation runbook.
If the contract code itself has a critical vulnerability:
- Pause the existing contract immediately (Option A).
- Communicate transparently with users: all staked funds remain safe in the paused contract.
- Deploy and verify the new contract version (repeat this checklist).
- Provide a migration path for any active matches: announce a refund window, then call
cancel_match/expire_matchfor remaining active matches (requires admin action or waiting for timeout). - Redirect oracle service and frontend to the new contract address.
- Announce the migration and new contract address publicly.
Important: Always exhaust Option A and B before considering Option C. A deploy of a new contract requires users to trust the new address and may disrupt active matches.
When an incident requires user communication:
[CHECKMATE-ESCROW NOTICE — {DATE}]
We have identified an issue affecting [component].
The escrow contract has been [paused / oracle rotated].
Impact: [describe what users cannot do]
Funds: All staked funds are safe and inaccessible to any party except their owners.
ETA: We expect to resolve this by [time estimate].
Updates: Follow this channel / [status page URL] for updates.
All responsible parties must sign off before the deployment is considered complete and before the system is opened to public use.
| Role | Full Name | Date (UTC) | Signature / Approval Link |
|---|---|---|---|
| Lead Developer | |||
| Security Reviewer | |||
| DevOps / Infrastructure | |||
| Product Owner | |||
| (Auditor — if required) |
Deployment declared production-ready on: _________________________ (UTC)
This checklist was introduced in #979. Update it as the deployment process evolves.