Реалізація історії транзакцій у мобільному крипто-кошельку
Історія транзакцій у крипто-кошельку — це не просто список записів із бази даних. Це агрегація даних з кількох блокчейнів, парсинг raw транзакцій через RPC-ноди або індексатори, декодування input data смарт-контрактів, розрахунок fiat-еквівалентів на дату транзакції. При цьому все повинно завантажуватися швидко та працювати офлайн.
Джерела даних транзакцій
Отримати транзакції напряму через RPC-ноду (eth_getTransactionsByAddress не існує в стандартному JSON-RPC) — неможливо. Ethereum-нода не індексує транзакції за адресою. Варіанти:
Indexer API — Moralis, Alchemy, Etherscan API, The Graph. Moralis getWalletTransactions повертає всі транзакції за адресою з пагінацією. Etherscan API — безплатний лімит 5 req/s, достатньо для MVP.
Власний індексер — якщо потрібна приватність або специфічна логіка. The Graph дозволяє написати subgraph з потрібною схемою даних та запитувати через GraphQL.
Multicall — для отримання балансів токенів з одного запиту замість N запитів.
На практиці для multi-chain кошельку: Alchemy/Moralis для EVM-сітей, Solana RPC з getSignaturesForAddress для Solana, власні модулі для інших сітей.
Структура даних та нормалізація
Транзакції з різних сітей мають різний формат. Нормалізуємо в єдину модель:
class TransactionRecord {
final String hash;
final String chainId;
final TransactionType type; // send, receive, swap, approve, contract_call
final String fromAddress;
final String toAddress;
final BigInt amount; // у wei/lamports/satoshi
final String tokenSymbol;
final String? tokenAddress; // null для нативної валюти
final int decimals;
final DateTime timestamp;
final TransactionStatus status; // confirmed, pending, failed
final BigInt? gasFee;
final double? fiatValueAtTime; // у USD за курсом на момент транзакції
final String? swapFromToken; // для DEX-свопів
final String? swapToToken;
}
fiatValueAtTime — окреме завдання. CoinGecko Historical API (/coins/{id}/history?date=DD-MM-YYYY) дає ціну токена на конкретну дату. Для USD-еквіваленту при створенні запису про транзакцію — запитуємо та кешуємо в локальній БД, ціни змінюються.
Декодування типу транзакції
Простий переведення ETH — input: "0x", to — адреса одержувача. Але ERC-20 transfer виглядає як вызов контракту з input: "0xa9059cbb...". Потрібно декодувати:
TransactionType detectTxType(String inputData, String toAddress, List<String> knownContracts) {
if (inputData == '0x' || inputData.isEmpty) return TransactionType.send;
final selector = inputData.substring(0, 10); // перші 4 байти
const selectors = {
'0xa9059cbb': TransactionType.tokenTransfer, // transfer(address,uint256)
'0x095ea7b3': TransactionType.approve, // approve(address,uint256)
'0x38ed1739': TransactionType.swap, // swapExactTokensForTokens (Uniswap v2)
'0x7ff36ab5': TransactionType.swap, // swapExactETHForTokens
};
return selectors[selector] ?? TransactionType.contractCall;
}
Для декодування параметрів ERC-20 transfer — парсимо input: адреса одержувача в байтах 4-35, сума — байти 36-67.
Кешування та пагінація
Історія транзакцій — дані, які ростуть в одну сторону (append-only, старі транзакції не змінюються). Стратегія кешування: зберігаємо всі завантажені транзакції в SQLite через drift, при оновленні запитуємо тільки нові (від останнього відомого блока).
Пагінація на UI: LazyColumn (Android Jetpack Compose) або ListView.builder (Flutter) з pagination_controller. При скролі вниз — завантажуємо наступну сторінку через cursor-based pagination (за block number або timestamp), не offset-based.
// Flutter — пагінація з курсором
Future<void> loadMoreTransactions() async {
if (_isLoading || !_hasMore) return;
_isLoading = true;
final oldestTx = _transactions.lastOrNull;
final newTxs = await repository.getTransactions(
address: walletAddress,
before: oldestTx?.timestamp,
limit: 20,
);
_transactions.addAll(newTxs);
_hasMore = newTxs.length == 20;
_isLoading = false;
notifyListeners();
}
Фільтрація та пошук
Фільтри за типом (відправити/отримати/своп), за токеном, за датою, за сіттю. Реалізуємо локально на закешованих даних — швидко та без мережевих запитів. drift підтримує складні WHERE-запити з індексами.
Пошук за адресою або хешем транзакції — полнотекстовий пошук у SQLite через LIKE або FTS5 extension.
Статуси pending-транзакцій
Відправлені транзакції спочатку потрапляють у mempool зі статусом pending. Потрібно відстежувати їх підтвердження: періодично перевіряємо eth_getTransactionReceipt для кожної pending транзакції, або підписуємось на WebSocket eth_subscribe("newHeads") і при кожному новому блоці перевіряємо pending список.
Що входить у роботу
- Інтеграція з indexer API (Moralis / Alchemy / Etherscan) або GraphQL subgraph
- Нормалізація транзакцій з підтримкою кількох сітей
- Декодування типів транзакцій (transfer, approve, swap)
- Локальний кеш у SQLite з пагінацією
- Fiat-еквіваленти на дату транзакції
- Фільтрація та пошук
- Відстеження pending транзакцій
Строки
Один блокчейн (EVM), базовий список з кешем: 1–2 тижні. Multi-chain з DEX-декодуванням, фіатними еквівалентами та повною фільтрацією: 3–5 тижнів. Вартість розраховується індивідуально.







