Реалізація відслідковування статусу транзакції в мобільному криптокошельку
Транзакція відправлена — і користувач дивиться на "Pending" статус наступні 10 хвилин. Це нормально для блокчейну, але ненормально — не отримати сповіщення, коли транзакція підтвердилася або зафейлилася. Відслідковування статусу транзакції — це real-time завдання з кількома рівнями: polling блокчейну, WebSocket-з'єднання, push-сповіщення при фінальному статусі.
Життєвий цикл транзакції
Ethereum-транзакція проходить через стани: submitted → pending (mempool) → confirmed (1 confirmation) → finalized (12+ confirmations) → failed (reverted / dropped).
Bitcoin: mempool → 1 confirmation → 6 confirmations (finalized).
Solana — значно швидше: слоти по ~400ms, processed → confirmed → finalized за секунди.
На клієнті потрібно показувати поточний стан та кількість підтверджень.
Стратегії отримання статусу
Polling через node RPC. Найпростіший варіант — перевіряти статус транзакції кожні N секунд:
// iOS — polling статусу ETH транзакції через JSON-RPC
func pollTransactionStatus(txHash: String) async throws -> TransactionStatus {
let params: [AnyEncodable] = [txHash, false]
let receipt = try await ethClient.call(method: "eth_getTransactionReceipt", params: params)
if receipt == nil {
return .pending // Ще в mempool
}
let confirmations = try await getConfirmationsCount(txHash: txHash)
return confirmations >= requiredConfirmations ? .confirmed : .confirmingWith(count: confirmations)
}
Polling кожні 3–5 секунд при відкритому екрані — нормально. У фоні — лише через silent push або WebSocket.
WebSocket підписка через Alchemy / Infura / QuickNode. Більш ефективний підхід:
// Бекенд — підписка на подію через Alchemy WebSocket
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const web3 = createAlchemyWeb3(process.env.ALCHEMY_WS_URL);
async function watchTransaction(txHash, userId) {
const subscription = web3.eth.subscribe('newBlockHeaders');
subscription.on('data', async (blockHeader) => {
const receipt = await web3.eth.getTransactionReceipt(txHash);
if (receipt) {
subscription.unsubscribe();
await updateTransactionStatus(txHash, receipt.status ? 'confirmed' : 'failed');
await sendPushNotification(userId, txHash, receipt.status);
}
});
}
Alchemy та Infura також надають webhooks на підтвердження транзакції — бекенд не утримує постійне WS-з'єднання.
Мобільний клієнт: real-time UI
На клієнті — WebSocket з'єднання з власним бекендом для real-time статусу:
// Android — підписка на статус транзакції через WebSocket
class TransactionStatusSocket(
private val token: String,
private val okHttpClient: OkHttpClient
) {
fun subscribe(txHash: String): Flow<TransactionStatus> = callbackFlow {
val ws = okHttpClient.newWebSocket(
Request.Builder()
.url("wss://api.yourwallet.app/ws/tx/$txHash")
.header("Authorization", "Bearer $token")
.build(),
object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
val status = json.decodeFromString<TransactionStatusUpdate>(text)
trySend(status.status)
if (status.isFinal) close()
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
close(t)
}
}
)
awaitClose { ws.close(1000, "Subscription ended") }
}
}
Progress індикатор підтверджень
Для ETH — відображаємо прогрес до 12 підтверджень:
struct ConfirmationProgressView: View {
let current: Int
let required: Int = 12
var body: some View {
VStack(alignment: .leading, spacing: 4) {
ProgressView(value: Double(min(current, required)), total: Double(required))
.tint(current >= required ? .green : .orange)
Text("\(current)/\(required) підтверджень")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
Дизайн транзакції в різних статусах:
-
pending— анімований spinner, жовтий акцент -
confirming— progress bar з кількістю підтверджень -
confirmed— зелена галочка, анімація -
failed— червоний, причина помилки якщо доступна (revert reason з receipt)
Push-сповіщення при зміні статусу
При фінальному статусі — push користувачу:
{
"title": "Транзакція підтвердилася",
"body": "0.05 ETH відправлено на 0x742d...3B8C",
"data": {
"screen": "transaction_detail",
"tx_hash": "0xabc123...",
"status": "confirmed"
}
}
Для failed транзакції — окремий шаблон з описанням причини якщо вона відома (revert reason або "dropped from mempool").
Обробка dropped транзакцій
Транзакція може "зникнути" з mempool якщо gas price був занадто низький. Через 15–30 хвилин без підтвердження — транзакція вважається dropped. Потрібно це виявити:
suspend fun checkDroppedTransactions() {
val pendingTxs = transactionDao.getPendingOlderThan(minutes = 20)
pendingTxs.forEach { tx ->
val receipt = ethClient.getTransactionReceipt(tx.hash)
if (receipt == null) {
transactionDao.updateStatus(tx.hash, TransactionStatus.DROPPED)
pushService.notifyUser(tx.userId, "Транзакція не попала в блок", tx.hash)
}
}
}
Графік
Реалізація відслідковування статусу транзакції з WebSocket real-time обновленнями, progress індикатором підтверджень, push при фінальному статусі та обробкою dropped транзакцій — 6–10 робочих днів.







