Solana Mobile Stack (SMS) дає змогу створювати нативні мобільні застосунки, які взаємодіють із блокчейном безпосередньо з пристрою, оминаючи веб-обгортки та глибокі посилання на гаманці. Цей довідник описує архітектуру SDK, реальні обмеження мобільного середовища, підходи до оптимізації мережевого взаємодії та типові інженерні проблеми, з якими стикаються команди при виведенні мобільних Solana-застосунків у production.

Архітектура Solana Mobile SDK

SDK побудований навколо двох ключових компонентів: клієнтської бібліотеки для взаємодії з мережею та протоколу Mobile Wallet Adapter (MWA) для безпечної комунікації з гаманцем. Архітектура спроєктована так, щоб розділити відповідальність між застосунком (який формує транзакції) та гаманцем (який їх підписує).

Інтеграція з Android та iOS

На Android SDK розповсюджується як стандартний набір Artecact (AAR), який підключається через Gradle. Він забезпечує нативний доступ до MWA через Android Intent та Binder IPC, що дозволяє застосунку та гаманцю обмінюватися даними в межах одного пристрою без мережевого маршрутизації.

Підтримка iOS реалізована через Swift Package Manager. Проте архітектурна різниця істотна: на iOS відсутній прямий аналог Android Binder, тому комунікація між застосунком та гаманцем відбувається через App Groups та спільні контейнери даних. Це накладає додаткові обмеження на розмір переданих даних та вимагає явної конфігурації entitlements у Xcode-проєкті.

Передумови для інтеграції:

  • Android: minSdk 24+, Kotlin 1.8+ або Java 11+, наявність Solana Mobile Stack AAR у локальному репозиторії або залежність від опублікованого артефакту
  • iOS: iOS 15.0+, Xcode 15+, налаштований App Group для спільного доступу з гаманцем

Очікуваний результат інтеграції: застосунок здатен ініціювати сесію з гаманцем, формувати транзакції, передавати їх на підпис та отримувати підписані результати без переходу користувача між застосунками.

Ризик: використання застарілих версій AAR або SPM-пакету може призвести до несумісності з поточними версіями гаманців. Перевіряйте актуальність версій у репозиторії Solana Mobile перед кожним релізом.

Мобільний гаманець та Local Wallet Adapter

Local Wallet Adapter (LWA) — це реалізація MWA, призначена для сценаріїв, коли гаманець встановлено на тому самому пристрої, що й застосунок. На пристроях Saga та Seek LWA інтегровано на рівні системного сервісу, що дає переваги в швидкості та надійності порівняно з рішеннями на базі глибоких посилань.

Ключова відмінність LWA від Remote Wallet Adapter: відсутність мережевого хопу. Усе взаємодія відбувається через локальний IPC, що усуває клас вразливостей, пов'язаних із перехопленням трафіку між застосунком та гаманцем.

Для перевірки коректності інтеграції з LWA виконайте такі кроки:

  1. Ініціюйте з'єднання з гаманцем через MobileWalletAdapterClient
  2. Сформуйте тестову транзакцію (наприклад, передачу мінімальної кількості SOL між двома PDA-адресами на devnet)
  3. Перевірте, що підписана транзакція повертається без переходу користувача за межі застосунку
  4. Відправте підписану транзакцію на RPC та переконайтеся, що вона підтверджена

Відкат: якщо LWA-з'єднання нестабільне, реалізуйте fallback на Remote Wallet Adapter через стандартний протокол MWA з глибокими посиланнями. Це збільшить затримку, але збереже функціональність на пристроях без нативної інтеграції гаманця.

Обмеження мобільного середовища

Мобільні пристрої принципово відрізняються від серверних та десктопних середовищ. Ігнорування цих обмежень — найчастіша причина нестабільності Solana-застосунків у production.

Термічне тротлінг. Під час інтенсивної взаємодії з блокчейном (наприклад, масове підписання транзакцій або складна десеріалізація даних) процесор мобільного пристрою може переходити в термічний тротлінг. Це призводить до непередбачуваного збільшення затримки операцій. Не покладайтеся на стабільність часу виконання криптографічних операцій — завжди використовуйте асинхронні виклики з таймаутами.

Обмеження пам'яті. Десеріалізація великих Account-даних (особливо для програм із великими state-масивами) може спричинити OOM на пристроях із 4–6 ГБ RAM. Реалізуйте потокову десеріалізацію або завантажуйте лише необхідні частини даних через getAccountInfo з параметром offset.

Життєвий цикл застосунку. Операційна система може призупинити або знищити застосунок у будь-який момент. Транзакції, що знаходяться в процесі підписання або відправки на момент призупинення, повинні мати механізм відновлення. Зберігайте серіалізовані транзакції в локальній базі даних перед відправкою та перевіряйте їхній статус при відновленні сесії.

Фонові обмеження. На Android 12+ та iOS 15+ фонове виконання мережевих запитів суттєво обмежене. Не намагайтеся відправляти транзакції або слухати WebSocket-підписки у фоні — це ненадійно. Використовуйте foreground service (Android) або background tasks з жорсткими часовими рамками (iOS) лише для критичних операцій, і завжди майте план повторної синхронізації при поверненні застосунку на передній план.

Оптимізація для мобільних мереж

Мобільні мережі характеризуються високою варіативністю затримки, періодичними розривами з'єднання та значними втратами пакетів. Стандартні підходи до взаємодії з Solana RPC, розроблені для стабільних проводових з'єднань, у мобільному середовищі працюють ненадійно.

Управління WebSocket-підписками. Не тримайте постійні WebSocket-з'єднання відкритими без необхідності. Реалізуйте логіку повторного підключення з експоненційною затримкою (exponential backoff) та обмеженням кількості спроб. При втраті з'єднання перемикайтеся на polling через HTTP-запити до відновлення WebSocket-каналу.

Симуляція перед відправкою. Завжди викликайте simulateTransaction перед sendTransaction. У мобільній мережі відправка транзакції, що гарантовано впаде через помилку в логіці, — це марна витрата трафіку та часу користувача. Симуляція дозволяє виявити більшість помилок локально на RPC-вузлі без включення транзакції в блок.

Компактифікація транзакцій. Уникайте зайвих інструкцій у транзакціях. Кожна додаткова інструкція збільшує розмір транзакції та підвищує ймовірність відхилення через обмеження розміру блоку. Перевіряйте розмір серіалізованої транзакції перед відправкою — він не повинен перевищувати 1232 байти.

Кешування даних акаунтів. Не завантажуйте одні й ті ж дані повторно при кожному відкритті екрана. Реалізуйте локальний кеш із чіткою політикою інвалідації: на основі slot (параметр minContextSlot) або з використанням Clock-акаунта для визначення актуальності. Це особливо важливо для даних, які рідко змінюються (конфігурація програми, метадані токенів).

Обробка таймаутів. Встановлюйте диференційовані таймаути для різних типів операцій. Симуляція транзакції зазвичай завершується за 1–3 секунди, відправка — за 2–5 секунд, підтвердження — залежить від поточного завантаження мережі (може сягати 30+ секунд при congestion). Не використовуйте єдиний таймаут для всіх операцій.

Типові проблеми мобільної розробки на Solana

Нижче наведено проблеми, з якими інженерні команди стикаються регулярно, із вказанням фактичних причин та рекомендованими шляхами вирішення.

Збій підписання транзакції через зміну стану гаманця. Фактична причина: між моментом формування транзакції та моментом підписання стан відповідних акаунтів змінився (інша транзакція була підтверджена). Транзакція стає невалідною, але гаманець може не повідомити про це на рівні MWA — помилка виникає лише при відправці на RPC. Рішення: додавайте перевірку актуальності блокхешу (recentBlockhash) перед підписанням та реалізуйте повторне формування транзакції з оновленим блокхешем при виявленні застарілого стану.

Відхилення транзакції з помилкою "preinstruction simulation failed" після успішної локальної симуляції. Фактична причина: між локальною симуляцією та фактичним включенням у блок інший валідатор вже змінив стан. У періоди високого завантаження мережі цей проміжок може становити сотні мілісекунд. Рішення: використовуйте computeBudgetConfig з явно вказаним computeUnitLimit та розгляньте можливість підвищення computeUnitPrice (priority fee) для мобільних транзакцій, оскільки користувачі мобільних застосунків менш толерантні до повторних спроб.

Зависання інтерфейсу при десеріалізації великих об'єктів. Фактична причина: десеріалізація складних структур (наприклад, великих OrderBook-акаунтів або масивів NFT-метаданих) виконується на головному потоці. Рішення: усю десеріалізацію даних, отриманих з RPC, виконуйте на фонових потоках (Coroutines Dispatchers.IO на Android, Swift Concurrency Task на iOS). На головний потік передавайте лише готові об'єкти моделі даних.

Втрата транзакцій при переключенні мереж (Wi-Fi до LTE та навпаки). Фактична причина: при зміні мережевого інтерфейсу операційна система може скинути існуючі TCP-з'єднання, зокрема активні HTTP-запити до RPC. Якщо транзакція вже була відправлена та отримала позитивну відповідь, але з'єднання розірвалося до отримання підтвердження через WebSocket, застосунок може вважати транзакцію втраченою. Рішення: після відправки транзакції зберігайте її підпис (transaction signature) локально та при відновленні з'єднання перевіряйте статус через getSignatureStatuses, перш ніж повторно відправляти. Ніколи не відправляйте одну й ту саму транзакцію двічі без перевірки — це може призвести до дублювання операцій.

Невідповідність балансів між гаманцем та застосунком. Фактична причина: гаманець та застосунок можуть використовувати різні RPC-вузли з різною затримкою синхронізації. Рішення: для критичних операцій (наприклад, відображення доступного балансу перед відправкою) використовуйте той самий RPC-endpoint, що й для відправки транзакцій, або явно запитуйте актуальний стан через getAccountInfo з commitment: "confirmed" замість "processed".

Для кожної з описаних проблем рекомендується реалізувати відповідний моніторинг — логування з рівнем транзакційного підпису, таймстемпами та ідентифікаторами мережевого інтерфейсу. Це суттєво прискорює діагностику в production. Детальніше про інструменти моніторингу читайте у розділі Як моніторити Solana-застосунок у production.

Джерела