Purpose: Enable app functionality despite unreliable Venezuelan internet connectivity
Complexity: Medium - Queue management + graceful degradation strategies
Venezuelan internet reality:
Asgaya must work offline:
Design principle: Never block user with “No internet” error. Queue, cache, degrade gracefully.
What: Detect when connectivity changes (online ↔ offline)
Pseudocode:
class ConnectivityMonitor(context: Context) {
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
log("Internet available")
onConnectivityRestored()
}
override fun onLost(network: Network) {
log("Internet lost")
onConnectivityLost()
}
}
fun startMonitoring() {
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, networkCallback)
}
}
Trigger actions:
onConnectivityRestored(): Process queued actions, sync blockchain stateonConnectivityLost(): Show offline indicator, disable real-time featuresProblem: Android might say “online” but Electrum servers unreachable
Solution: Ping Electrum on connectivity change
Pseudocode:
function checkElectrumConnectivity():
try:
response = electrumQuery("server.ping", timeout=5_SECONDS)
if response == "pong":
return true
catch Timeout:
log_warning("Electrum unreachable")
return false
function onConnectivityRestored():
if checkElectrumConnectivity():
log("Electrum reachable, syncing")
processQueuedActions()
syncBlockchainState()
else:
log_warning("Internet available but Electrum unreachable")
// Retry in 30 seconds
schedule_retry(checkElectrumConnectivity, 30_SECONDS)
Actions that require internet:
Actions that work offline:
Database table:
Table: action_queue
- id (INTEGER, PRIMARY KEY AUTOINCREMENT)
- action_type (TEXT: "create_covenant", "fund_covenant", "send_nostr", "create_listing", "delist_listing")
- payload (TEXT, JSON-encoded action data)
- created_at (INTEGER, Unix timestamp)
- retry_count (INTEGER, default 0)
- last_retry_at (INTEGER, nullable)
- status (TEXT: "pending", "processing", "failed")
Example queue entry:
{
"id": 1,
"action_type": "create_covenant",
"payload": {
"recipient_cash_account": "Elena#142",
"amount_eur": 100,
"buffer_percent": 7,
"expiry_hours": 8
},
"created_at": 1735686000,
"retry_count": 0,
"status": "pending"
}
Pseudocode:
function enqueueAction(action_type, payload):
db.insert("action_queue", {
action_type: action_type,
payload: json_encode(payload),
created_at: now(),
retry_count: 0,
status: "pending"
})
showNotification("Action queued (offline). Will process when online.")
// If we're actually online now, process immediately
if isOnline():
processQueuedActions()
Pseudocode:
function processQueuedActions():
if not isOnline() or not checkElectrumConnectivity():
return // Still offline
pending_actions = db.query("SELECT * FROM action_queue WHERE status = 'pending' ORDER BY created_at ASC")
for action in pending_actions:
// Mark as processing (prevent duplicate processing)
db.update("action_queue", {status: "processing"}, action.id)
try:
result = executeAction(action.action_type, json_decode(action.payload))
if result.success:
// Success - remove from queue
db.delete("action_queue", action.id)
showNotification("Queued action completed: " + action.action_type)
else:
// Failed - retry later
handleFailedAction(action)
catch NetworkError:
// Lost connectivity mid-processing
log_warning("Lost connectivity while processing queue")
db.update("action_queue", {status: "pending"}, action.id)
break // Stop processing, wait for connectivity
Exponential backoff: Avoid hammering Electrum when repeatedly failing
Pseudocode:
function handleFailedAction(action):
retry_count = action.retry_count + 1
if retry_count > MAX_RETRIES: // MAX_RETRIES = 5
// Too many failures - give up
db.update("action_queue", {status: "failed"}, action.id)
showNotification("Action failed after " + retry_count + " retries. Manual intervention needed.")
return
// Calculate backoff delay: 2^retry_count minutes
backoff_seconds = Math.pow(2, retry_count) * 60
db.update("action_queue", {
retry_count: retry_count,
last_retry_at: now(),
status: "pending"
}, action.id)
log("Will retry action in " + backoff_seconds + " seconds")
// Schedule retry
schedule_retry(processQueuedActions, backoff_seconds)
Retry delays:
TTL: 5 minutes (balance freshness vs performance)
Pseudocode:
function getBulletinBoardListings(currency):
cached = getCachedListings(currency)
if cached and (now() - cached.timestamp < 300): // 5 minutes
if not isOnline():
showBanner("Offline. Showing cached listings from " + formatTime(cached.timestamp))
return cached.listings
// Cache expired or empty
if not isOnline():
// Return stale cache (better than nothing)
if cached:
showBanner("Offline. Showing stale listings from " + formatTime(cached.timestamp))
return cached.listings
else:
show_error("No cached listings. Connect to internet.")
return []
// Online and cache expired - refresh
fresh_listings = queryElectrumListings(currency)
cacheListings(currency, fresh_listings)
return fresh_listings
Manual refresh:
function refreshBulletinBoard():
if not isOnline():
show_error("Cannot refresh offline")
return
clearCache("bulletin_board")
getBulletinBoardListings(currency) // Re-fetches
TTL: 24 hours (Cash Accounts rarely change)
Offline behavior:
function resolveCashAccount(cash_account):
cached = getCachedCashAccount(cash_account)
if cached and (now() - cached.timestamp < 86400): // 24 hours
return cached.address
// Cache expired
if not isOnline():
if cached:
// Use stale cache (acceptable - Cash Accounts don't change)
return cached.address
else:
return null // Can't resolve offline without cache
// Online - resolve fresh
address = electrumQuery("blockchain.cash_account.resolve", {name: cash_account})
cacheCashAccount(cash_account, address)
return address
Problem: Wallet balance query requires Electrum
Solution: Cache UTXOs locally, sync when online
Pseudocode:
Table: cached_utxos
- txid (TEXT)
- vout (INTEGER)
- value (INTEGER, satoshis)
- script (TEXT)
- token (TEXT, nullable, JSON for CashTokens)
- height (INTEGER, block height)
- PRIMARY KEY (txid, vout)
function getWalletBalance():
if isOnline():
// Refresh UTXO cache
fresh_utxos = electrumQuery("blockchain.address.listunspent", {address: my_addresses})
// Clear old cache
db.execute("DELETE FROM cached_utxos")
// Store fresh UTXOs
for utxo in fresh_utxos:
db.insert("cached_utxos", utxo)
// Use cached UTXOs (fresh if online, stale if offline)
cached_utxos = db.query("SELECT * FROM cached_utxos")
total_balance = sum(cached_utxos.map(u => u.value))
if not isOnline():
showBanner("Offline. Balance may be outdated.")
return total_balance
✅ Full functionality:
⚠️ Degraded:
❌ Unavailable:
Offline banner:
┌─────────────────────────────────────┐
│ ⚠️ Offline. Using cached data. │
│ Last synced: 5 minutes ago │
│ [Retry Now] │
└─────────────────────────────────────┘
Queued action indicator:
Your covenant is queued (offline).
Will broadcast when connectivity returns.
[View Queue (1 action)]
Stale data warning:
Showing listings from 15 minutes ago.
Connect to internet for latest listings.
Pseudocode:
function onConnectivityRestored():
log("Connectivity restored")
// 1. Check Electrum reachability
if not checkElectrumConnectivity():
log_warning("Internet available but Electrum unreachable")
schedule_retry(onConnectivityRestored, 30_SECONDS)
return
// 2. Process queued actions
showNotification("Online. Processing queued actions...")
processQueuedActions()
// 3. Sync blockchain state
syncBlockchainState()
// 4. Refresh caches
refreshBulletinBoard()
refreshWalletBalance()
// 5. Reconnect Nostr relays
connectToNostrRelays()
showNotification("Sync complete. App up to date.")
Problem: Internet available but slow (3G)
Solution: Prioritize critical operations
Pseudocode:
function processQueueWithPriority():
if isSlowConnection(): // <100kbps
log("Slow connection detected, prioritizing critical actions")
// Priority order:
// 1. Fund covenants (time-sensitive)
// 2. Claim covenants (time-sensitive)
// 3. Send Nostr messages (medium priority)
// 4. Create listings (low priority)
// 5. Refresh bulletin board (lowest priority)
critical_actions = db.query("SELECT * FROM action_queue WHERE action_type IN ('fund_covenant', 'claim_covenant') ORDER BY created_at ASC")
for action in critical_actions:
executeAction(action)
// Skip low-priority actions on slow connection
log("Deferring non-critical actions until faster connection")
else:
// Fast connection - process all
processQueuedActions()
What: Schedule periodic sync when online
Pseudocode:
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(30, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(syncRequest)
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
if (checkElectrumConnectivity()) {
processQueuedActions()
syncBlockchainState()
refreshCaches()
return Result.success()
} else {
return Result.retry() // Retry later
}
}
}
Benefit: Even if user doesn’t open app, queued actions process automatically
What: iOS wakes app periodically for background sync
Pseudocode:
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if checkElectrumConnectivity() {
processQueuedActions()
syncBlockchainState()
completionHandler(.newData)
} else {
completionHandler(.failed)
}
}
Limitation: iOS controls when background fetch runs (not guaranteed)
function executeActionWithTimeout(action):
try:
result = executeAction(action, timeout=30_SECONDS)
return result
catch Timeout:
log_warning("Action timed out (slow connection)")
// Retry with longer timeout
result = executeAction(action, timeout=60_SECONDS)
return result
ELECTRUM_SERVERS = [
"electrum1.cipig.net:10055",
"bch.imaginary.cash:50002",
"electroncash.de:50002"
]
function queryElectrumWithFallback(method, params):
for server in ELECTRUM_SERVERS:
try:
response = electrumQuery(server, method, params, timeout=10_SECONDS)
return response
catch Timeout:
log_warning("Electrum server " + server + " timed out, trying next")
continue
// All servers failed
throw ElectrumUnavailableError("All Electrum servers unreachable")
try:
processQueuedActions()
catch DatabaseError:
log_error("Queue database corrupted")
// Clear corrupted queue
db.execute("DELETE FROM action_queue")
show_error("Action queue corrupted and cleared. Recent queued actions lost.")
online/offline eventsCritical: Test on actual Venezuelan internet conditions
Uses:
Used by:
Status: Phase 0 - Critical infrastructure
Updated: 2026-06-25
Complexity: Medium (queue management + graceful degradation)
Research: See RS070 (implementation documentation strategy)
—
| 🏠 Home | ↑ Android App | 📖 Glossary |