WIP: Add token functionality

This commit is contained in:
likho 2023-01-25 18:08:27 +02:00
parent abf9f02f8e
commit d4653ea794
8 changed files with 760 additions and 611 deletions

View file

@ -4,7 +4,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
import 'package:stackwallet/providers/global/tokens_provider.dart';
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -31,11 +33,12 @@ class MyTokenSelectItem extends ConsumerWidget {
final ChangeNotifierProvider<Manager> managerProvider;
final String walletId;
final String walletAddress;
final Map<String, String> tokenData;
final Map<dynamic, dynamic> tokenData;
@override
Widget build(BuildContext context, WidgetRef ref) {
int balance = int.parse(tokenData["balance"] as String);
print("TOKEN DATA IS $tokenData");
int balance = tokenData["balance"] as int;
int tokenDecimals = int.parse(tokenData["decimals"] as String);
final balanceInDecimal = (balance / (pow(10, tokenDecimals)));
@ -51,38 +54,20 @@ class MyTokenSelectItem extends ConsumerWidget {
BorderRadius.circular(Constants.size.circularBorderRadius),
),
onPressed: () {
// ref
// .read(walletsChangeNotifierProvider)
// .getManagerProvider(walletId)
final mnemonicList = ref.read(managerProvider).mnemonic;
// final walletId = ref
// .read(managerProvider)
// .walletName;
// final manager = ref
// .read(walletsChangeNotifierProvider)
// .getManagerProvider(walletId)
// arguments: Tuple2(walletId, managerProvider, walletAddress,
// tokenData["contractAddress"])
// arguments: Tuple2(
// walletId,
// ref
// .read(tokensChangeNotifierProvider)
// .getManagerProvider(walletId)
// arguments: Tuple2(
// walletId,
// ref
// .read(tokensChangeNotifierProvider)
// .getManagerProvider(walletId)
final token = EthereumToken(
contractAddress: tokenData["contractAddress"] as String,
walletMnemonic: mnemonicList);
Navigator.of(context).pushNamed(
TokenView.routeName,
arguments: Tuple2(
arguments: Tuple3(
walletId,
ref.read(tokensChangeNotifierProvider).getManagerProvider(
tokenData["contractAddress"] as String)),
ref
.read(walletsChangeNotifierProvider)
.getManagerProvider(walletId),
token),
);
},

View file

@ -31,7 +31,7 @@ class MyTokensList extends StatelessWidget {
managerProvider: managerProvider,
walletId: walletId,
walletAddress: walletAddress,
tokenData: tokens[index] as Map<String, String>,
tokenData: tokens[index] as Map<dynamic, dynamic>,
),
);
},

File diff suppressed because it is too large Load diff

View file

@ -783,8 +783,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
.read(managerProvider)
.currentReceivingAddress;
// String walletTokens = await
List<dynamic> tokens =
await getWalletTokens(await ref
.read(managerProvider)

View file

@ -117,6 +117,7 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncin
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/tokens/ethereum/ethereum_token.dart';
import 'package:stackwallet/services/tokens/token_manager.dart';
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -1327,14 +1328,14 @@ class RouteGenerator {
// }
case TokenView.routeName:
if (args is Tuple2<String, ChangeNotifierProvider<TokenManager>>) {
if (args
is Tuple3<String, ChangeNotifierProvider<Manager>, EthereumToken>) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => TokenView(
contractAddress: args.item1,
walletId: args.item1,
managerProvider: args.item2,
// walletAddress: args.item3,
// contractAddress: args.item4,
token: args.item3,
),
settings: RouteSettings(
name: settings.name,

View file

@ -5,7 +5,6 @@ import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import "package:hex/hex.dart";
import 'package:bitcoindart/bitcoindart.dart';
import 'package:decimal/decimal.dart';
import 'package:devicelocale/devicelocale.dart';
import 'package:ethereum_addresses/ethereum_addresses.dart';
@ -22,7 +21,6 @@ import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:string_to_hex/string_to_hex.dart';
import 'package:web3dart/web3dart.dart';
import 'package:web3dart/web3dart.dart' as web3;
import 'package:web3dart/web3dart.dart' as Transaction;
@ -45,7 +43,7 @@ import 'package:stackwallet/services/event_bus/events/global/updated_in_backgrou
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
const int MINIMUM_CONFIRMATIONS = 5;
const int MINIMUM_CONFIRMATIONS = 3;
//THis is used for mapping transactions per address from the block explorer
class AddressTransaction {
@ -469,11 +467,15 @@ class EthereumWallet extends CoinServiceAPI {
isSendAll = true;
}
print("SATOSHI AMOUNT BEFORE $satoshiAmount");
print("FEE IS $fee");
if (isSendAll) {
//Subtract fee amount from send amount
satoshiAmount -= feeEstimate;
}
print("SATOSHI AMOUNT AFTER $satoshiAmount");
Map<String, dynamic> txData = {
"fee": feeEstimate,
"feeInWei": fee,
@ -505,8 +507,6 @@ class EthereumWallet extends CoinServiceAPI {
String privateKey = getPrivateKey(mnemonic);
_credentials = EthPrivateKey.fromHex(privateKey);
// _credentials = EthPrivateKey.fromHex(StringToHex.toHexString(mnemonic));
// print(_credentials.address);
//Get ERC-20 transactions for wallet (So we can get the and save wallet's ERC-20 TOKENS
AddressTransaction tokenTransactions = await fetchAddressTransactions(
@ -1063,8 +1063,6 @@ class EthereumWallet extends CoinServiceAPI {
@override
set walletName(String newName) => _walletName = newName;
// Future<String>
void stopNetworkAlivePinging() {
_networkAliveTimer?.cancel();
_networkAliveTimer = null;

View file

@ -1,30 +1,69 @@
import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:http/http.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/services/tokens/token_service.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:web3dart/web3dart.dart';
class AbiRequestResponse {
final String message;
final String result;
final String status;
const AbiRequestResponse({
required this.message,
required this.result,
required this.status,
});
factory AbiRequestResponse.fromJson(Map<String, dynamic> json) {
return AbiRequestResponse(
message: json['message'] as String,
result: json['result'] as String,
status: json['status'] as String,
);
}
}
class EthereumToken extends TokenServiceAPI {
@override
late bool shouldAutoSync;
late String _walletId;
late String _contractAddress;
late EthPrivateKey _credentials;
late Future<List<String>> _walletMnemonic;
late SecureStorageInterface _secureStore;
late String _tokenAbi;
late final TransactionNotificationTracker txTracker;
String rpcUrl =
'https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba';
EthereumToken({
required String contractAddress,
required String walletId,
required SecureStorageInterface secureStore,
required TransactionNotificationTracker tracker,
required Future<List<String>> walletMnemonic,
// required SecureStorageInterface secureStore,
}) {
txTracker = tracker;
_walletId = walletId;
_contractAddress = contractAddress;
_secureStore = secureStore;
_walletMnemonic = walletMnemonic;
// _secureStore = secureStore;
}
Future<AbiRequestResponse> fetchTokenAbi() async {
final response = await get(Uri.parse(
"https://api.etherscan.io/api?module=contract&action=getabi&address=$_contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
if (response.statusCode == 200) {
return AbiRequestResponse.fromJson(
json.decode(response.body) as Map<String, dynamic>);
} else {
throw Exception('Failed to load transactions');
}
}
@override
@ -64,7 +103,18 @@ class EthereumToken extends TokenServiceAPI {
Future<FeeObject> get fees => throw UnimplementedError();
@override
Future<void> initializeExisting() {
Future<void> initializeExisting() async {
AbiRequestResponse abi = await fetchTokenAbi();
//Fetch token ABI so we can call token functions
if (abi.message == "OK") {
_tokenAbi = abi.result;
}
final mnemonic = await _walletMnemonic;
String mnemonicString = mnemonic.join(' ');
//Get private key for given mnemonic
String privateKey = getPrivateKey(mnemonicString);
// TODO: implement initializeExisting
throw UnimplementedError();
}

View file

@ -1,6 +1,11 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'flutter_secure_storage_interface.dart';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import "package:hex/hex.dart";
class AccountModule {
final String message;
@ -22,11 +27,13 @@ class AccountModule {
}
}
const _blockExplorer = "https://blockscout.com/eth/mainnet/api?";
const _blockExplorer = "https://api.etherscan.io/api?";
late SecureStorageInterface _secureStore;
const _hdPath = "m/44'/60'/0'/0";
Future<AccountModule> fetchAccountModule(String action, String address) async {
final response = await get(Uri.parse(
"${_blockExplorer}module=account&action=$action&address=$address"));
"${_blockExplorer}module=account&action=$action&address=$address&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
if (response.statusCode == 200) {
return AccountModule.fromJson(
json.decode(response.body) as Map<String, dynamic>);
@ -36,21 +43,48 @@ Future<AccountModule> fetchAccountModule(String action, String address) async {
}
Future<List<dynamic>> getWalletTokens(String address) async {
AccountModule tokens = await fetchAccountModule("tokenlist", address);
//THIS IS ONLY HARD CODED UNTIL API WORKS AGAIN - TODO REMOVE HARDCODED
return [
{
"balance": "369039500000000000",
"contractAddress": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
"decimals": "18",
"name": "Uniswap",
"symbol": "UNI",
"type": "ERC-20"
}
];
AccountModule tokens = await fetchAccountModule("tokentx", address);
List<dynamic> tokensList = [];
var tokenMap = {};
if (tokens.message == "OK") {
return tokens.result as List<String>;
final allTxs = tokens.result;
print("RESULT IS $allTxs");
allTxs.forEach((element) {
String key = element["tokenSymbol"] as String;
tokenMap[key] = {};
tokenMap[key]["balance"] = 0;
if (tokenMap.containsKey(key)) {
tokenMap[key]["contractAddress"] = element["contractAddress"] as String;
tokenMap[key]["decimals"] = element["tokenDecimal"];
tokenMap[key]["name"] = element["tokenName"];
tokenMap[key]["symbol"] = element["tokenSymbol"];
if (checksumEthereumAddress(address) == address) {
tokenMap[key]["balance"] += int.parse(element["value"] as String);
} else {
tokenMap[key]["balance"] -= int.parse(element["value"] as String);
}
}
});
tokenMap.forEach((key, value) {
tokensList.add(value as Map<dynamic, dynamic>);
});
return tokensList;
}
return <String>[];
return <dynamic>[];
}
String getPrivateKey(String mnemonic) {
final isValidMnemonic = bip39.validateMnemonic(mnemonic);
if (!isValidMnemonic) {
throw 'Invalid mnemonic';
}
final seed = bip39.mnemonicToSeed(mnemonic);
final root = bip32.BIP32.fromSeed(seed);
const index = 0;
final addressAtIndex = root.derivePath("$_hdPath/$index");
return HEX.encode(addressAtIndex.privateKey as List<int>);
}