mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-11 05:04:35 +00:00
Complete adding ERC-20 functionality
This commit is contained in:
parent
dbcbfe342c
commit
fd0b20d661
5 changed files with 199 additions and 119 deletions
|
@ -4,12 +4,12 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/pages/token_view/token_view.dart';
|
import 'package:stackwallet/pages/token_view/token_view.dart';
|
||||||
|
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
||||||
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.dart';
|
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
|
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -35,7 +35,6 @@ class MyTokenSelectItem extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
print("TOKEN DATA IS $tokenData");
|
|
||||||
int balance = tokenData["balance"] as int;
|
int balance = tokenData["balance"] as int;
|
||||||
int tokenDecimals = int.parse(tokenData["decimals"] as String);
|
int tokenDecimals = int.parse(tokenData["decimals"] as String);
|
||||||
final balanceInDecimal = (balance / (pow(10, tokenDecimals)));
|
final balanceInDecimal = (balance / (pow(10, tokenDecimals)));
|
||||||
|
@ -55,9 +54,9 @@ class MyTokenSelectItem extends ConsumerWidget {
|
||||||
final mnemonicList = ref.read(managerProvider).mnemonic;
|
final mnemonicList = ref.read(managerProvider).mnemonic;
|
||||||
|
|
||||||
final token = EthereumToken(
|
final token = EthereumToken(
|
||||||
// contractAddress: tokenData["contractAddress"] as String,
|
|
||||||
tokenData: tokenData,
|
tokenData: tokenData,
|
||||||
walletMnemonic: mnemonicList);
|
walletMnemonic: mnemonicList,
|
||||||
|
secureStore: ref.read(secureStoreProvider));
|
||||||
|
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
TokenView.routeName,
|
TokenView.routeName,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
|
||||||
|
@ -42,31 +41,9 @@ import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
|
|
||||||
const int MINIMUM_CONFIRMATIONS = 3;
|
const int MINIMUM_CONFIRMATIONS = 3;
|
||||||
|
|
||||||
//THis is used for mapping transactions per address from the block explorer
|
|
||||||
class AddressTransaction {
|
|
||||||
final String message;
|
|
||||||
final List<dynamic> result;
|
|
||||||
final String status;
|
|
||||||
|
|
||||||
const AddressTransaction({
|
|
||||||
required this.message,
|
|
||||||
required this.result,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory AddressTransaction.fromJson(Map<String, dynamic> json) {
|
|
||||||
return AddressTransaction(
|
|
||||||
message: json['message'] as String,
|
|
||||||
result: json['result'] as List<dynamic>,
|
|
||||||
status: json['status'] as String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EthereumWallet extends CoinServiceAPI {
|
class EthereumWallet extends CoinServiceAPI {
|
||||||
NodeModel? _ethNode;
|
NodeModel? _ethNode;
|
||||||
final _gasLimit = 21000;
|
final _gasLimit = 21000;
|
||||||
final _blockExplorer = "https://blockscout.com/eth/mainnet/api?";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get walletId => _walletId;
|
String get walletId => _walletId;
|
||||||
|
@ -436,42 +413,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
String privateKey = getPrivateKey(mnemonic);
|
String privateKey = getPrivateKey(mnemonic);
|
||||||
_credentials = EthPrivateKey.fromHex(privateKey);
|
_credentials = EthPrivateKey.fromHex(privateKey);
|
||||||
|
|
||||||
//Get ERC-20 transactions for wallet (So we can get the and save wallet's ERC-20 TOKENS
|
|
||||||
AddressTransaction tokenTransactions = await fetchAddressTransactions(
|
|
||||||
_credentials.address.toString(), "tokentx");
|
|
||||||
var tokenMap = {};
|
|
||||||
List<Map<dynamic, dynamic>> tokensList = [];
|
|
||||||
if (tokenTransactions.message == "OK") {
|
|
||||||
final allTxs = tokenTransactions.result;
|
|
||||||
|
|
||||||
allTxs.forEach((element) {
|
|
||||||
String key = element["tokenSymbol"] as String;
|
|
||||||
tokenMap[key] = {};
|
|
||||||
tokenMap[key]["balance"] = 0;
|
|
||||||
|
|
||||||
if (tokenMap.containsKey(key)) {
|
|
||||||
tokenMap[key]["contractAddress"] = element["contractAddress"];
|
|
||||||
tokenMap[key]["decimals"] = element["tokenDecimal"];
|
|
||||||
tokenMap[key]["name"] = element["tokenName"];
|
|
||||||
tokenMap[key]["symbol"] = element["tokenSymbol"];
|
|
||||||
if (element["to"] == _credentials.address.toString()) {
|
|
||||||
tokenMap[key]["balance"] += int.parse(element["value"] as String);
|
|
||||||
} else {
|
|
||||||
tokenMap[key]["balance"] -= int.parse(element["value"] as String);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tokenMap.forEach((key, value) {
|
|
||||||
//Create New token
|
|
||||||
|
|
||||||
tokensList.add(value as Map<dynamic, dynamic>);
|
|
||||||
});
|
|
||||||
|
|
||||||
await _secureStore.write(
|
|
||||||
key: '${_walletId}_tokens', value: tokensList.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
await DB.instance
|
await DB.instance
|
||||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
||||||
await DB.instance
|
await DB.instance
|
||||||
|
@ -846,19 +787,6 @@ class EthereumWallet extends CoinServiceAPI {
|
||||||
return isValidEthereumAddress(address);
|
return isValidEthereumAddress(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<AddressTransaction> fetchAddressTransactions(
|
|
||||||
String address, String action) async {
|
|
||||||
final response = await get(Uri.parse(
|
|
||||||
"${_blockExplorer}module=account&action=$action&address=$address&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
|
||||||
|
|
||||||
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 =
|
final cachedTransactions =
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:devicelocale/devicelocale.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:stackwallet/utilities/eth_commons.dart';
|
import 'package:stackwallet/utilities/eth_commons.dart';
|
||||||
|
@ -13,9 +14,14 @@ import 'package:stackwallet/services/tokens/token_service.dart';
|
||||||
import 'package:stackwallet/utilities/format.dart';
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||||
|
import 'package:stackwallet/models/paymint/transactions_model.dart' as models;
|
||||||
import 'package:web3dart/web3dart.dart';
|
import 'package:web3dart/web3dart.dart';
|
||||||
import 'package:web3dart/web3dart.dart' as transaction;
|
import 'package:web3dart/web3dart.dart' as transaction;
|
||||||
|
|
||||||
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
|
import 'package:stackwallet/services/node_service.dart';
|
||||||
|
|
||||||
class AbiRequestResponse {
|
class AbiRequestResponse {
|
||||||
final String message;
|
final String message;
|
||||||
final String result;
|
final String result;
|
||||||
|
@ -36,6 +42,8 @@ class AbiRequestResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int MINIMUM_CONFIRMATIONS = 3;
|
||||||
|
|
||||||
class EthereumToken extends TokenServiceAPI {
|
class EthereumToken extends TokenServiceAPI {
|
||||||
@override
|
@override
|
||||||
late bool shouldAutoSync;
|
late bool shouldAutoSync;
|
||||||
|
@ -50,28 +58,25 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
late String _tokenAbi;
|
late String _tokenAbi;
|
||||||
late Web3Client _client;
|
late Web3Client _client;
|
||||||
late final TransactionNotificationTracker txTracker;
|
late final TransactionNotificationTracker txTracker;
|
||||||
|
TransactionData? cachedTxData;
|
||||||
|
|
||||||
String rpcUrl =
|
|
||||||
'https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba';
|
|
||||||
final _gasLimit = 200000;
|
final _gasLimit = 200000;
|
||||||
|
|
||||||
EthereumToken({
|
EthereumToken({
|
||||||
required Map<dynamic, dynamic> tokenData,
|
required Map<dynamic, dynamic> tokenData,
|
||||||
required Future<List<String>> walletMnemonic,
|
required Future<List<String>> walletMnemonic,
|
||||||
// required SecureStorageInterface secureStore,
|
required SecureStorageInterface secureStore,
|
||||||
}) {
|
}) {
|
||||||
_contractAddress =
|
_contractAddress =
|
||||||
EthereumAddress.fromHex(tokenData["contractAddress"] as String);
|
EthereumAddress.fromHex(tokenData["contractAddress"] as String);
|
||||||
_walletMnemonic = walletMnemonic;
|
_walletMnemonic = walletMnemonic;
|
||||||
_tokenData = tokenData;
|
_tokenData = tokenData;
|
||||||
// _secureStore = secureStore;
|
_secureStore = secureStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<AbiRequestResponse> fetchTokenAbi() async {
|
Future<AbiRequestResponse> fetchTokenAbi() async {
|
||||||
print(
|
|
||||||
"$blockExplorer?module=contract&action=getabi&address=$_contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP");
|
|
||||||
final response = await get(Uri.parse(
|
final response = await get(Uri.parse(
|
||||||
"$blockExplorer?module=contract&action=getabi&address=$_contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
"$abiUrl?module=contract&action=getabi&address=$_contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return AbiRequestResponse.fromJson(
|
return AbiRequestResponse.fromJson(
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
json.decode(response.body) as Map<String, dynamic>);
|
||||||
|
@ -156,12 +161,24 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initializeExisting() async {
|
Future<void> initializeExisting() async {
|
||||||
//TODO - GET abi FROM secure store
|
if ((await _secureStore.read(
|
||||||
AbiRequestResponse abi = await fetchTokenAbi();
|
key: '${_contractAddress.toString()}_tokenAbi')) !=
|
||||||
//Fetch token ABI so we can call token functions
|
null) {
|
||||||
if (abi.message == "OK") {
|
_tokenAbi = (await _secureStore.read(
|
||||||
_tokenAbi = abi.result;
|
key: '${_contractAddress.toString()}_tokenAbi'))!;
|
||||||
|
} else {
|
||||||
|
AbiRequestResponse abi = await fetchTokenAbi();
|
||||||
|
//Fetch token ABI so we can call token functions
|
||||||
|
if (abi.message == "OK") {
|
||||||
|
_tokenAbi = abi.result;
|
||||||
|
//Store abi in secure store
|
||||||
|
await _secureStore.write(
|
||||||
|
key: '${_contractAddress.toString()}_tokenAbi', value: _tokenAbi);
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load token abi');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final mnemonic = await _walletMnemonic;
|
final mnemonic = await _walletMnemonic;
|
||||||
String mnemonicString = mnemonic.join(' ');
|
String mnemonicString = mnemonic.join(' ');
|
||||||
|
|
||||||
|
@ -175,17 +192,21 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
_balanceFunction = _contract.function('balanceOf');
|
_balanceFunction = _contract.function('balanceOf');
|
||||||
_sendFunction = _contract.function('transfer');
|
_sendFunction = _contract.function('transfer');
|
||||||
_client = await getEthClient();
|
_client = await getEthClient();
|
||||||
print("${await totalBalance}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initializeNew() async {
|
Future<void> initializeNew() async {
|
||||||
//TODO - Save abi in secure store
|
|
||||||
AbiRequestResponse abi = await fetchTokenAbi();
|
AbiRequestResponse abi = await fetchTokenAbi();
|
||||||
//Fetch token ABI so we can call token functions
|
//Fetch token ABI so we can call token functions
|
||||||
if (abi.message == "OK") {
|
if (abi.message == "OK") {
|
||||||
_tokenAbi = abi.result;
|
_tokenAbi = abi.result;
|
||||||
|
//Store abi in secure store
|
||||||
|
await _secureStore.write(
|
||||||
|
key: '${_contractAddress.toString()}_tokenAbi', value: _tokenAbi);
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load token abi');
|
||||||
}
|
}
|
||||||
|
|
||||||
final mnemonic = await _walletMnemonic;
|
final mnemonic = await _walletMnemonic;
|
||||||
String mnemonicString = mnemonic.join(' ');
|
String mnemonicString = mnemonic.join(' ');
|
||||||
|
|
||||||
|
@ -253,7 +274,6 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
"recipientAmt": satoshiAmount,
|
"recipientAmt": satoshiAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
print("TX DATA TO BE SENT IS $txData");
|
|
||||||
return txData;
|
return txData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,12 +297,13 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement transactionData
|
Future<TransactionData> get transactionData =>
|
||||||
Future<TransactionData> get transactionData => throw UnimplementedError();
|
_transactionData ??= _fetchTransactionData();
|
||||||
|
Future<TransactionData>? _transactionData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||||
Decimal currentPrice = Decimal.parse(0.0 as String);
|
Decimal currentPrice = Decimal.zero;
|
||||||
final locale = await Devicelocale.currentLocale;
|
final locale = await Devicelocale.currentLocale;
|
||||||
final String worthNow = Format.localizedStringAsFixed(
|
final String worthNow = Format.localizedStringAsFixed(
|
||||||
value:
|
value:
|
||||||
|
@ -321,12 +342,134 @@ class EthereumToken extends TokenServiceAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<TransactionData> _fetchTransactionData() async {
|
||||||
|
String thisAddress = await currentReceivingAddress;
|
||||||
|
// final cachedTransactions = {} as TransactionData?;
|
||||||
|
int latestTxnBlockHeight = 0;
|
||||||
|
|
||||||
|
// final priceData =
|
||||||
|
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||||
|
Decimal currentPrice = Decimal.zero;
|
||||||
|
final List<Map<String, dynamic>> midSortedArray = [];
|
||||||
|
|
||||||
|
AddressTransaction txs =
|
||||||
|
await fetchAddressTransactions(thisAddress, "tokentx");
|
||||||
|
|
||||||
|
if (txs.message == "OK") {
|
||||||
|
final allTxs = txs.result;
|
||||||
|
allTxs.forEach((element) {
|
||||||
|
Map<String, dynamic> midSortedTx = {};
|
||||||
|
// create final tx map
|
||||||
|
midSortedTx["txid"] = element["hash"];
|
||||||
|
int confirmations = int.parse(element['confirmations'].toString());
|
||||||
|
|
||||||
|
int transactionAmount = int.parse(element['value'].toString());
|
||||||
|
int decimal = int.parse(
|
||||||
|
_tokenData["decimals"] as String); //Eth has up to 18 decimal places
|
||||||
|
final transactionAmountInDecimal =
|
||||||
|
transactionAmount / (pow(10, decimal));
|
||||||
|
|
||||||
|
//Convert to satoshi, default display for other coins
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
midSortedArray.sort((a, b) =>
|
||||||
|
(int.parse(b['timestamp'].toString())) -
|
||||||
|
(int.parse(a['timestamp'].toString())));
|
||||||
|
|
||||||
|
// buildDateTimeChunks
|
||||||
|
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 = {} as Map<String, models.Transaction>;
|
||||||
|
// transactionsMap
|
||||||
|
// .addAll(TransactionData.fromJson(result).getAllTransactions());
|
||||||
|
final txModel = TransactionData.fromMap(
|
||||||
|
TransactionData.fromJson(result).getAllTransactions());
|
||||||
|
|
||||||
|
cachedTxData = txModel;
|
||||||
|
return txModel;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool validateAddress(String address) {
|
bool validateAddress(String address) {
|
||||||
return isValidEthereumAddress(address);
|
return isValidEthereumAddress(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<NodeModel> getCurrentNode() async {
|
||||||
|
return NodeService(secureStorageInterface: _secureStore)
|
||||||
|
.getPrimaryNodeFor(coin: coin) ??
|
||||||
|
DefaultNodes.getNodeFor(coin);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Web3Client> getEthClient() async {
|
Future<Web3Client> getEthClient() async {
|
||||||
return Web3Client(rpcUrl, Client());
|
final node = await getCurrentNode();
|
||||||
|
return Web3Client(node.host, Client());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ abstract class TokenServiceAPI {
|
||||||
return EthereumToken(
|
return EthereumToken(
|
||||||
tokenData: tokenData,
|
tokenData: tokenData,
|
||||||
walletMnemonic: walletMnemonic,
|
walletMnemonic: walletMnemonic,
|
||||||
// secureStore: secureStorageInterface,
|
secureStore: secureStorageInterface,
|
||||||
// tracker: tracker,
|
// tracker: tracker,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,24 +4,23 @@ import 'dart:math';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'flutter_secure_storage_interface.dart';
|
|
||||||
import 'package:bip32/bip32.dart' as bip32;
|
import 'package:bip32/bip32.dart' as bip32;
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import "package:hex/hex.dart";
|
import "package:hex/hex.dart";
|
||||||
|
|
||||||
class AccountModule {
|
class AddressTransaction {
|
||||||
final String message;
|
final String message;
|
||||||
final List<dynamic> result;
|
final List<dynamic> result;
|
||||||
final String status;
|
final String status;
|
||||||
|
|
||||||
const AccountModule({
|
const AddressTransaction({
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.result,
|
required this.result,
|
||||||
required this.status,
|
required this.status,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory AccountModule.fromJson(Map<String, dynamic> json) {
|
factory AddressTransaction.fromJson(Map<String, dynamic> json) {
|
||||||
return AccountModule(
|
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 String,
|
status: json['status'] as String,
|
||||||
|
@ -30,32 +29,40 @@ class AccountModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
class GasTracker {
|
class GasTracker {
|
||||||
final int code;
|
final double average;
|
||||||
final Map<String, dynamic> data;
|
final double fast;
|
||||||
|
final double slow;
|
||||||
|
// final Map<String, dynamic> data;
|
||||||
|
|
||||||
const GasTracker({
|
const GasTracker({
|
||||||
required this.code,
|
required this.average,
|
||||||
required this.data,
|
required this.fast,
|
||||||
|
required this.slow,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory GasTracker.fromJson(Map<String, dynamic> json) {
|
factory GasTracker.fromJson(Map<String, dynamic> json) {
|
||||||
return GasTracker(
|
return GasTracker(
|
||||||
code: json['code'] as int,
|
average: json['average'] as double,
|
||||||
data: json['data'] as Map<String, dynamic>,
|
fast: json['fast'] as double,
|
||||||
|
slow: json['slow'] as double,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const blockExplorer = "https://blockscout.com/eth/mainnet/api";
|
const blockExplorer = "https://blockscout.com/eth/mainnet/api";
|
||||||
const blockExplorer = "https://api.etherscan.io/api";
|
const abiUrl =
|
||||||
|
"https://api.etherscan.io/api"; //TODO - Once our server has abi functionality update
|
||||||
const _hdPath = "m/44'/60'/0'/0";
|
const _hdPath = "m/44'/60'/0'/0";
|
||||||
const _gasTrackerUrl = "https://beaconcha.in/api/v1/execution/gasnow";
|
const _gasTrackerUrl =
|
||||||
|
"https://blockscout.com/eth/mainnet/api/v1/gas-price-oracle";
|
||||||
|
|
||||||
Future<AccountModule> fetchAccountModule(String action, String address) async {
|
Future<AddressTransaction> fetchAddressTransactions(
|
||||||
|
String address, String action) async {
|
||||||
final response = await get(Uri.parse(
|
final response = await get(Uri.parse(
|
||||||
"${blockExplorer}module=account&action=$action&address=$address&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
|
"$blockExplorer?module=account&action=$action&address=$address"));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return AccountModule.fromJson(
|
return AddressTransaction.fromJson(
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
json.decode(response.body) as Map<String, dynamic>);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to load transactions');
|
throw Exception('Failed to load transactions');
|
||||||
|
@ -63,7 +70,8 @@ Future<AccountModule> fetchAccountModule(String action, String address) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<dynamic>> getWalletTokens(String address) async {
|
Future<List<dynamic>> getWalletTokens(String address) async {
|
||||||
AccountModule tokens = await fetchAccountModule("tokentx", address);
|
AddressTransaction tokens =
|
||||||
|
await fetchAddressTransactions(address, "tokentx");
|
||||||
List<dynamic> tokensList = [];
|
List<dynamic> tokensList = [];
|
||||||
var tokenMap = {};
|
var tokenMap = {};
|
||||||
if (tokens.message == "OK") {
|
if (tokens.message == "OK") {
|
||||||
|
@ -110,7 +118,6 @@ String getPrivateKey(String mnemonic) {
|
||||||
|
|
||||||
Future<GasTracker> getGasOracle() async {
|
Future<GasTracker> getGasOracle() async {
|
||||||
final response = await get(Uri.parse(_gasTrackerUrl));
|
final response = await get(Uri.parse(_gasTrackerUrl));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return GasTracker.fromJson(
|
return GasTracker.fromJson(
|
||||||
json.decode(response.body) as Map<String, dynamic>);
|
json.decode(response.body) as Map<String, dynamic>);
|
||||||
|
@ -121,14 +128,17 @@ Future<GasTracker> getGasOracle() async {
|
||||||
|
|
||||||
Future<FeeObject> getFees() async {
|
Future<FeeObject> getFees() async {
|
||||||
GasTracker fees = await getGasOracle();
|
GasTracker fees = await getGasOracle();
|
||||||
final feesMap = fees.data;
|
final feesFast = fees.fast * (pow(10, 9));
|
||||||
|
final feesStandard = fees.average * (pow(10, 9));
|
||||||
|
final feesSlow = fees.slow * (pow(10, 9));
|
||||||
|
|
||||||
return FeeObject(
|
return FeeObject(
|
||||||
numberOfBlocksFast: 1,
|
numberOfBlocksFast: 1,
|
||||||
numberOfBlocksAverage: 3,
|
numberOfBlocksAverage: 3,
|
||||||
numberOfBlocksSlow: 3,
|
numberOfBlocksSlow: 3,
|
||||||
fast: feesMap['fast'] as int,
|
fast: feesFast.toInt(),
|
||||||
medium: feesMap['standard'] as int,
|
medium: feesStandard.toInt(),
|
||||||
slow: feesMap['slow'] as int);
|
slow: feesSlow.toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
double estimateFee(int feeRate, int gasLimit, int decimals) {
|
double estimateFee(int feeRate, int gasLimit, int decimals) {
|
||||||
|
|
Loading…
Reference in a new issue