Реализация сканирования 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 секунды, но предотвращает потери от qrshing-атак (подмена QR в физическом пространстве).
Сроки: 1 день: сканирование через ML Kit / DataScannerViewController, парсинг BIP-21 и EIP-681 URI, валидация адреса, автозаполнение полей формы отправки.







