Реалізація сканування QR-коду крипто-адреси у мобільному додатку
Сканер QR — точка входу адреси одержувача. Завдання не тільки розпізнати QR, але й розпарсити URI-схему, провалідувати адресу та автоматично заповнити поля форми відправки. Помилка в парсингу — користувач вводить неверні дані, втрачає средства.
Вибір бібліотеки сканування
iOS — AVFoundation з AVCaptureMetadataOutput нативно. Для більш зручного API — VisionKit (iOS 16+) з DataScannerViewController. Останній вимагає менше кода та підтримує одночасно QR та текст.
// iOS 16+ — DataScannerViewController
import VisionKit
let scanner = DataScannerViewController(
recognizedDataTypes: [.barcode(symbologies: [.qr])],
qualityLevel: .balanced,
recognizesMultipleItems: false,
isHighFrameRateTrackingEnabled: false,
isPinchToZoomEnabled: true,
isGuidanceEnabled: true,
isHighlightingEnabled: true
)
scanner.delegate = self
present(scanner, animated: true)
try? scanner.startScanning()
Android — ML Kit Barcode Scanning (com.google.mlkit:barcode-scanning). Працює on-device, без інтернету, швидше за ZXing на сучасних пристроях.
// Android — ML Kit сканування
val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
val scanner = BarcodeScanning.getClient(options)
// Передати ImageProxy з CameraX у scanner.process()
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull()?.rawValue?.let { parseQRContent(it) }
}
Парсинг URI та автозаповнення
Сканер повертає строку. Може бути чиста адреса, може бути URI за BIP-21/EIP-681. Парсер повинен розуміти обидва випадки.
// Android — парсинг крипто-URI
fun parseQRContent(content: String): QRParseResult {
// Чиста Ethereum адреса (EIP-55 checksum або lowercase)
if (content.matches(Regex("^0x[0-9a-fA-F]{40}$"))) {
return QRParseResult(chain = "ethereum", address = content)
}
// EIP-681: ethereum:0xAddress@chainId?value=...
if (content.startsWith("ethereum:")) {
val uri = URI(content)
val address = uri.schemeSpecificPart.substringBefore("@").substringBefore("?")
val chainId = uri.schemeSpecificPart.substringAfter("@").substringBefore("?").toLongOrNull() ?: 1
val params = parseQueryParams(uri.query)
return QRParseResult(
chain = "ethereum",
address = address,
chainId = chainId,
value = params["value"],
contractAddress = params["address"] // для ERC-20 transfer
)
}
// BIP-21: bitcoin:address?amount=...
if (content.startsWith("bitcoin:")) {
val address = content.removePrefix("bitcoin:").substringBefore("?")
val amount = parseQueryParams(content.substringAfter("?"))["amount"]
return QRParseResult(chain = "bitcoin", address = address, amount = amount)
}
return QRParseResult(error = "Невідомий формат")
}
Валідація адреси після парсингу
Після вилучення адреси — перевірка коректності до заповнення поля:
- Ethereum/EVM: checksum через EIP-55, довжина 42 символи (з
0x) - Bitcoin: декодувати base58check або bech32 — невалідна checksum вернеться помилкою
- Solana: base58, 32 байти (43–44 символи)
Невалідна адреса — показати помилку прямо на екрані сканера, не дозволяти переходити далі.
Попередження про підмену
Після вставки адреси з QR показувати скорочений вид (перші 6 + останні 4 символи) з проханням візуально сверити. Це займає 2 секунди, але запобігає втратам від qr-hijacking атак (підміна QR у фізичному просторі).
Часовий графік: 1 день: сканування через ML Kit / DataScannerViewController, парсинг BIP-21 та EIP-681 URI, валідація адреси, автозаповнення полів форми відправки.







