parse eth tx json to data transfer objects

This commit is contained in:
julian 2023-03-02 15:07:25 -06:00
parent 4cec54620a
commit a5d8fdde79
4 changed files with 272 additions and 110 deletions

View file

@ -0,0 +1,82 @@
import 'package:stackwallet/utilities/logger.dart';
class EthTokenTxDTO {
final String blockHash;
final int blockNumber;
final int confirmations;
final String contractAddress;
final int cumulativeGasUsed;
final String from;
final int gas;
final BigInt gasPrice;
final int gasUsed;
final String hash;
final String input;
final int logIndex;
final int nonce;
final int timeStamp;
final String to;
final int tokenDecimal;
final String tokenName;
final String tokenSymbol;
final int transactionIndex;
final BigInt value;
EthTokenTxDTO({
required this.blockHash,
required this.blockNumber,
required this.confirmations,
required this.contractAddress,
required this.cumulativeGasUsed,
required this.from,
required this.gas,
required this.gasPrice,
required this.gasUsed,
required this.hash,
required this.input,
required this.logIndex,
required this.nonce,
required this.timeStamp,
required this.to,
required this.tokenDecimal,
required this.tokenName,
required this.tokenSymbol,
required this.transactionIndex,
required this.value,
});
factory EthTokenTxDTO.fromMap({
required Map<String, dynamic> map,
}) {
try {
return EthTokenTxDTO(
blockHash: map["blockHash"] as String,
blockNumber: int.parse(map["blockNumber"] as String),
confirmations: int.parse(map["confirmations"] as String),
contractAddress: map["contractAddress"] as String,
cumulativeGasUsed: int.parse(map["cumulativeGasUsed"] as String),
from: map["from"] as String,
gas: int.parse(map["gas"] as String),
gasPrice: BigInt.parse(map["gasPrice"] as String),
gasUsed: int.parse(map["gasUsed"] as String),
hash: map["hash"] as String,
input: map["input"] as String,
logIndex: int.parse(map["logIndex"] as String? ?? "-1"),
nonce: int.parse(map["nonce"] as String),
timeStamp: int.parse(map["timeStamp"] as String),
to: map["to"] as String,
tokenDecimal: int.parse(map["tokenDecimal"] as String),
tokenName: map["tokenName"] as String,
tokenSymbol: map["tokenSymbol"] as String,
transactionIndex: int.parse(map["transactionIndex"] as String),
value: BigInt.parse(map["value"] as String),
);
} catch (e, s) {
Logging.instance.log(
"EthTokenTxDTO.fromMap() failed: $e\n$s",
level: LogLevel.Fatal,
);
rethrow;
}
}
}

View file

@ -0,0 +1,131 @@
import 'dart:convert';
class EthTxDTO {
EthTxDTO({
required this.hash,
required this.blockHash,
required this.blockNumber,
required this.transactionIndex,
required this.timestamp,
required this.from,
required this.to,
required this.value,
required this.gas,
required this.gasPrice,
required this.maxFeePerGas,
required this.maxPriorityFeePerGas,
required this.isError,
required this.hasToken,
required this.compressedTx,
required this.gasCost,
required this.gasUsed,
});
factory EthTxDTO.fromMap(Map<String, dynamic> map) => EthTxDTO(
hash: map['hash'] as String,
blockHash: map['blockHash'] as String,
blockNumber: map['blockNumber'] as int,
transactionIndex: map['transactionIndex'] as int,
timestamp: map['timestamp'] as int,
from: map['from'] as String,
to: map['to'] as String,
value: map['value'] as int,
gas: map['gas'] as int,
gasPrice: map['gasPrice'] as int,
maxFeePerGas: map['maxFeePerGas'] as int,
maxPriorityFeePerGas: map['maxPriorityFeePerGas'] as int,
isError: map['isError'] as int,
hasToken: map['hasToken'] as int,
compressedTx: map['compressedTx'] as String,
gasCost: map['gasCost'] as int,
gasUsed: map['gasUsed'] as int,
);
factory EthTxDTO.fromJsonString(String jsonString) => EthTxDTO.fromMap(
Map<String, dynamic>.from(
jsonDecode(jsonString) as Map,
),
);
final String hash;
final String blockHash;
final int blockNumber;
final int transactionIndex;
final int timestamp;
final String from;
final String to;
final int value;
final int gas;
final int gasPrice;
final int maxFeePerGas;
final int maxPriorityFeePerGas;
final int isError;
final int hasToken;
final String compressedTx;
final int gasCost;
final int gasUsed;
EthTxDTO copyWith({
String? hash,
String? blockHash,
int? blockNumber,
int? transactionIndex,
int? timestamp,
String? from,
String? to,
int? value,
int? gas,
int? gasPrice,
int? maxFeePerGas,
int? maxPriorityFeePerGas,
int? isError,
int? hasToken,
String? compressedTx,
int? gasCost,
int? gasUsed,
}) =>
EthTxDTO(
hash: hash ?? this.hash,
blockHash: blockHash ?? this.blockHash,
blockNumber: blockNumber ?? this.blockNumber,
transactionIndex: transactionIndex ?? this.transactionIndex,
timestamp: timestamp ?? this.timestamp,
from: from ?? this.from,
to: to ?? this.to,
value: value ?? this.value,
gas: gas ?? this.gas,
gasPrice: gasPrice ?? this.gasPrice,
maxFeePerGas: maxFeePerGas ?? this.maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas,
isError: isError ?? this.isError,
hasToken: hasToken ?? this.hasToken,
compressedTx: compressedTx ?? this.compressedTx,
gasCost: gasCost ?? this.gasCost,
gasUsed: gasUsed ?? this.gasUsed,
);
Map<String, dynamic> toMap() {
final map = <String, dynamic>{};
map['hash'] = hash;
map['blockHash'] = blockHash;
map['blockNumber'] = blockNumber;
map['transactionIndex'] = transactionIndex;
map['timestamp'] = timestamp;
map['from'] = from;
map['to'] = to;
map['value'] = value;
map['gas'] = gas;
map['gasPrice'] = gasPrice;
map['maxFeePerGas'] = maxFeePerGas;
map['maxPriorityFeePerGas'] = maxPriorityFeePerGas;
map['isError'] = isError;
map['hasToken'] = hasToken;
map['compressedTx'] = compressedTx;
map['gasCost'] = gasCost;
map['gasUsed'] = gasUsed;
return map;
}
@override
String toString() => jsonEncode(toMap());
}

View file

@ -567,14 +567,13 @@ class EthereumWallet extends CoinServiceAPI
}
if (!needsRefresh) {
var allOwnAddresses = await _fetchAllOwnAddresses();
AddressTransaction addressTransactions =
await EthereumAPI.fetchAddressTransactions(
final response = await EthereumAPI.getEthTransactions(
allOwnAddresses.elementAt(0).value,
);
if (addressTransactions.message == "OK") {
final allTxs = addressTransactions.result;
if (response.value != null) {
final allTxs = response.value!;
for (final element in allTxs) {
final txid = element["hash"] as String;
final txid = element.hash;
if ((await db
.getTransactions(walletId)
.filter()
@ -582,7 +581,7 @@ class EthereumWallet extends CoinServiceAPI
.findFirst()) ==
null) {
Logging.instance.log(
" txid not found in address history already ${element['hash']}",
" txid not found in address history already $txid",
level: LogLevel.Info);
needsRefresh = true;
break;
@ -845,20 +844,18 @@ class EthereumWallet extends CoinServiceAPI
Future<void> _refreshTransactions() async {
String thisAddress = await currentReceivingAddress;
AddressTransaction txs =
await EthereumAPI.fetchAddressTransactions(thisAddress);
final txsResponse = await EthereumAPI.getEthTransactions(thisAddress);
if (txs.message == "OK") {
final allTxs = txs.result;
if (txsResponse.value != null) {
final allTxs = txsResponse.value!;
final List<Tuple2<Transaction, Address?>> txnsData = [];
for (final element in allTxs) {
int transactionAmount = int.parse(element['value'].toString());
int transactionAmount = element.value;
bool isIncoming;
bool txFailed = false;
if (checksumEthereumAddress(element["from"].toString()) ==
thisAddress) {
if (!(int.parse(element["isError"] as String) == 0)) {
if (checksumEthereumAddress(element.from) == thisAddress) {
if (element.isError != 0) {
txFailed = true;
}
isIncoming = false;
@ -867,16 +864,16 @@ class EthereumWallet extends CoinServiceAPI
}
//Calculate fees (GasLimit * gasPrice)
int txFee = int.parse(element['gasPrice'].toString()) *
int.parse(element['gasUsed'].toString());
// int txFee = element.gasPrice * element.gasUsed;
int txFee = element.gasCost;
final String addressString = element["to"] as String;
final int height = int.parse(element['blockNumber'].toString());
final String addressString = checksumEthereumAddress(element.to);
final int height = element.blockNumber;
final txn = Transaction(
walletId: walletId,
txid: element["hash"] as String,
timestamp: int.parse(element["timeStamp"].toString()),
txid: element.hash,
timestamp: element.timestamp,
type:
isIncoming ? TransactionType.incoming : TransactionType.outgoing,
subType: TransactionSubType.none,

View file

@ -3,6 +3,8 @@ import 'dart:math';
import 'package:decimal/decimal.dart';
import 'package:http/http.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart';
import 'package:stackwallet/dto/ethereum/eth_tx_dto.dart';
import 'package:stackwallet/models/ethereum/erc20_token.dart';
import 'package:stackwallet/models/ethereum/erc721_token.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
@ -11,87 +13,6 @@ import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/logger.dart';
class EthTokenTx {
final String blockHash;
final int blockNumber;
final int confirmations;
final String contractAddress;
final int cumulativeGasUsed;
final String from;
final int gas;
final BigInt gasPrice;
final int gasUsed;
final String hash;
final String input;
final int logIndex;
final int nonce;
final int timeStamp;
final String to;
final int tokenDecimal;
final String tokenName;
final String tokenSymbol;
final int transactionIndex;
final BigInt value;
EthTokenTx({
required this.blockHash,
required this.blockNumber,
required this.confirmations,
required this.contractAddress,
required this.cumulativeGasUsed,
required this.from,
required this.gas,
required this.gasPrice,
required this.gasUsed,
required this.hash,
required this.input,
required this.logIndex,
required this.nonce,
required this.timeStamp,
required this.to,
required this.tokenDecimal,
required this.tokenName,
required this.tokenSymbol,
required this.transactionIndex,
required this.value,
});
factory EthTokenTx.fromMap({
required Map<String, dynamic> map,
}) {
try {
return EthTokenTx(
blockHash: map["blockHash"] as String,
blockNumber: int.parse(map["blockNumber"] as String),
confirmations: int.parse(map["confirmations"] as String),
contractAddress: map["contractAddress"] as String,
cumulativeGasUsed: int.parse(map["cumulativeGasUsed"] as String),
from: map["from"] as String,
gas: int.parse(map["gas"] as String),
gasPrice: BigInt.parse(map["gasPrice"] as String),
gasUsed: int.parse(map["gasUsed"] as String),
hash: map["hash"] as String,
input: map["input"] as String,
logIndex: int.parse(map["logIndex"] as String? ?? "-1"),
nonce: int.parse(map["nonce"] as String),
timeStamp: int.parse(map["timeStamp"] as String),
to: map["to"] as String,
tokenDecimal: int.parse(map["tokenDecimal"] as String),
tokenName: map["tokenName"] as String,
tokenSymbol: map["tokenSymbol"] as String,
transactionIndex: int.parse(map["transactionIndex"] as String),
value: BigInt.parse(map["value"] as String),
);
} catch (e, s) {
Logging.instance.log(
"EthTokenTx.fromMap() failed: $e\n$s",
level: LogLevel.Fatal,
);
rethrow;
}
}
}
class EthApiException with Exception {
EthApiException(this.message);
@ -123,7 +44,7 @@ abstract class EthereumAPI {
static const gasTrackerUrl =
"https://blockscout.com/eth/mainnet/api/v1/gas-price-oracle";
static Future<AddressTransaction> fetchAddressTransactions(
static Future<EthereumResponse<List<EthTxDTO>>> getEthTransactions(
String address) async {
try {
final response = await get(
@ -135,19 +56,50 @@ abstract class EthereumAPI {
);
// "$etherscanApi?module=account&action=txlist&address=$address&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
if (response.statusCode == 200) {
return AddressTransaction.fromJson(
jsonDecode(response.body)["data"] as List);
if (response.body.isNotEmpty) {
final json = jsonDecode(response.body) as Map;
final list = json["data"] as List?;
final List<EthTxDTO> txns = [];
for (final map in list!) {
txns.add(EthTxDTO.fromMap(Map<String, dynamic>.from(map as Map)));
}
return EthereumResponse(
txns,
null,
);
} else {
throw EthApiException(
"getEthTransactions($address) response is empty but status code is "
"${response.statusCode}",
);
}
} else {
throw Exception(
'ERROR GETTING TRANSACTIONS WITH STATUS ${response.statusCode}');
throw EthApiException(
"getEthTransactions($address) failed with status code: "
"${response.statusCode}",
);
}
} on EthApiException catch (e) {
return EthereumResponse(
null,
e,
);
} catch (e, s) {
throw Exception('ERROR GETTING TRANSACTIONS ${e.toString()}');
Logging.instance.log(
"getEthTransactions(): $e\n$s",
level: LogLevel.Error,
);
return EthereumResponse(
null,
EthApiException(e.toString()),
);
}
}
static Future<EthereumResponse<List<EthTokenTx>>> getTokenTransactions({
static Future<EthereumResponse<List<EthTokenTxDTO>>> getTokenTransactions({
required String address,
String? contractAddress,
int? startBlock,
@ -170,9 +122,9 @@ abstract class EthereumAPI {
if (json["message"] == "OK") {
final result =
List<Map<String, dynamic>>.from(json["result"] as List);
final List<EthTokenTx> tokenTxns = [];
final List<EthTokenTxDTO> tokenTxns = [];
for (final map in result) {
tokenTxns.add(EthTokenTx.fromMap(map: map));
tokenTxns.add(EthTokenTxDTO.fromMap(map: map));
}
return EthereumResponse(