Реалізація отправки криптовалюти з мобільного гаманця
Екран відправки—один із найкритичніших у мобільному гаманці. Тут користувач вводить адресу отримувача, суму та підтверджує транзакцію. Помилка на будь-якому кроці—невідворотна втрата коштів. Тому логіка відправки будується з явним упором на валідацію, захист від випадкового підтвердження та прозоре відображення комісій.
Валідація адреси до відправки
Найчастіша причина втрати коштів—невалідна або некоректна адреса. Для Ethereum та EVM-сумісних мереж обов'язкова перевірка checksum по EIP-55:
// iOS — web3swift
import web3swift
let address = EthereumAddress(inputString)
guard address != nil else { /* показати помилку */ }
// Android — web3j
import org.web3j.crypto.WalletUtils
val isValid = WalletUtils.isValidAddress(inputAddress)
Для Bitcoin потрібно окремо розбирати формат—P2PKH, P2SH або bech32 (SegWit). Бібліотека BitcoinKit (iOS) та bitcoinj (Android) покривають всі три. Solana-адреси—base58, 32 байти; SolanaSwift надає PublicKey(string:) з виключенням при некоректному введенні.
EVM-адреси в нижньому регістрі та адреси з checksum—різні рядки, але обидва валідні. Відображати користувачу краще checksum-версію.
Побудова та підписання транзакції
Флоу відправки:
- Користувач вводить адресу та суму.
- Програма запитує актуальний
gasPrice/maxFeePerGasчерезeth_gasPriceабоeth_feeHistory. - Оцінює
gasLimitчерезeth_estimateGasз параметрами транзакції—не захардкоживати 21000, якщо це не plain ETH transfer. - Показує итогову комісію в USD по актуальному курсу.
- Користувач підтверджує—програма підписує транзакцію приватним ключем локально.
- Відправка підписаного hex через
eth_sendRawTransaction.
Приватний ключ ніколи не покидає пристрій. Для зберігання—iOS Keychain з kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, Android Keystore з KeyPairGenerator та флагом setUserAuthenticationRequired(true).
// Android — підписання через web3j
val credentials = Credentials.create(privateKey)
val rawTransaction = RawTransaction.createEtherTransaction(
nonce, gasPrice, gasLimit, toAddress, value
)
val signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials)
val hexValue = Numeric.toHexString(signedMessage)
web3j.ethSendRawTransaction(hexValue).send()
UX підтвердження та захист від помилок
Екран підтвердження повинен містити повну адресу отримувача (не скорочену), суму, мережу та итогову комісію. Кнопку «Відправити»—не поруч з «Скасування», краще внизу з явним відступом. На iOS уместен UIImpactFeedbackGenerator при успішній відправці—тактильний відклик знижує тривожність.
Після eth_sendRawTransaction програма отримує txHash. Статус транзакції відслідковується через eth_getTransactionReceipt в циклі з затримкою (polling щих 3–5 секунд) або через WebSocket підписку eth_subscribe("newHeads"). Показувати користувачу посилання на Etherscan / BscScan / Solscan—обов'язково.
Типові помилки реалізації
Підмена адреси з буфера обміну—реальний вектор атаки. Програма повинна порівнювати перші та останні 4 байти вставленої адреси з тим, що користувач бачить на екрані, та при невідповідності—показувати попередження. Ряд гаманців додатково показує візуальний ідентикон адреси (Blockies або Jazzicon).
Nonce management: якщо користувач відправив транзакцію з pending-статусом, наступна транзакція повинна використовувати nonce + 1. Інакше друга транзакція зависнеме або заменить першу. Зберігаємо nonce локально, синхронізуємо з eth_getTransactionCount(..., "pending") перед кожною відправкою.
Сроки: 3–5 днів: екран введення з валідацією адреси та суми, побудова транзакції, підписання, відображення комісій, екран підтвердження, трекінг статусу. ERC-20 transfer потребує додатково дня на ABI-encode даних transfer(address,uint256).







