This implementation adds a state-sanity check on startup for the StellarFlow backend to prevent silent failures when the target smart contract has been redeployed or upgraded on Testnet.
If the target smart contract has been redeployed or upgraded on Testnet, a mismatch between the backend's expected contract ID and the ledger will break operations silently. The backend would continue running but fail to interact with the contract properly.
Implemented an on-startup sanity check that performs a low-cost read on the target contract ID before starting the ingestion loop. If the contract fails to respond or returns unexpected structural data, the backend ingestion loop is prevented from starting.
Added CONTRACT_ID to .env.example:
# Soroban Contract Configuration
# Target smart contract ID for state sanity checks on startup
CONTRACT_ID=your_contract_id_hereCreated src/services/contractSanityCheckService.ts with the following features:
- Low-cost contract reads: Attempts to read contract version or active status
- Dual fallback strategy: Tries
get_versionfirst, falls back tois_activecheck - Network-aware: Automatically uses correct RPC endpoint based on
STELLAR_NETWORK - Graceful degradation: Allows startup if
CONTRACT_IDis not configured (backward compatibility) - Error handling: Provides detailed error messages for debugging
performSanityCheck(): Main entry point for contract validationtryGetVersion(): Attempts to read contract version (low-cost read)tryIsActive(): Fallback method to check if contract exists and is activeisConfigured(): Checks if CONTRACT_ID is set
Modified src/index.ts to integrate the sanity check:
- Import the service:
import { contractSanityCheckService } from "./services/contractSanityCheckService";- Perform check before ingestion loop:
// Perform contract sanity check before starting ingestion loop
let contractSanityPassed = true;
if (contractSanityCheckService.isConfigured()) {
try {
const sanityResult = await contractSanityCheckService.performSanityCheck();
if (!sanityResult.success) {
console.error(`❌ Contract sanity check failed: ${sanityResult.error}`);
console.error("⛔ Preventing ingestion loop from starting due to contract failure");
contractSanityPassed = false;
}
} catch (err) {
console.error("❌ Contract sanity check error:", err);
console.error("⛔ Preventing ingestion loop from starting due to contract check error");
contractSanityPassed = false;
}
}- Conditional ingestion loop start:
// Start Soroban event listener to track confirmed on-chain prices
// Only start if contract sanity check passed or if check is not configured
if (contractSanityPassed) {
try {
sorobanEventListener = new SorobanEventListener();
sorobanEventListener.start().catch((err) => {
console.error("Failed to start event listener:", err);
});
console.log(`👂 Soroban event listener started`);
} catch (err) {
console.warn("Event listener not started:", err);
sorobanEventListener = null;
}
} else {
console.warn("⚠️ Soroban event listener NOT started due to failed contract sanity check");
}- Add your contract ID to
.env:
CONTRACT_ID=CDLZFC3SYJNHZV5DQJZ5YIQ5ZG4YMJF5JXK7HNXATFQ4J3E2B5S7V4N- Set the network (if not already set):
STELLAR_NETWORK=TESTNET🔍 Performing contract sanity check on CDLZFC3SYJNHZV5DQJZ5YIQ5ZG4YMJF5JXK7HNXATFQ4J3E2B5S7V4N (TESTNET)
✅ Contract sanity check passed - Version: 1.0.0
👂 Soroban event listener started
🔍 Performing contract sanity check on CDLZFC3SYJNHZV5DQJZ5YIQ5ZG4YMJF5JXK7HNXATFQ4J3E2B5S7V4N (TESTNET)
❌ Contract sanity check failed: Contract not found on ledger
⛔ Preventing ingestion loop from starting due to contract failure
⚠️ Soroban event listener NOT started due to failed contract sanity check
ℹ️ CONTRACT_ID not configured - skipping contract sanity check (ingestion loop will start)
👂 Soroban event listener started
The service automatically selects the correct RPC endpoint based on the network:
- TESTNET:
https://rpc.testnet.stellar.org - PUBLIC:
https://rpc.mainnet.stellar.org
The service attempts two types of low-cost reads:
- Version Read: Attempts to read a
versionfunction from the contract - Active Check: Falls back to checking if the contract code exists on the ledger
The service handles various error scenarios:
- Contract not found: Indicates contract was redeployed with new ID
- Network errors: RPC connectivity issues
- Timeout: Contract not responding within 10 seconds
- Invalid response: Contract returns unexpected data structure
- Early detection: Catches contract mismatches at startup before processing data
- Silent failure prevention: Prevents backend from running with broken contract configuration
- Backward compatibility: Allows operation without CONTRACT_ID for existing deployments
- Clear logging: Provides detailed error messages for debugging
- Low overhead: Uses low-cost read operations that don't consume significant resources
To test the implementation:
-
Valid Contract ID:
- Set
CONTRACT_IDto a valid contract on the network - Start the backend
- Verify the sanity check passes and ingestion loop starts
- Set
-
Invalid Contract ID:
- Set
CONTRACT_IDto an invalid/non-existent contract - Start the backend
- Verify the sanity check fails and ingestion loop does not start
- Set
-
No Contract ID:
- Remove or comment out
CONTRACT_ID - Start the backend
- Verify the check is skipped and ingestion loop starts normally
- Remove or comment out
Potential improvements for future iterations:
- Automatic contract ID discovery: Fetch contract ID from a configuration service
- Contract version validation: Compare contract version against expected version
- Retry mechanism: Add retry logic for transient network failures
- Health check endpoint: Expose contract status via
/healthendpoint - Alert integration: Send alerts when contract sanity check fails
.env.example: Added CONTRACT_ID configurationsrc/index.ts: Integrated sanity check into startup processsrc/services/contractSanityCheckService.ts: New service fileCONTRACT_SANITY_CHECK_IMPLEMENTATION.md: This documentation file
Uses existing @stellar/stellar-sdk package for Soroban RPC interactions. No additional dependencies required.