Реалізація вбудованого DApp-браузера в мобільному криптогаманці
Вбудований DApp-браузер — один із найскладніших компонентів криптогаманця. Він завантажує довільні веб-застосунки, впроваджує провайдер window.ethereum, обробляє підписи транзакцій і при цьому не має стати способом атаки на активи користувача. Наш досвід у мобільній розробці та десятки успішно запущених продуктів дозволяють виконати таку інтеграцію під ключ із гарантією безпеки. Типовий кейс: користувач відкриває Uniswap у браузері гаманця, DApp перевіряє наявність window.ethereum, викликає eth_requestAccounts — і одразу бачить список своїх акаунтів. Якщо інжекція провайдера виконана неправильно, DApp або не виявить гаманець, або буде вразливим до фішингу. Приклад із практики: в одному проєкті ми виявили, що на Android провайдер інжектувався після завантаження сторінки, і DApp не встигали зареєструватися, що призводило до помилки "No Ethereum provider found". Рішення — попереднє завантаження скрипта до onPageStarted.
Основа браузера — нативний WebView з інжекцією JavaScript-провайдера. На iOS це WKWebView, на Android — WebView з addJavascriptInterface. MetaMask, Trust Wallet та Coinbase Wallet реалізують цей патерн однаково. Провайдер window.ethereum повинен реалізовувати інтерфейс EIP-1193: метод request(method, params) для всіх RPC-запитів. DApp звертається до цього об'єкта, а нативний код обробляє запити.
Архітектура та інжекція провайдера
Схема роботи:
DApp (JS) → window.ethereum.request({method: 'eth_sendTransaction'})
→ постMessage в нативний шар
→ нативний код показує діалог підтвердження
→ користувач схвалює/відхиляє
→ відповідь повертається в JS через postMessage
→ Promise вирішується в DApp
Інжекція на iOS (WKWebView). Скрипт провайдера інжектуємо через WKUserScript з injectionTime: .atDocumentStart. Критично — саме atDocumentStart, інакше DApp може перевірити window.ethereum до інжекції та вирішити, що гаманця немає.
let providerScript = loadProviderJS() // Читаємо з бандлу
let userScript = WKUserScript(
source: providerScript,
injectionTime: .atDocumentStart,
forMainFrameOnly: false
)
webView.configuration.userContentController.addUserScript(userScript)
webView.configuration.userContentController.add(self, name: "ethereum")
JS-провайдер надсилає повідомлення через webkit.messageHandlers.ethereum.postMessage({...}). Нативний код отримує в userContentController(_:didReceive:). Відповіді передаємо назад через webView.evaluateJavaScript("window.ethereum._resolveResponse(\(id), \(result))").
Інжекція на Android.
webView.addJavascriptInterface(EthereumProvider(this), "AndroidEthereum")
webView.settings.javaScriptEnabled = true
На Android JS-інтерфейс працює синхронно, що створює проблему: методи @JavascriptInterface не можуть повертати Promise. Обходимо через callback-патерн: JS викликає AndroidEthereum.request(id, method, paramsJson), нативний код в результаті викликає webView.evaluateJavascript("resolveCallback($id, $result)", null).
Важливо: addJavascriptInterface потенційно небезпечний. Методи, анотовані @JavascriptInterface, видні для всього JavaScript на сторінці, включаючи шкідливі iframe. Анотуйте лише необхідні методи і ніколи не додавайте інтерфейс із широким API.
Порівняння платформ
| Параметр | iOS (WKWebView) | Android (WebView) |
|---|---|---|
| Інжекція коду | WKUserScript, інжект до завантаження | addJavascriptInterface, гонка умов |
| Асинхронність | через postMessage, native callback | потрібен callback-патерн |
| Безпека | ізоляція вбудована | потрібна додаткова перевірка |
| Швидкість завантаження | на 30–50% вища | залежить від версії Chromium |
Як забезпечити безпеку при інжекції провайдера?
Безпека — головний пріоритет. Основні заходи:
- Ізоляція сесії. Кожна DApp повинна мати окремий cookie-jar та localStorage. Не дозволяйте DApp A читати дані DApp B. На iOS — окремі
WKWebViewConfigurationтаWKWebsiteDataStoreдля кожної вкладки. - Фішинг-захист. Перевіряємо SSL-сертифікат, показуємо URL в адресному рядку, який користувач не може приховати, блокуємо
alert()таprompt()з JS. На iOS обробляємоwebView(_:runJavaScriptAlertPanelWithMessage:)та замінюємо нативнимUIAlertController. -
eth_signTypedData_v4(EIP-712). Це структуровані дані — DApp просить підписати типізований об'єкт. Потрібно розпарсити JSON schema та показати користувачеві, що саме підписується в людино-читаному вигляді. Підписання наосліп — ризик для користувача.
Чому важливо ізолювати сесії DApp?
Ізоляція запобігає витоку даних між DApp. Якщо знехтувати цим, шкідливе DApp зможе прочитати збережені паролі або ключі іншого DApp. На практиці ми використовуємо окремі WKWebsiteDataStore для кожної вкладки на iOS та окремі каталоги для WebView на Android.
Реалізація window.ethereum провайдера
Мінімальна реалізація підтримує методи EIP-1193:
// Інжектований провайдер (спрощено)
window.ethereum = {
isMetaMask: true, // багато DApp перевіряють цей прапорець
chainId: '0x1',
selectedAddress: null,
request: async function({ method, params }) {
return new Promise((resolve, reject) => {
const id = generateId();
pendingRequests[id] = { resolve, reject };
webkit.messageHandlers.ethereum.postMessage({ id, method, params });
});
},
on: function(event, handler) {
// chainChanged, accountsChanged, connect, disconnect
eventHandlers[event] = eventHandlers[event] || [];
eventHandlers[event].push(handler);
}
};
Обов'язкові методи: eth_requestAccounts, eth_accounts, eth_chainId, eth_sendTransaction, personal_sign, eth_signTypedData_v4, wallet_switchEthereumChain.
Multi-tab та продуктивність
Браузер з однією вкладкою — мінімум. Реалізуємо:
- Вкладки з ізольованими даними
- Історію переглядів (опціонально, багато користувачів гаманців надають перевагу конфіденційності)
- Закладки для часто використовуваних DApp
- Список популярних DApp (curated) для onboarding
На iOS кілька WKWebView можна тримати в пам'яті — вони ліниві, поки не видимі. На Android WebView важкий, для економії пам'яті знищуємо WebView неактивної вкладки та відновлюємо URL при поверненні.
Продуктивність WebView залежить від платформи. На застарілих Android-пристроях (WebView на базі Chromium 80) складні DeFi DApp можуть гальмувати. Моніторимо через WebViewClient.onPageStarted/onPageFinished, показуємо прогрес-бар. Попереднє завантаження WebView при старті застосунку скорочує cold-start час з ~800 мс до ~200 мс.
Що входить в роботу
| Етап | Що отримуєте |
|---|---|
| Базовий WebView | Навігація, адресний рядок, прогрес-бар |
| Інжекція провайдера | Підтримка EIP-1193 методів: requestAccounts, accounts, chainId, sendTransaction, personal_sign, signTypedData_v4 |
| Діалог транзакції | Чите відображення, підписання, відправка через RPC |
| Multi-tab | Ізольовані сесії, вкладки, історія, закладки |
| Безпека | Фішинг-захист, SSL-валідація, ізоляція, аудит |
| Документація та код-рев'ю | Повна документація API, керівництво з інтеграції |
Процес розробки
- Базовий WebView з навігацією, адресним рядком, прогрес-баром
- Інжекція провайдера та підтримка
eth_requestAccounts,eth_accounts,eth_chainId - Діалог транзакції — відображення, підписання, відправка через RPC
- Розширені методи —
personal_sign,eth_signTypedData_v4,wallet_switchEthereumChain - Безпека — ізоляція, фішинг-захист, аудит
- Multi-tab та UX-полірування
Терміни: базовий браузер з eth_sendTransaction — 3–4 тижні. Повноцінний браузер з multi-tab, EIP-712 відображенням, security-аудитом — 2–3 місяці. Вартість розраховується індивідуально. Повторне використання компонентів дозволяє заощадити до 40% бюджету при типовій інтеграції.
Отримайте консультацію по вашому проєкту: ми оцінимо архітектуру та запропонуємо оптимальне рішення під ключ. Зв'яжіться з нами, щоб обговорити деталі.







