mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-11 13:14:32 +00:00
Transaction listing
This commit is contained in:
parent
5fcb65ba0c
commit
b60122fd85
2 changed files with 134 additions and 168 deletions
|
@ -96,7 +96,8 @@ class TransactionChunk {
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return TransactionChunk(
|
return TransactionChunk(
|
||||||
timestamp: json['timestamp'] as int, transactions: txList);
|
timestamp: int.parse(json['timestamp'].toString()),
|
||||||
|
transactions: txList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -192,13 +193,13 @@ class Transaction {
|
||||||
return Transaction(
|
return Transaction(
|
||||||
txid: json['txid'] as String,
|
txid: json['txid'] as String,
|
||||||
confirmedStatus: json['confirmed_status'] as bool,
|
confirmedStatus: json['confirmed_status'] as bool,
|
||||||
timestamp: json['timestamp'] as int,
|
timestamp: int.parse(json['timestamp'].toString()),
|
||||||
txType: json['txType'] as String,
|
txType: json['txType'] as String,
|
||||||
amount: json['amount'] as int,
|
amount: json['amount'] as int,
|
||||||
aliens: json['aliens'] as List,
|
aliens: json['aliens'] as List,
|
||||||
worthNow: json['worthNow'] as String? ?? "",
|
worthNow: json['worthNow'] as String? ?? "",
|
||||||
worthAtBlockTimestamp: json['worthAtBlockTimestamp'] as String? ?? "",
|
worthAtBlockTimestamp: json['worthAtBlockTimestamp'] as String? ?? "",
|
||||||
fees: json['fees'] as int,
|
fees: int.parse(json['fees'].toString()),
|
||||||
inputSize: json['inputSize'] as int,
|
inputSize: json['inputSize'] as int,
|
||||||
outputSize: json['outputSize'] as int,
|
outputSize: json['outputSize'] as int,
|
||||||
inputs: inputList,
|
inputs: inputList,
|
||||||
|
|
|
@ -41,7 +41,7 @@ const String GENESIS_HASH_MAINNET =
|
||||||
class AddressTransaction {
|
class AddressTransaction {
|
||||||
final String message;
|
final String message;
|
||||||
final List<dynamic> result;
|
final List<dynamic> result;
|
||||||
final int status;
|
final String status;
|
||||||
|
|
||||||
const AddressTransaction({
|
const AddressTransaction({
|
||||||
required this.message,
|
required this.message,
|
||||||
|
@ -53,7 +53,7 @@ class AddressTransaction {
|
||||||
return AddressTransaction(
|
return AddressTransaction(
|
||||||
message: json['message'] as String,
|
message: json['message'] as String,
|
||||||
result: json['result'] as List<dynamic>,
|
result: json['result'] as List<dynamic>,
|
||||||
status: json['status'] as int,
|
status: json['status'] as String,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,10 +86,9 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
late PriceAPI _priceAPI;
|
late PriceAPI _priceAPI;
|
||||||
final _prefs = Prefs.instance;
|
final _prefs = Prefs.instance;
|
||||||
final _client = Web3Client(
|
final _client = Web3Client(
|
||||||
"https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba",
|
"https://goerli.infura.io/v3/22677300bf774e49a458b73313ee56ba", Client());
|
||||||
Client());
|
|
||||||
|
|
||||||
final _blockExplorer = "https://blockscout.com/eth/mainnet/api?";
|
final _blockExplorer = "https://eth-goerli.blockscout.com/api?";
|
||||||
|
|
||||||
late EthPrivateKey _credentials;
|
late EthPrivateKey _credentials;
|
||||||
int _chainId = 5; //5 for testnet and 1 for mainnet
|
int _chainId = 5; //5 for testnet and 1 for mainnet
|
||||||
|
@ -388,7 +387,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
print("CALLING REFRESH");
|
|
||||||
if (refreshMutex) {
|
if (refreshMutex) {
|
||||||
Logging.instance.log("$walletId $walletName refreshMutex denied",
|
Logging.instance.log("$walletId $walletName refreshMutex denied",
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
|
@ -397,9 +395,7 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
refreshMutex = true;
|
refreshMutex = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("SYNC STATUS IS ");
|
|
||||||
final blockNumber = await _client.getBlockNumber();
|
final blockNumber = await _client.getBlockNumber();
|
||||||
print("BLOCK NUMBER IS ::: ${blockNumber}");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
GlobalEventBus.instance.fire(
|
GlobalEventBus.instance.fire(
|
||||||
|
@ -414,7 +410,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
|
||||||
|
|
||||||
final currentHeight = await chainHeight;
|
final currentHeight = await chainHeight;
|
||||||
print("CURRENT CHAIN HEIGHT IS $currentHeight");
|
|
||||||
const storedHeight = 1; //await storedChainHeight;
|
const storedHeight = 1; //await storedChainHeight;
|
||||||
|
|
||||||
Logging.instance
|
Logging.instance
|
||||||
|
@ -429,7 +424,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
final newTxData = await _fetchTransactionData();
|
final newTxData = await _fetchTransactionData();
|
||||||
print("RETREIVED TX DATA IS $newTxData");
|
|
||||||
GlobalEventBus.instance
|
GlobalEventBus.instance
|
||||||
.fire(RefreshPercentChangedEvent(0.50, walletId));
|
.fire(RefreshPercentChangedEvent(0.50, walletId));
|
||||||
}
|
}
|
||||||
|
@ -474,8 +468,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
// TODO: Check difference between total and available balance for eth
|
// TODO: Check difference between total and available balance for eth
|
||||||
Future<Decimal> get totalBalance async {
|
Future<Decimal> get totalBalance async {
|
||||||
EtherAmount ethBalance = await _client.getBalance(_credentials.address);
|
EtherAmount ethBalance = await _client.getBalance(_credentials.address);
|
||||||
print(
|
|
||||||
"BALANCE NOW IS ${ethBalance.getValueInUnit(EtherUnit.ether).toString()}");
|
|
||||||
return Decimal.parse(ethBalance.getValueInUnit(EtherUnit.ether).toString());
|
return Decimal.parse(ethBalance.getValueInUnit(EtherUnit.ether).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,169 +536,142 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
return isValidEthereumAddress(address);
|
return isValidEthereumAddress(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AddressTransaction> parseTransactions(String responseBody) {
|
Future<AddressTransaction> fetchAddressTransactions(String address) async {
|
||||||
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
|
final response = await get(Uri.parse(
|
||||||
return parsed
|
"${_blockExplorer}module=account&action=txlist&address=$address"));
|
||||||
.map<AddressTransaction>((json) => AddressTransaction.fromJson(json))
|
|
||||||
.toList();
|
if (response.statusCode == 200) {
|
||||||
|
return AddressTransaction.fromJson(
|
||||||
|
json.decode(response.body) as Map<String, dynamic>);
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load transactions');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TransactionData> _fetchTransactionData() async {
|
Future<TransactionData> _fetchTransactionData() async {
|
||||||
String thisAddress = await currentReceivingAddress;
|
String thisAddress = await currentReceivingAddress;
|
||||||
|
final cachedTransactions =
|
||||||
|
DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
|
||||||
|
as TransactionData?;
|
||||||
|
int latestTxnBlockHeight =
|
||||||
|
DB.instance.get<dynamic>(boxName: walletId, key: "storedTxnDataHeight")
|
||||||
|
as int? ??
|
||||||
|
0;
|
||||||
|
|
||||||
final response = await get(Uri.parse(
|
final priceData =
|
||||||
"${_blockExplorer}module=account&action=txlist&address=$thisAddress"));
|
await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||||
|
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||||
|
final List<Map<String, dynamic>> midSortedArray = [];
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
AddressTransaction txs = await fetchAddressTransactions(thisAddress);
|
||||||
// If the server did return a 200 OK response,
|
if (txs.message == "OK") {
|
||||||
// then parse the JSON.
|
final allTxs = txs.result;
|
||||||
|
allTxs.forEach((element) {
|
||||||
|
Map<String, dynamic> midSortedTx = {};
|
||||||
|
|
||||||
print(parseTransactions(response.body.toString()));
|
// create final tx map
|
||||||
} else {
|
midSortedTx["txid"] = element["hash"];
|
||||||
// If the server did not return a 200 OK response,
|
int confirmations = int.parse(element['confirmations'].toString());
|
||||||
// then throw an exception.
|
|
||||||
throw Exception('Failed to load album');
|
int transactionAmount = int.parse(element['value'].toString());
|
||||||
|
const decimal = 18; //Eth has up to 18 decimal places
|
||||||
|
final transactionAmountInDecimal =
|
||||||
|
transactionAmount / (pow(10, decimal));
|
||||||
|
|
||||||
|
//Convert to satoshi, default display for other coins
|
||||||
|
// Decimal.parse(gasPrice.getValueInUnit(EtherUnit.ether).toString())
|
||||||
|
final satAmount = Format.decimalAmountToSatoshis(
|
||||||
|
Decimal.parse(transactionAmountInDecimal.toString()), coin);
|
||||||
|
|
||||||
|
midSortedTx["confirmed_status"] =
|
||||||
|
(confirmations != 0) && (confirmations >= MINIMUM_CONFIRMATIONS);
|
||||||
|
midSortedTx["confirmations"] = confirmations;
|
||||||
|
midSortedTx["timestamp"] = element["timeStamp"];
|
||||||
|
|
||||||
|
if (checksumEthereumAddress(element["from"].toString()) ==
|
||||||
|
thisAddress) {
|
||||||
|
midSortedTx["txType"] = "Sent";
|
||||||
|
} else {
|
||||||
|
midSortedTx["txType"] = "Received";
|
||||||
|
}
|
||||||
|
|
||||||
|
midSortedTx["amount"] = satAmount;
|
||||||
|
final String worthNow = ((currentPrice * Decimal.fromInt(satAmount)) /
|
||||||
|
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||||
|
.toDecimal(scaleOnInfinitePrecision: 2)
|
||||||
|
.toStringAsFixed(2);
|
||||||
|
|
||||||
|
//Calculate fees (GasLimit * gasPrice)
|
||||||
|
int txFee = int.parse(element['gasPrice'].toString()) *
|
||||||
|
int.parse(element['gasUsed'].toString());
|
||||||
|
final txFeeDecimal = txFee / (pow(10, decimal));
|
||||||
|
|
||||||
|
midSortedTx["worthNow"] = worthNow;
|
||||||
|
midSortedTx["worthAtBlockTimestamp"] = worthNow;
|
||||||
|
midSortedTx["aliens"] = <dynamic>[];
|
||||||
|
midSortedTx["fees"] = Format.decimalAmountToSatoshis(
|
||||||
|
Decimal.parse(txFeeDecimal.toString()), coin);
|
||||||
|
midSortedTx["address"] = element["to"];
|
||||||
|
midSortedTx["inputSize"] = 1;
|
||||||
|
midSortedTx["outputSize"] = 1;
|
||||||
|
midSortedTx["inputs"] = <dynamic>[];
|
||||||
|
midSortedTx["outputs"] = <dynamic>[];
|
||||||
|
midSortedTx["height"] = int.parse(element['blockNumber'].toString());
|
||||||
|
|
||||||
|
midSortedArray.add(midSortedTx);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
print("RETURNED TRANSACTIONS IS $response");
|
|
||||||
// int currentBlock = await chainHeight;
|
|
||||||
// var balance = await availableBalance;
|
|
||||||
// print("MY ADDRESS HERE IS $thisAddress");
|
|
||||||
// var n =
|
|
||||||
// await _client.getTransactionCount(EthereumAddress.fromHex(thisAddress));
|
|
||||||
// print("TRANSACTION COUNT IS $n");
|
|
||||||
// String hexHeight = currentBlock.toRadixString(16);
|
|
||||||
// print("HEIGHT TO HEX IS $hexHeight");
|
|
||||||
// print('0x$hexHeight');
|
|
||||||
//
|
|
||||||
// final cachedTransactions =
|
|
||||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
|
|
||||||
// as TransactionData?;
|
|
||||||
//
|
|
||||||
// final priceData =
|
|
||||||
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
|
||||||
// Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
|
||||||
//
|
|
||||||
// //Initilaize empty transactions array
|
|
||||||
//
|
|
||||||
// final List<Map<String, dynamic>> midSortedArray = [];
|
|
||||||
// Map<String, dynamic> midSortedTx = {};
|
|
||||||
// for (int i = currentBlock;
|
|
||||||
// i >= 0 && (n > 0 || balance.toDouble() > 0.0);
|
|
||||||
// --i) {
|
|
||||||
// try {
|
|
||||||
// // print(StringToHex.toHexString(i.toString()))
|
|
||||||
// print(
|
|
||||||
// "BLOCK IS $i AND HEX IS --------->>>>>>> ${StringToHex.toHexString(i.toString())}");
|
|
||||||
// String blockHex = i.toRadixString(16);
|
|
||||||
// var block = await _client.getBlockInformation(
|
|
||||||
// blockNumber: '0x$blockHex', isContainFullObj: true);
|
|
||||||
//
|
|
||||||
// if (block != null && block.transactions != null) {
|
|
||||||
// block.transactions.forEach((element) {
|
|
||||||
// // print("TRANSACTION OBJECT IS $element");
|
|
||||||
// final jsonObject = json.encode(element);
|
|
||||||
// final decodedTransaction = jsonDecode(jsonObject);
|
|
||||||
// // print(somethingElse['from']);
|
|
||||||
// // print(jsonObject.containsKey(other));
|
|
||||||
// Logging.instance.log(decodedTransaction,
|
|
||||||
// level: LogLevel.Info, printFullLength: true);
|
|
||||||
//
|
|
||||||
// if (thisAddress == decodedTransaction['from']) {
|
|
||||||
// //Ensure this is not a self send
|
|
||||||
// if (decodedTransaction['from'] != decodedTransaction['to']) {
|
|
||||||
// midSortedTx["txType"] = "Sent";
|
|
||||||
// midSortedTx["txid"] = decodedTransaction["hash"];
|
|
||||||
// midSortedTx["height"] = i;
|
|
||||||
// midSortedTx["address"] = decodedTransaction['to'];
|
|
||||||
// int confirmations = 0;
|
|
||||||
// try {
|
|
||||||
// confirmations = currentBlock - i;
|
|
||||||
// } catch (e, s) {
|
|
||||||
// debugPrint("$e $s");
|
|
||||||
// }
|
|
||||||
// midSortedTx["confirmations"] = confirmations;
|
|
||||||
// midSortedTx["inputSize"] = 1;
|
|
||||||
// midSortedTx["outputSize"] = 1;
|
|
||||||
// midSortedTx["aliens"] = <dynamic>[];
|
|
||||||
// midSortedTx["inputs"] = <dynamic>[];
|
|
||||||
// midSortedTx["outputs"] = <dynamic>[];
|
|
||||||
//
|
|
||||||
// midSortedArray.add(midSortedTx);
|
|
||||||
// Logging.instance.log(
|
|
||||||
// "TX SENT FROM THIS ACCOUNT ${decodedTransaction['from']} ${decodedTransaction['to']}",
|
|
||||||
// level: LogLevel.Info);
|
|
||||||
// --n;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (thisAddress == decodedTransaction['to']) {
|
|
||||||
// if (decodedTransaction['from'] != decodedTransaction['to']) {
|
|
||||||
// midSortedTx["txType"] = "Received";
|
|
||||||
// midSortedTx["txid"] = decodedTransaction["hash"];
|
|
||||||
// midSortedTx["height"] = i;
|
|
||||||
// midSortedTx["address"] = decodedTransaction['from'];
|
|
||||||
// int confirmations = 0;
|
|
||||||
// try {
|
|
||||||
// confirmations = currentBlock - i;
|
|
||||||
// } catch (e, s) {
|
|
||||||
// debugPrint("$e $s");
|
|
||||||
// }
|
|
||||||
// midSortedTx["confirmations"] = confirmations;
|
|
||||||
// midSortedTx["inputSize"] = 1;
|
|
||||||
// midSortedTx["outputSize"] = 1;
|
|
||||||
// midSortedTx["aliens"] = <dynamic>[];
|
|
||||||
// midSortedTx["inputs"] = <dynamic>[];
|
|
||||||
// midSortedTx["outputs"] = <dynamic>[];
|
|
||||||
// midSortedArray.add(midSortedTx);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// } catch (e, s) {
|
|
||||||
// print("Error getting transactions ${e.toString()} $s");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// midSortedArray
|
|
||||||
// .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int));
|
|
||||||
// final Map<String, dynamic> result = {"dateTimeChunks": <dynamic>[]};
|
|
||||||
// final dateArray = <dynamic>[];
|
|
||||||
//
|
|
||||||
// for (int i = 0; i < midSortedArray.length; i++) {
|
|
||||||
// final txObject = midSortedArray[i];
|
|
||||||
// final date = extractDateFromTimestamp(txObject["timestamp"] as int);
|
|
||||||
// final txTimeArray = [txObject["timestamp"], date];
|
|
||||||
//
|
|
||||||
// if (dateArray.contains(txTimeArray[1])) {
|
|
||||||
// result["dateTimeChunks"].forEach((dynamic chunk) {
|
|
||||||
// if (extractDateFromTimestamp(chunk["timestamp"] as int) ==
|
|
||||||
// txTimeArray[1]) {
|
|
||||||
// if (chunk["transactions"] == null) {
|
|
||||||
// chunk["transactions"] = <Map<String, dynamic>>[];
|
|
||||||
// }
|
|
||||||
// chunk["transactions"].add(txObject);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// dateArray.add(txTimeArray[1]);
|
|
||||||
// final chunk = {
|
|
||||||
// "timestamp": txTimeArray[0],
|
|
||||||
// "transactions": [txObject],
|
|
||||||
// };
|
|
||||||
// result["dateTimeChunks"].add(chunk);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final transactionsMap = cachedTransactions?.getAllTransactions() ?? {};
|
|
||||||
// transactionsMap
|
|
||||||
// .addAll(TransactionData.fromJson(result).getAllTransactions());
|
|
||||||
|
|
||||||
// print("THIS CURRECT ADDRESS IS $thisAddress");
|
midSortedArray.sort((a, b) =>
|
||||||
// print("THIS CURRECT BLOCK IS $currentBlock");
|
(int.parse(b['timestamp'].toString())) -
|
||||||
// print("THIS BALANCE IS $balance");
|
(int.parse(a['timestamp'].toString())));
|
||||||
// print("THIS COUNT TRANSACTIONS IS $n");
|
|
||||||
|
|
||||||
return TransactionData();
|
// buildDateTimeChunks
|
||||||
// throw UnimplementedError();
|
final Map<String, dynamic> result = {"dateTimeChunks": <dynamic>[]};
|
||||||
|
final dateArray = <dynamic>[];
|
||||||
|
|
||||||
|
for (int i = 0; i < midSortedArray.length; i++) {
|
||||||
|
final txObject = midSortedArray[i];
|
||||||
|
final date =
|
||||||
|
extractDateFromTimestamp(int.parse(txObject['timestamp'].toString()));
|
||||||
|
final txTimeArray = [txObject["timestamp"], date];
|
||||||
|
|
||||||
|
if (dateArray.contains(txTimeArray[1])) {
|
||||||
|
result["dateTimeChunks"].forEach((dynamic chunk) {
|
||||||
|
if (extractDateFromTimestamp(
|
||||||
|
int.parse(chunk['timestamp'].toString())) ==
|
||||||
|
txTimeArray[1]) {
|
||||||
|
if (chunk["transactions"] == null) {
|
||||||
|
chunk["transactions"] = <Map<String, dynamic>>[];
|
||||||
|
}
|
||||||
|
chunk["transactions"].add(txObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dateArray.add(txTimeArray[1]);
|
||||||
|
final chunk = {
|
||||||
|
"timestamp": txTimeArray[0],
|
||||||
|
"transactions": [txObject],
|
||||||
|
};
|
||||||
|
result["dateTimeChunks"].add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final transactionsMap = cachedTransactions?.getAllTransactions() ?? {};
|
||||||
|
transactionsMap
|
||||||
|
.addAll(TransactionData.fromJson(result).getAllTransactions());
|
||||||
|
|
||||||
|
final txModel = TransactionData.fromMap(transactionsMap);
|
||||||
|
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: walletId,
|
||||||
|
key: 'storedTxnDataHeight',
|
||||||
|
value: latestTxnBlockHeight);
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: walletId, key: 'latest_tx_model', value: txModel);
|
||||||
|
|
||||||
|
cachedTxData = txModel;
|
||||||
|
return txModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
Loading…
Reference in a new issue