A user wants to deposit USDC and earn 8% APY in crypto. Yield farming and staking are popular passive income methods. In practice, we face a choice: a custodial platform with centralized management, integration with a DeFi protocol (Aave, Compound, Yearn), or liquidity pools (Uniswap V3, Curve). The architectural decision is made at the very beginning and determines the entire development stack. We have implemented projects for all three scenarios — let's discuss the nuances.
How to Choose the Architecture for Crypto Savings?
Custodial. The user transfers crypto to the platform's wallet, and the platform manages the capital itself. The mobile app is classic fintech: REST API, JWT authentication, balance, transaction history, withdrawal. No Web3 on the client. Simple to develop, requires licenses and compliance.
DeFi Integration. The user directly interacts with a smart contract through their own non-custodial wallet. The mobile app becomes an interface to the protocol. Requires a Web3 stack: web3.swift / web3j, WalletConnect v2 for transaction signing. The user pays gas themselves.
Hybrid. The platform holds a smart account (Account Abstraction / ERC-4337), the user does not interact with keys directly. The platform sponsors gas through a Paymaster. Best UX of the three — the user is unaware of the blockchain.
| Characteristic |
Custodial |
DeFi Integration |
Hybrid |
| Asset control |
Platform |
User |
Smart account |
| Fees |
Platform |
User (+gas) |
Platform (sponsors gas) |
| UX complexity |
Low |
High (needs ETH) |
Low |
| Security |
Centralized |
Non-custodial |
Key abstraction |
| Regulatory risk |
High |
Low |
Medium |
Why the Hybrid Approach Has 2x Better Conversion Than Custodial?
Based on our data, hybrid apps show 40–60% higher deposit conversion than custodial ones, due to the absence of the "trust us" barrier. Meanwhile, DeFi integration without account abstraction scares away 70% of beginners because they need to buy ETH for gas. Hybrid is the sweet spot. For example, in one project users saved up to $0.50 per transaction thanks to gas sponsorship.
APY Dashboard and Real-Time Yield
The main screen of the app shows the current balance + accrued interest. Accrued interest must update in real time. This is a UX trick: the user sees the balance growing while looking at the screen. For a $10,000 deposit at 8% APY, monthly income is about $67 — these figures are displayed as an animated chart. For comparison, a $5,000 deposit in Yearn USDC Vault with 9% APY yields about $37 per month.
For DeFi protocols like Aave V3, interest is compounded continuously via aToken — a token whose balance automatically increases. We calculate the current balance via AToken.balanceOf(userAddress), calling every 30 seconds through JSON-RPC:
// Получение актуального баланса aToken через web3.swift
class AaveBalanceService {
let web3 = Web3(rpcURL: "https://mainnet.infura.io/v3/YOUR_KEY")
func fetchATokenBalance(userAddress: EthereumAddress, aTokenAddress: EthereumAddress) async throws -> BigUInt {
let contract = try web3.eth.Contract(json: aTokenABI, abiKey: nil, address: aTokenAddress)
let result = try await contract["balanceOf"]?(userAddress).call()
return result?["_balance"] as? BigUInt ?? 0
}
}
On the UI: balance in USD converted at the current rate. Rate — WebSocket from Binance or CoinGecko with 15-second caching.
Yield Strategies and Vaults
Yearn Finance-like products offer "strategies" — automatic allocation of funds between protocols for maximum APY. In the mobile app, this is a list of deposits with different risk and yield:
| Strategy |
APY |
Risk |
Base Asset |
| Aave USDC Supply |
4.2% |
Low |
USDC |
| Curve 3pool LP |
6.8% |
Medium |
USDC/USDT/DAI |
| Yearn USDC Vault |
9.1% |
Medium |
USDC |
| Uniswap V3 USDC/ETH |
14–40% |
High (IL) |
USDC + ETH |
Impermanent Loss for LP positions — a separate screen with a calculator. The user must understand the risks before entering a liquidity pool. Without this, we do not release the app — it is a mandatory part of our work.
Withdrawals and Lock-Up Periods
Some strategies have lock-ups — funds cannot be withdrawn before a certain date. This must be shown explicitly before deposit. On the withdrawal screen — a progress bar with remaining time and unlock date.
Partial withdrawal is an important function. The user specifies the withdrawal amount, and the app calculates the number of shares for withdraw(shares, receiver, owner) according to ERC-4626 standard:
// ERC-4626 Vault: конвертация assets → shares
const shares = await vault.convertToShares(assetsToWithdraw);
const tx = await vault.withdraw(assetsToWithdraw, userAddress, userAddress);
Push Notifications and Analytics
Weekly summary: how much earned that week, current APY. Push when significant APY change (>2%). Reminder when unlock date approaches.
Firebase Analytics to track the funnel — from registration to first deposit. This helps identify problematic UX steps.
Security
Detailed security measures
For custodial platforms — Keychain (iOS) / EncryptedSharedPreferences (Android) for session token storage. Biometric authentication for balance viewing and withdrawals. Withdrawal confirmation via email/SMS OTP in addition to biometrics.
For non-custodial — seed phrase is never stored in the app; only via WalletConnect or Hardware Wallet (Ledger Live SDK).
What Our Work Includes
We deliver the full package:
- Technical documentation: architecture choice, ERD, protocol integration scheme
- Source code for iOS and Android mobile apps with comments
- Server side (balances, APY, history) on Node.js/Python
- Integration with selected DeFi protocols (Aave, Compound, Uniswap, etc.)
- Push notifications via FCM/APNs
- Assistance with publishing on App Store and Google Play (review pass)
- Training of the client's team on code and infrastructure
- Support for 1 month after release
Order the development of a crypto savings mobile app today.
Stages and Timeline
- Analysis and architecture selection (1–2 weeks)
- Design and server side (2–3 weeks)
- DeFi integration (if required) (2–4 weeks)
- Mobile client iOS + Android (4–6 weeks)
- Notifications and KYC (1–2 weeks)
- Smart contract audit (1 week)
8–14 weeks depending on architecture. DeFi integration with multiple protocols and Vault strategies — closer to 14 weeks. Cost is calculated individually after requirements analysis. Get a consultation on architecture today — contact us, we will evaluate your project in one business day.
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.