Mobile App Development for Crypto Lending
We frequently get requests to build mobile products for crypto lending. This is a complex technical task: the app must display collateral positions in real time, calculate the Health Factor, send liquidation risk notifications, and interact with DeFi protocols. An error in calculations or data delay can cost the user money. With over 5 years of experience in DeFi mobile development, we have completed 15+ projects for lending platforms, ensuring compliance and robust security.
Rigorous Development in Crypto Lending
How Is Health Factor Calculated and Liquidation Handled?
Health Factor = (Collateral × Liquidation Threshold) / Borrowed Amount. If HF falls below 1, the position is liquidated. This must be displayed to the user in real time with a visual indicator — not just a number, but a scale with zones: safe (green, HF > 1.5), risk (yellow, 1.1–1.5), critical (red, < 1.1).
The price of a collateral asset changes every few seconds. So HF must be recalculated on the client with every price update — via Chainlink Price Feeds or CEX WebSocket. It's important not to burden the server with polling every 2 seconds from thousands of clients; it's wise to subscribe to Binance WebSocket (wss://stream.binance.com:9443/ws/ethusdt@ticker) and calculate HF locally.
// Health Factor calculation on client
struct LendingPosition {
let collateralUSD: Decimal // collateral value in USD
let liquidationThreshold: Decimal // e.g., 0.82 for ETH
let borrowedUSD: Decimal // borrowed amount in USD
var healthFactor: Decimal {
guard borrowedUSD > 0 else { return Decimal.greatestFiniteMagnitude }
return (collateralUSD * liquidationThreshold) / borrowedUSD
}
var riskLevel: RiskLevel {
switch healthFactor {
case ..<1.1: return .critical
case 1.1..<1.5: return .warning
default: return .safe
}
}
}
Push notification at HF < 1.2 is mandatory. Without it, the user won't learn about liquidation. We implement this via a server worker that monitors positions through smart contract events (LiquidationCall event in the protocol's ABI) or through a protocol API (if Aave, Compound), and sends push via FCM/APNs when thresholds are reached. These event structures are documented in Aave V3 developer docs.
Connecting to a DeFi Protocol
If building on top of an existing DeFi protocol — Aave V3, Compound V3, Morpho — we use their SDKs: Aave V3 (packages @aave/contract-helpers and @aave/math-utils via JavaScript bridge or backend), Compound (compound-js or direct calls via web3j / web3.swift).
For native iOS/Android apps without React Native, it's more convenient to keep all Web3 logic on the backend and expose it via REST API: the client doesn't know about ABI, it simply makes POST /positions/deposit or GET /positions/{id}.
| Protocol |
SDK/Tools |
Features |
| Aave V3 |
@aave/contract-helpers + math-utils |
L2 optimization, portals, eMode |
| Compound V3 |
compound-js, web3j |
Customizable pools, base rate |
| Morpho |
own library |
Peer-to-peer matching, low fees |
Morpho's peer-to-peer matching can achieve up to 30% better interest rates for lenders compared to Compound V3.
// Android: position display via Jetpack Compose
@Composable
fun PositionCard(position: LendingPosition, currentPrice: BigDecimal) {
val updatedPosition = position.copy(
collateralUSD = position.collateralAmount * currentPrice
)
Card(
colors = CardDefaults.cardColors(
containerColor = when (updatedPosition.riskLevel) {
RiskLevel.CRITICAL -> MaterialTheme.colorScheme.errorContainer
RiskLevel.WARNING -> Color(0xFFFFF3E0)
RiskLevel.SAFE -> MaterialTheme.colorScheme.surfaceVariant
}
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Collateral: ${updatedPosition.collateralUSD.formatUSD()}")
Text("Debt: ${updatedPosition.borrowedUSD.formatUSD()}")
HealthFactorBar(hf = updatedPosition.healthFactor)
}
}
}
Interest Rates and APY Calculation
Rates in DeFi protocols are dynamic — they change based on the pool's utilization rate. They need to be updated in the UI every few minutes. For custodial platforms (Nexo, BlockFi-like), rates are set administratively and change less frequently.
APY vs APR — we show both. Users get confused: APR 8% ≠ APY 8%. APY with compounding is higher. Formula: APY = (1 + APR/n)^n - 1, where n is the number of compounding periods per year. Formula derived from standard compound interest calculations used in DeFi. We guarantee calculation accuracy with verification on testnet.
Transactions and Gas
When interacting with EVM smart contracts, every action is an on-chain transaction. Deposit, borrow, repay, withdraw — each costs gas. Before sending, we show the user a gas estimate via eth_estimateGas and the current price from eth_gasPrice or EIP-1559 eth_maxFeePerGas. Using L2 networks like Arbitrum reduces gas fees up to 5 times compared to Ethereum mainnet, which can save an active user hundreds of dollars per month — in some cases up to $300–$500 monthly. For example, a user with $50,000 in collateral can save up to $600 annually on Ethereum mainnet by using L2s.
Verification and Compliance
KYC via SumSub or Veriff for regulated platforms. Jurisdictional restrictions — users from the US cannot use most DeFi platforms without SEC registration. IP geofiltering on the server, additionally check at registration. We provide support for setting up the compliance module.
What's Included in the Work
- API and architecture documentation
- Source codes of iOS/Android applications
- CI/CD setup and deployment to app stores
- Access to backend admin panel and monitoring
- Client team training (2 sessions)
Timelines and Cost
View detailed timeline and cost
| Stage |
Duration |
| Architecture: DeFi protocol or custodial scheme |
3–5 days |
| Smart contracts or protocol integration |
1–3 weeks |
| Server part (positions, prices, notifications) |
2 weeks |
| Mobile client iOS + Android |
3–4 weeks |
| Testing on testnet |
1 week |
Total: 8–12 weeks. The cost for a basic integration starts at $85,000 and can reach $150,000 for multi-chain support.
Step-by-Step Development Process
- Analysis and protocol selection (Aave, Compound, Morpho)
- API and application architecture design
- Development of smart contracts (if needed) or SDK integration
- Creating mobile client for iOS and Android
- Testing on testnet and security audit
- Deployment and monitoring
Typical integration problems — incorrect transaction signing due to WalletConnect incompatibility, outdated ABI after protocol update, delays in data from WebSocket, geofiltering issues on the client side. Our experience allows us to avoid these errors at the design stage. Our backend implementation reduces latency by up to 3 times compared to direct RPC calls.
Contact us to get a consultation on your project. We guarantee security and compliance with App Store Review Guidelines (Section 4.2/5.1) and Google Play Console. Get a consultation — we will help choose the right stack and avoid typical mistakes.
Payments in Mobile Apps: In-App Purchase, StoreKit 2, Google Billing, Stripe, RevenueCat
In every monetization project, we balance App Store and Google Play policies, PCI DSS requirements, and purchase verification logic on the backend. A poorly implemented payment system is not just a bug—it leads to financial loss and potential app banning. Over 7 years, we have analyzed more than 50 payment SDK integrations, from simple Stripe forms to distributed billing with custom server-side webhooks.
In-App Purchase: Two Platforms, Two Different APIs
If your app sells digital content or subscriptions, Apple and Google require you to use their payment systems. This is non-negotiable: violating App Store rule 3.1.1 or Google Play Developer Policy results in app removal. Physical goods and offline services are a different story.
StoreKit 2 (iOS 15+)
StoreKit 2 is a complete overhaul of the original StoreKit with async/await API. Product.products(for:), product.purchase(), Transaction.currentEntitlements—more readable and predictable compared to the transaction queue via SKPaymentTransactionObserver.
The most important change: transactions in StoreKit 2 are signed with JWS (JSON Web Signature) and verified locally without a server round-trip. Transaction.verificationResult returns .verified(Transaction) or .unverified(Transaction, VerificationError). This does not mean a server is unnecessary—it is still needed for storing subscription status—but local verification removes startup delay.
StoreKit.AppTransaction verifies the actual app download from the App Store. Required for paid downloads or non-renewing purchases.
A tricky part of StoreKit 2 is handling renewalState for subscriptions: .subscribed, .expired, .inBillingRetryPeriod, .inGracePeriod, .revoked. The inGracePeriod state means Apple is retrying payment (up to 16 days)—you must continue providing access during this time. Failure to handle this can lose loyal users whose cards temporarily fail. Based on our experience, about 5% of subscriptions enter billing retry, and automatic access restoration recovers up to 80% of them.
Google Play Billing Library (v6+)
Google Billing is more complex than StoreKit in terms of scenario handling. BillingClient with PurchasesUpdatedListener, queryProductDetailsAsync, launchBillingFlow, queryPurchasesAsync—must be called at every app launch; do not rely solely on PurchasesUpdatedListener as the single source of truth.
Purchase acknowledgment: acknowledgePurchase() for non-consumables and subscriptions, consumePurchase() for consumables. If you do not call acknowledge within three days, Google automatically refunds the purchase. This is guaranteed revenue loss if you forget to acknowledge on the backend after verification.
ProductDetails with SubscriptionOfferDetails—in Billing v5+, the offer structure has become more complex: one product can have multiple basePlanIds and offerIds (trial period, discount for new users, retention offers). BillingFlowParams.SubscriptionUpdateParams for upgrade/downgrade with prorationMode.
Why Is Server-Side Verification Mandatory?
Never trust only client-side code when unlocking paid content. Client-side verification can be bypassed by modifying the app.
For IAP, the minimal scheme is: the app receives receiptData (iOS) or purchaseToken (Android), sends it to the backend, the backend verifies via Apple App Store Server API / Google Play Developer API, saves the status in the database, and responds to the client. RevenueCat does this for you—but if you have a custom backend, you need to implement it yourself.
Webhooks are more important than they seem. Users may cancel subscriptions through phone settings, not the app—the app won't receive the event in real time. Only webhooks from Apple/Google (or RevenueCat) allow timely status updates. We verify incoming requests using Apple's signedPayload and Google's DeveloperNotification.
How Does RevenueCat Simplify Integration?
Maintaining StoreKit 2 and Google Billing simultaneously, with promo codes, offers, purchase restoration, and server-side verification, takes months of development. RevenueCat handles most of this layer.
RevenueCat is not just a payment SDK. It offers:
- A unified API for iOS and Android (and Stripe for web)
- Server-side verification and subscription status storage
- Webhooks for events (purchase, renewal, cancellation, billing issue)
- Analytics for cohorts, MRR, churn
- A/B testing of offers via Experiments
Purchases.configure(withAPIKey:) at startup, Purchases.shared.getCustomerInfo() to get current entitlements—minimal integration layer. Purchases.shared.purchase(package:) instead of directly calling StoreKit/Billing.
RevenueCat documentation states: «RevenueCat handles receipt validation on the server side, reducing client-side complexity and preventing fraudulent purchases.»
Limitations of RevenueCat: it is paid (free up to $2.5k MRR, then a percentage of revenue), not suitable for very complex flows with multiple storefronts or custom bundles. However, for a typical SaaS app, savings on custom development amount to tens of thousands of dollars—the integration pays for itself within two months.
Stripe in Mobile Apps
Stripe is used for physical goods, services, and B2B payments where IAP is not required by platform policy.
Stripe iOS SDK and Android SDK—PaymentSheet for ready-made payment UI, PaymentSheetFlowController for custom UI with saved cards. Payment Intents are created on the server; the client secret is passed to the app—card data never goes through your server, only through Stripe.
Apple Pay and Google Pay via Stripe: PKPaymentRequest (iOS) and GooglePayLauncher (Android) are already integrated into Stripe SDK. Apple Pay conversion rates are 1.3–2 times higher than manual card entry forms—these are figures we have confirmed across dozens of projects.
Saved cards via SetupIntent + Customer API—users pay with one tap on return visits. Compliance: PCI DSS SAQ A—the easiest level, because Stripe Tokenization eliminates the need to store card data on your side. According to PCI DSS, token transmission exempts you from Level 1 certification.
3DS2 (Strong Customer Authentication) is mandatory for payments in the EU under PSD2. Stripe handles it automatically via PaymentIntent.confirmPayment, but you need to correctly handle the .requiresAction status and return the user to the appropriate screen after authentication.
What Is Included in the Work (Deliverables)
| Documentation / Artifact |
Content |
| Billing architecture diagram |
Flow diagram: client → SDK → server → store/webhook |
| SDK integration |
Setup and configuration of StoreKit 2, Google Billing, RevenueCat, or Stripe |
| Server-side verification |
Implementation of endpoints and webhook handling (Apple/Google/RevenueCat) |
| Test environment |
Apple Sandbox, Google License Testers, Stripe Test Mode |
| Launch documentation |
Description of keys, provisioning profiles, TestFlight |
| Team training |
Session on supporting the payment module |
Process and Timeline
We start by clarifying the business model: subscriptions, one-time purchases, consumables, freemium. The architecture depends on this. Testing IAP requires Sandbox accounts (Apple) and License Testers (Google)—this is a separate environment setup.
Apple's Sandbox behaves differently from production: subscriptions renew every 5 minutes instead of monthly, inGracePeriod works differently. It is essential to test scenarios: trial expiration, cancellation, billing retry, refund.
| Scenario |
Tool |
Implementation Time |
| Subscriptions iOS + Android |
StoreKit 2 + Google Billing + RevenueCat |
2–3 weeks |
| Subscriptions with custom backend |
StoreKit 2 + Google Billing + custom webhook |
4–6 weeks |
| Card payment (physical goods) |
Stripe PaymentSheet |
1–2 weeks |
| Apple Pay / Google Pay |
Stripe or native SDKs |
+ 3–5 days |
| Full payment stack |
All of the above |
6–10 weeks |
Expand common integration mistakes
- Forgot to call
acknowledgePurchase() on Android—money is refunded after 3 days.
- Did not handle
inGracePeriod—loyal users are blocked from access.
- Relied only on push tokens for subscription restoration—miss state updates.
- Used production keys in TestFlight—real charges occur.
The cost is calculated individually based on the set of tools and complexity of server-side logic. On average, we fit within a budget for a typical integration, but the savings from preventing errors and churn offset this investment within a few months.
Get a consultation for your project—contact us. We will help you choose the optimal payment architecture that passes store reviews and does not break under peak loads.