This guide explains how ApexChainx integrates with the Stellar blockchain network to enable automated SLA-based payments, instant settlements, and immutable audit trails.
- Overview
- Architecture
- Prerequisites
- Setup
- Wallet Management
- SLA Payment System
- Smart Contracts
- Testing on Testnet
- Deployment to Mainnet
- API Reference
- Troubleshooting
- FAQ
Stellar is a fast, low-cost blockchain network designed for payments and asset transfers. Transactions confirm in 3-5 seconds with fees of ~$0.00001.
ApexChainx integrates Stellar to solve key problems in telecom network operations:
- Slow Payments: Traditional bank transfers take days. Stellar settles in seconds.
- Manual SLA Tracking: Smart contracts automatically calculate penalties/rewards.
- Trust Issues: Blockchain provides transparent, immutable records.
- Cross-Border Payments: Stellar enables instant international settlements.
- High Transaction Costs: Stellar fees are negligible compared to wire transfers.
- Automated SLA Payments: Smart contracts trigger penalty or reward payments based on MTTR
- Instant Settlements: Payments confirm in 3-5 seconds
- Multi-Currency: Support for USDC (payments), ApexChainx tokens (rewards), XLM (fees)
- Immutable Audit Trails: RCA reports hashed and stored on-chain
- Transparent: All transactions viewable on public ledger
┌──────────────┐
│ Outage │
│ Detected │
└──────┬───────┘
│
▼
┌──────────────┐
│ Ops Engineer │
│ Resolves │
└──────┬───────┘
│
▼
┌──────────────────┐
│ ApexChainx │──► MTTR: 25 minutes
│ Calculates MTTR │ Threshold: 15 minutes
└──────┬───────────┘ Status: VIOLATED
│
▼
┌──────────────────────┐
│ Soroban Smart │──► Penalty: $1,000
│ Contract Invoked │ (10 min × $100/min)
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ Stellar Network │──► Transaction submitted
│ Payment Executed │ Confirmed in 3-5 seconds
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ Notifications Sent │──► Email, Webhook, UI update
│ Records Updated │ Tx hash stored in Firestore
└──────────────────────┘
- ApexChainx Frontend: User interface with wallet connection
- ApexChainx Backend: FastAPI server with Stellar SDK
- Soroban Smart Contracts: On-chain SLA calculation logic
- Stellar Network: Blockchain for payments and storage
- Firestore: Off-chain database for application data
-
Stellar Account
- Create testnet account: Stellar Laboratory
- For mainnet: Use Freighter or other wallet
-
Freighter Wallet (for frontend)
- Install: freighter.app
- Switch to Testnet during development
-
Stellar SDK
- JavaScript:
npm install stellar-sdk - Python:
pip install stellar-sdk
- JavaScript:
-
Soroban CLI (for smart contracts)
cargo install --locked soroban-cli
-
USDC Token
- Testnet USDC: We'll provide the asset code
- Mainnet USDC:
USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
Frontend (.env.local):
# Stellar Network
VITE_STELLAR_NETWORK=testnet
VITE_STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
VITE_STELLAR_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
# Smart Contract Addresses
VITE_SLA_CONTRACT_ID=CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
VITE_USDC_TOKEN_ADDRESS=CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
VITE_APEXCHAINX_TOKEN_ADDRESS=CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABackend (.env):
# Stellar Configuration
STELLAR_NETWORK=testnet
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
STELLAR_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
# Pool Wallet - NEVER commit secret keys to version control!
# Generate these using: stellar-sdk Keypair.random()
STELLAR_POOL_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Smart Contract IDs - obtained after contract deployment
SLA_CONTRACT_ID=CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
USDC_TOKEN_ADDRESS=CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
APEXCHAINX_TOKEN_ADDRESS=CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# Payment Settings
AUTO_PAYMENT_ENABLED=true
MAX_AUTO_PAYMENT_AMOUNT=10000SECURITY WARNING: Never commit secret keys to version control. Use environment variables or secure key management systems. The
STELLAR_POOL_SECRET_KEYshould be stored securely and never exposed in logs, documentation, or code examples.
SECURITY CRITICAL: Handle secret keys with extreme care. Never expose them in logs, documentation, or share them electronically.
Using Stellar Laboratory (Testnet):
- Go to Stellar Laboratory
- Click "Generate keypair"
- IMMEDIATELY SAVE the Secret Key securely (starts with 'S') - store in password manager or hardware security module
- Copy the Public Key (starts with 'G') for configuration
- Click "Fund account" to get testnet XLM from Friendbot
Create accounts with clear purposes:
- Pool Account: For holding and distributing funds (highest security requirement)
- Operator Account: For testing penalty payments (medium security)
NEVER:
- Commit secret keys to version control
- Log secret keys in application logs
- Include secret keys in documentation examples
- Share secret keys via email, chat, or unencrypted channels
- Use the same keys for testnet and mainnet
Before receiving USDC, accounts must establish a trustline:
Using Stellar Laboratory:
- Go to Transaction Builder
- Source Account: Your public key
- Operations → Add Operation → Change Trust
- Asset Code:
USDC - Issuer:
[USDC issuer public key] - Sign and submit
Using Stellar SDK (Python):
from stellar_sdk import Server, Keypair, TransactionBuilder, Network, Asset
server = Server("https://horizon-testnet.stellar.org")
source_keypair = Keypair.from_secret("SXXX...")
# Build trustline transaction
source_account = server.load_account(source_keypair.public_key)
transaction = (
TransactionBuilder(
source_account=source_account,
network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
base_fee=100,
)
.append_change_trust_op(
asset=Asset("USDC", "GXXX...") # USDC issuer
)
.set_timeout(30)
.build()
)
transaction.sign(source_keypair)
response = server.submit_transaction(transaction)
print(f"Transaction hash: {response['hash']}")Build contracts:
cd contracts/sla_calculator
cargo build --target wasm32-unknown-unknown --releaseDeploy to testnet:
# SECURITY: Use environment variable for secret key, never hardcode
export STELLAR_DEPLOYER_SECRET="your-actual-secret-key-here"
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/sla_calculator.wasm \
--network testnet \
--source-account "$STELLAR_DEPLOYER_SECRET"Initialize contract:
soroban contract invoke \
--id CCCC... \
--network testnet \
--source-account "$STELLAR_DEPLOYER_SECRET" \
-- initialize \
--admin GXXX... \
--usdc_token CBBB... \
--pool_address GXXX...Save the contract ID to your .env files.
import { isConnected, requestAccess, getPublicKey } from '@stellar/freighter-api';
async function connectWallet() {
// Check if Freighter is installed
const installed = await isConnected();
if (!installed) {
alert('Please install Freighter wallet');
return;
}
// Request access
const publicKey = await requestAccess();
console.log('Connected:', publicKey);
return publicKey;
}from stellar_sdk import Keypair
def create_wallet():
"""Create a new Stellar keypair for a user"""
keypair = Keypair.random()
# SECURITY: Never return or log the secret key
# Only return the public key for wallet linking
return {
"public_key": keypair.public_key,
"secret_key": "[REDACTED - Store securely server-side or use hardware security module]"
}
# CORRECT implementation - only expose public key
def create_wallet_secure():
"""Create a new Stellar keypair"""
keypair = Keypair.random()
# Store secret key securely (database with encryption, HSM, etc.)
# NEVER return it in API responses
store_secret_key_securely(keypair.secret)
return {
"public_key": keypair.public_key,
"message": "Wallet created. Secret key stored securely."
}SECURITY WARNING: The above example shows what NOT to do. Secret keys should never be returned in API responses. Use secure key management systems and only expose public keys for wallet operations.
import { Server } from 'stellar-sdk';
async function getBalance(publicKey: string) {
const server = new Server('https://horizon-testnet.stellar.org');
const account = await server.loadAccount(publicKey);
const balances = {};
account.balances.forEach(balance => {
if (balance.asset_type === 'native') {
balances.XLM = balance.balance;
} else {
balances[balance.asset_code] = balance.balance;
}
});
return balances;
}- Outage Detected: Timer starts
- Engineer Resolves: Timer stops, MTTR calculated
- Smart Contract Called: Determines penalty or reward
- Payment Executed: Automatic transfer on Stellar
- Notification Sent: All parties notified
| Severity | Threshold | Penalty Rate | Base Reward |
|---|---|---|---|
| Critical | 15 min | $100/min | $750 |
| High | 30 min | $50/min | $750 |
| Medium | 60 min | $25/min | $750 |
| Low | 120 min | $10/min | $600 |
If MTTR > Threshold:
Penalty = (MTTR - Threshold) × Penalty_Rate
Example:
Severity: Critical
MTTR: 25 minutes
Threshold: 15 minutes
Penalty Rate: $100/min
Penalty = (25 - 15) × 100 = $1,000
If MTTR ≤ Threshold:
Performance % = (MTTR / Threshold) × 100
If Performance < 50%: Multiplier = 2.0 (Exceptional)
If Performance < 75%: Multiplier = 1.5 (Excellent)
If Performance ≤ 100%: Multiplier = 1.0 (Good)
Reward = Base_Reward × Multiplier
Example:
Severity: High
MTTR: 10 minutes
Threshold: 30 minutes
Performance: 33% (< 50%)
Base Reward: $750
Reward = $750 × 2.0 = $1,500
from app.services.sla.sla_calculator import SLACalculator
from app.services.stellar.payment_service import PaymentService
async def process_outage_resolution(outage_id: str):
"""Process SLA payment after outage resolution"""
# Get outage details
outage = await get_outage(outage_id)
# Calculate SLA result
calculator = SLACalculator()
sla_result = await calculator.calculate_sla_result(outage)
# Invoke smart contract
contract_result = await calculator.invoke_sla_contract(
outage_id=outage["id"],
severity=outage["severity"],
mttr_minutes=sla_result["mttr_minutes"]
)
# Execute payment if auto-payment enabled
if AUTO_PAYMENT_ENABLED:
payment_service = PaymentService()
payment = await payment_service.execute_sla_payment(
sla_result=contract_result,
operator_address=outage["operator_wallet"],
ops_team_address=outage["ops_team_wallet"]
)
return {
"sla_result": contract_result,
"payment": payment
}Functions:
initialize: Set up contract with admin and token addressescalculate_sla: Calculate penalty or reward for an outageexecute_payment: Execute payment based on SLA resultget_config: Get current SLA configurationupdate_config: Update SLA thresholds (admin only)
import os
from stellar_sdk import SorobanServer, TransactionBuilder, Network
from stellar_sdk.soroban_rpc import GetTransactionStatus
async def invoke_sla_contract(outage_id: str, severity: str, mttr: int):
"""Invoke SLA calculator contract"""
soroban_server = SorobanServer("https://soroban-testnet.stellar.org")
# SECURITY: Load secret from environment, never hardcode
source_secret = os.getenv("STELLAR_POOL_SECRET_KEY")
if not source_secret:
raise ValueError("STELLAR_POOL_SECRET_KEY environment variable not set")
source_keypair = Keypair.from_secret(source_secret)
# Build contract invocation
source_account = server.load_account(source_keypair.public_key)
transaction = (
TransactionBuilder(source_account, Network.TESTNET_NETWORK_PASSPHRASE, base_fee=100)
.append_invoke_contract_function_op(
contract_id=os.getenv("SLA_CONTRACT_ID"),
function_name="calculate_sla",
parameters=[
scval.to_symbol(outage_id),
scval.to_uint32(severity_to_enum(severity)),
scval.to_uint32(mttr)
]
)
.set_timeout(30)
.build()
)
# Simulate first
simulated = soroban_server.simulate_transaction(transaction)
# Prepare and sign
prepared = soroban_server.prepare_transaction(transaction, simulated)
prepared.sign(source_keypair)
# Submit
response = soroban_server.send_transaction(prepared)
# Wait for confirmation
while True:
status = soroban_server.get_transaction(response.hash)
if status.status != GetTransactionStatus.NOT_FOUND:
break
await asyncio.sleep(1)
# Parse result
return parse_contract_result(status.return_value)SECURITY NOTE: Always load sensitive keys from environment variables or secure key management systems. Never hardcode or log secret keys.
# Get free testnet XLM from Friendbot
curl "https://friendbot.stellar.org?addr=GXXX..."// In browser console
import { requestAccess } from '@stellar/freighter-api';
const publicKey = await requestAccess();
console.log('Connected:', publicKey);# Python script to test payment
from app.services.stellar.payment_service import PaymentService
service = PaymentService(network="testnet")
# SECURITY: Load from environment, never pass as parameter
import os
source_secret = os.getenv("TEST_PAYMENT_SECRET_KEY")
result = await service.create_payment(
source_secret=source_secret, # Only use for testing with testnet keys
destination="GXXX...",
amount="10.00",
asset_code="USDC"
)
print(f"Transaction hash: {result['tx_hash']}")
print(f"View on explorer: https://stellar.expert/explorer/testnet/tx/{result['tx_hash']}")SECURITY WARNING: Only use testnet keys for testing. Never use mainnet keys in test scripts. Consider using dedicated test accounts with minimal funds.
# Create test outage
curl -X POST http://localhost:8000/api/v1/outages \
-H "Content-Type: application/json" \
-d '{
"site_name": "Test Site",
"severity": "critical",
"detected_at": "2026-01-16T10:00:00Z"
}'
# Mark as resolved (25 minutes later - SLA violated)
curl -X PUT http://localhost:8000/api/v1/outages/OUT001 \
-H "Content-Type: application/json" \
-d '{
"status": "resolved",
"resolved_at": "2026-01-16T10:25:00Z"
}'
# Check SLA result
curl http://localhost:8000/api/v1/sla/status/OUT001Visit Stellar Expert and search for your transaction hash to see:
- Transaction details
- Payment amount
- Source and destination
- Timestamp
- Smart contract invocation (if applicable)
- All tests passing on testnet
- Smart contracts audited
- Security review completed
- Key management strategy in place
- Backup and recovery procedures documented
- Monitoring and alerting configured
- Rate limiting and error handling tested
- User documentation complete
Change network to mainnet:
STELLAR_NETWORK=mainnet
STELLAR_HORIZON_URL=https://horizon.stellar.org
STELLAR_SOROBAN_RPC_URL=https://soroban.stellar.orgsoroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/sla_calculator.wasm \
--network mainnet \
--source-account SXXX...Note: Mainnet deployment costs real XLM. Ensure you have sufficient balance.
Transfer sufficient USDC to your pool account to cover expected payments.
- Start with low-value transactions
- Enable for selected customers first
- Monitor closely for 24-48 hours
- Gradually increase limits
- Enable auto-payments for all
NEVER:
- Commit secret keys to version control
- Log secret keys in application logs
- Include secret keys in documentation or examples
- Share secret keys via email, chat, or unencrypted channels
- Use the same keys for testnet and mainnet
ALWAYS:
- Use environment variables or secure key management systems (AWS KMS, HashiCorp Vault, etc.)
- Rotate keys regularly, especially after any suspected compromise
- Use hardware security modules (HSM) for production keys
- Implement proper access controls and audit logging for key operations
- Use separate accounts/keys for different environments (dev/testnet/mainnet)
Wallet Operations:
- Never return secret keys in API responses
- Only expose public keys for wallet linking and balance checks
- Implement proper authentication and authorization for wallet operations
- Use rate limiting to prevent abuse
Payment Operations:
- Validate all payment amounts and destinations
- Implement idempotency to prevent duplicate payments
- Log all payment operations for audit purposes
- Use transaction monitoring and alerting
Contract Deployment:
- Audit contracts before mainnet deployment
- Use multisig for critical contract operations
- Implement proper access controls in contracts
- Test extensively on testnet before mainnet
Contract Invocation:
- Validate all inputs before sending to contracts
- Handle contract errors gracefully
- Implement retry logic with exponential backoff
- Monitor contract gas usage and costs
Testnet vs Mainnet:
- Never reuse keys between environments
- Use different contract addresses for each environment
- Implement environment-specific configuration validation
- Test all operations on testnet before mainnet deployment
Implement monitoring for:
- Failed transactions
- Unusual payment amounts
- Contract errors
- Key access patterns
- Balance changes
Set up alerts for:
- Large payment attempts
- Contract failures
- Key compromise indicators
- Balance anomalies
POST /api/v1/payments/process-sla
{
"outage_id": "OUT001"
}GET /api/v1/payments/history
{
"transactions": [
{
"tx_hash": "abc123...",
"type": "penalty",
"amount": 1000.00,
"asset": "USDC",
"status": "confirmed",
"timestamp": "2026-01-16T10:30:00Z"
}
]
}POST /api/v1/wallets/create
{
"user_id": "user123"
}GET /api/v1/wallets/{address}/balance
{
"XLM": 1000.00,
"USDC": 5000.00,
"APEXCHAINX": 500.00
}GET /api/v1/sla/status/{outage_id}
{
"status": "violated",
"mttr_minutes": 25,
"threshold_minutes": 15,
"penalty_amount": 1000.00,
"contract_tx_hash": "def456..."
}Problem: "Freighter not detected"
- Solution: Install Freighter browser extension from freighter.app
Problem: "Wrong network"
- Solution: Open Freighter → Settings → Switch to Testnet (for development)
Problem: "Transaction failed: Insufficient balance"
- Solution: Ensure account has enough XLM for fees (~0.00001 XLM per operation)
Problem: "Transaction failed: No trustline"
- Solution: Establish trustline for USDC before receiving payments
Problem: "Transaction timeout"
- Solution: Stellar network may be congested. Wait and retry, or increase timeout
Problem: "Contract not found"
- Solution: Verify contract ID in environment variables. Ensure contract is deployed.
Problem: "Contract invocation failed"
- Solution: Check contract parameters. Ensure source account is authorized.
Problem: "Payment not executing"
- Solution: Check
AUTO_PAYMENT_ENABLEDflag. Verify wallet balances. Check logs for errors.
Problem: "Payment stuck in pending"
- Solution: Check transaction status on Stellar Explorer. May need to resubmit.
Q: Do I need XLM for transactions?
A: Yes, every Stellar transaction requires a small fee (~0.00001 XLM). Accounts must maintain a minimum balance of 1 XLM.
Q: What's the difference between testnet and mainnet?
A: Testnet uses fake money for testing. Mainnet uses real assets with real value.
Q: How long do transactions take?
A: Stellar transactions typically confirm in 3-5 seconds.
Q: What if a payment fails?
A: Failed payments are logged and can be retried. The system will attempt up to 3 retries automatically.
Q: Can I cancel a payment?
A: No, Stellar transactions are final once confirmed. Always verify details before submitting.
Q: What happens if there's insufficient balance?
A: Transaction will fail with "insufficient balance" error. Ensure pool account has adequate funds.
Q: Where are private keys stored?
A: User private keys stay in their wallet (Freighter). Server private keys should be in secure vault (AWS Secrets Manager, Google Cloud KMS).
Q: Can transactions be reversed?
A: No, blockchain transactions are irreversible. Always verify recipient addresses.
Q: How do I backup my wallet?
A: In Freighter, go to Settings → Show Secret Key. Write it down and store securely offline.
Q: Can SLA thresholds be changed?
A: Yes, admins can update SLA configurations via the update_config function.
Q: What if there's a bug in the contract?
A: Contracts should be audited before mainnet deployment. In case of issues, admin can pause automated payments.
Q: How much does contract execution cost?
A: Soroban contract invocations cost a few cents in XLM, much cheaper than other blockchain platforms.
- Stellar Documentation
- Soroban Documentation
- Stellar SDK - JavaScript
- Stellar SDK - Python
- Stellar Expert Explorer
- Stellar Laboratory
- Freighter Wallet
- Stellar Discord Community
Need help? Open an issue on GitHub or join our Discord community!
CONTRACT_EXECUTION_MODE |
Behaviour |
|---|---|
local (default) |
SLA computed in-process by sla_calculator.py; no Soroban call |
contract |
SLA computed by invoking the deployed Soroban contract via HTTPX |
Switch to contract mode only when a deployed contract address is configured and the Stellar network is reachable.
For local development against the Stellar testnet:
- Generate a keypair using the Stellar Laboratory
- Fund the account using the Friendbot faucet
- Set
STELLAR_NETWORK=testnetandSTELLAR_POOL_SECRET_KEY=<your-secret>in.env - Set
CONTRACT_EXECUTION_MODE=contractto exercise the full Soroban path
Each completed SLA payment produces a record with:
| Field | Type | Description |
|---|---|---|
id |
UUID | Unique payment identifier |
transaction_hash |
string | Stellar transaction hash |
amount |
decimal | Payment amount in USDC |
from_address |
string | Pool wallet address |
to_address |
string | Recipient wallet address |
status |
enum | pending, confirmed, failed |
outage_id |
string | Linked outage reference |
created_at |
datetime | Submission timestamp |
confirmed_at |
datetime | On-chain confirmation timestamp |
The backend uses a single pool wallet (STELLAR_POOL_SECRET_KEY) as the funding source for all SLA payments. Ensure this wallet is funded with sufficient USDC before enabling CONTRACT_EXECUTION_MODE=contract in production.
Monitor the pool balance via the /api/v1/wallets endpoints or directly through Stellar Expert.
| Error | Cause | Resolution |
|---|---|---|
insufficient_funds |
Pool wallet balance too low | Top up pool wallet with USDC |
account_not_found |
Recipient wallet not activated | Ensure recipient has minimum XLM reserve |
tx_failed |
Soroban contract error | Check contract logs and network status |
timeout |
Horizon RPC unresponsive | Retry; check STELLAR_HORIZON_URL config |
Payments are denominated in USDC. The asset is identified by:
- Asset code:
USDC - Issuer (testnet):
GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 - Issuer (mainnet):
GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
Ensure the pool wallet holds a sufficient USDC trustline before executing payments.
| URL Variable | Purpose |
|---|---|
STELLAR_HORIZON_URL |
Used for account queries, balance checks, and transaction submission |
STELLAR_SOROBAN_RPC_URL |
Used exclusively for Soroban contract invocation |
Both are required when CONTRACT_EXECUTION_MODE=contract.
The backend polls Horizon for transaction confirmation after submission. Confirmation is recorded in the payment record as confirmed_at. If confirmation is not received within the polling window, the payment status remains pending and a retry is scheduled.
Stellar accounts require a minimum XLM balance (currently 1 XLM base reserve plus 0.5 XLM per trustline). Recipient wallets must meet this requirement before a USDC payment can be received. Validate wallet activation before executing SLA payments in production.
The deployed contract address is configured via SOROBAN_CONTRACT_ID in .env. This value is required when CONTRACT_EXECUTION_MODE=contract. The contract must be deployed to the same network as STELLAR_NETWORK.
When CONTRACT_EXECUTION_MODE=contract:
- Backend constructs a Soroban invocation transaction
- Transaction is signed with
STELLAR_POOL_SECRET_KEY - Transaction is submitted via
STELLAR_SOROBAN_RPC_URL - Backend polls for the transaction result
- Contract return value is parsed into an SLA outcome
- Outcome is persisted to the
sla_resultstable - Payment is executed if the outcome requires one
STELLAR_POOL_SECRET_KEYis loaded once at startup and held in memory- The secret key is never logged, never returned via any API endpoint, and never written to disk
- Only the corresponding public key (
STELLAR_POOL_PUBLIC_KEY) is stored in configuration for reference - If the secret key is compromised, rotate it immediately and update the
.envfile on all instances
If a Stellar payment fails (insufficient funds, account not found, network timeout), the backend schedules a retry via Celery. Retry attempts are tracked in the payment record's attempt_count field. After MAX_PAYMENT_RETRIES (default 3) attempts, the payment is marked failed and an audit event is emitted.
Each confirmed payment record includes an explorer_url field pointing to the transaction on Stellar Expert:
- Testnet:
https://stellar.expert/explorer/testnet/tx/{hash} - Mainnet:
https://stellar.expert/explorer/public/tx/{hash}
The correct URL is selected automatically based on STELLAR_NETWORK.
SLA payments include a Stellar transaction memo for on-chain verification. The memo format is validated end-to-end using a TxMemo Pydantic model.
<op>:<agg>:v<hash8>
Where:
- op: Operation code (2-3 uppercase letters, whitelisted)
- agg: Aggregation key (up to 16 chars, alphanumeric +
_-) - v: Versioned content hash — 8-character hex prefix of SHA-256
Total length: ≤ 28 bytes (Stellar memo limit).
| Code | Meaning |
|---|---|
SLP |
Settle-penalty |
SLR |
Settle-reward |
DSP |
Dispute-proposed |
DSR |
Dispute-resolved |
RCA |
Root-cause-analysis |
CFG |
Config-publish |
SLP:OUT001:va3f2c1b9 # Penalty settlement for outage OUT001
SLR:OUT042:vdeadbeef # Reward settlement for outage OUT042
DSP:dispute-99:v01234567 # Dispute proposed for dispute-99
Memes are validated at build time:
- Unknown op codes → rejected
- Non-hex content hash → rejected
- Exceeds 28 bytes → rejected with 422
- Malformed (missing
vprefix, wrong delimiters) → rejected
Suspicious memos (those that fail TxMemo.is_suspicious()) are flagged in audit rows. This detects:
- Raw UUIDs leaked into memos (privacy loss)
- Malformed or garbage-stuffed memos
- memos exceeding byte limits
- Model:
app/services/tx_memo.py—TxMemoPydantic model - Builder:
app/services/contracts/translation.py—build_tx_memo_from_result() - Tests:
tests/test_tx_memo.py— round-trip golden payloads, byte limit, op code whitelist
Before a wallet can receive USDC, it must have an active USDC trustline. The backend validates this before submitting a payment. If the trustline is missing, the payment is held in pending state and an operator notification is emitted via webhook (payment.blocked_no_trustline).
Soroban contract results are returned as XDR-encoded values. The contract adapter in app/services/contracts/ decodes the XDR result into a Python dict before passing it to the SLA service. Contributors extending the contract adapter must handle XDR decoding correctly — use the stellar-sdk library, not manual byte parsing.
Stellar transaction fees are paid in XLM from the pool wallet. The current fee strategy uses the Horizon fee stats endpoint to select a fee in the 90th percentile of recent base fees, ensuring timely inclusion without overpaying. The pool wallet must maintain a small XLM balance in addition to the USDC balance for fees.
| Operation | Endpoint |
|---|---|
| Check account balance | Horizon |
| Validate trustline | Horizon |
| Submit Soroban invocation | Soroban RPC |
| Submit classic payment | Horizon |
| Fetch transaction status | Horizon |
| Fetch ledger data | Horizon |