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_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/pages/wallets_view/wallets_view.dart';
import 'package:stackwallet/providers/global/tokens_provider.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/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';
@ -31,11 +33,12 @@ class MyTokenSelectItem extends ConsumerWidget {
final ChangeNotifierProvider<Manager> managerProvider; final ChangeNotifierProvider<Manager> managerProvider;
final String walletId; final String walletId;
final String walletAddress; final String walletAddress;
final Map<String, String> tokenData; final Map<dynamic, dynamic> tokenData;
@override @override
Widget build(BuildContext context, WidgetRef ref) { 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); int tokenDecimals = int.parse(tokenData["decimals"] as String);
final balanceInDecimal = (balance / (pow(10, tokenDecimals))); final balanceInDecimal = (balance / (pow(10, tokenDecimals)));
@ -51,38 +54,20 @@ class MyTokenSelectItem extends ConsumerWidget {
BorderRadius.circular(Constants.size.circularBorderRadius), BorderRadius.circular(Constants.size.circularBorderRadius),
), ),
onPressed: () { onPressed: () {
// ref final mnemonicList = ref.read(managerProvider).mnemonic;
// .read(walletsChangeNotifierProvider)
// .getManagerProvider(walletId)
// final walletId = ref final token = EthereumToken(
// .read(managerProvider) contractAddress: tokenData["contractAddress"] as String,
// .walletName; walletMnemonic: mnemonicList);
// 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)
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
TokenView.routeName, TokenView.routeName,
arguments: Tuple2( arguments: Tuple3(
walletId, walletId,
ref.read(tokensChangeNotifierProvider).getManagerProvider( ref
tokenData["contractAddress"] as String)), .read(walletsChangeNotifierProvider)
.getManagerProvider(walletId),
token),
); );
}, },

View file

@ -31,7 +31,7 @@ class MyTokensList extends StatelessWidget {
managerProvider: managerProvider, managerProvider: managerProvider,
walletId: walletId, walletId: walletId,
walletAddress: walletAddress, 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) .read(managerProvider)
.currentReceivingAddress; .currentReceivingAddress;
// String walletTokens = await
List<dynamic> tokens = List<dynamic> tokens =
await getWalletTokens(await ref await getWalletTokens(await ref
.read(managerProvider) .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/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/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_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/services/tokens/token_manager.dart';
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart'; import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -1327,14 +1328,14 @@ class RouteGenerator {
// } // }
case TokenView.routeName: case TokenView.routeName:
if (args is Tuple2<String, ChangeNotifierProvider<TokenManager>>) { if (args
is Tuple3<String, ChangeNotifierProvider<Manager>, EthereumToken>) {
return getRoute( return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute, shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => TokenView( builder: (_) => TokenView(
contractAddress: args.item1, walletId: args.item1,
managerProvider: args.item2, managerProvider: args.item2,
// walletAddress: args.item3, token: args.item3,
// contractAddress: args.item4,
), ),
settings: RouteSettings( settings: RouteSettings(
name: settings.name, 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:bip39/bip39.dart' as bip39;
import "package:hex/hex.dart"; import "package:hex/hex.dart";
import 'package:bitcoindart/bitcoindart.dart';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:devicelocale/devicelocale.dart'; import 'package:devicelocale/devicelocale.dart';
import 'package:ethereum_addresses/ethereum_addresses.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/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/prefs.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';
import 'package:web3dart/web3dart.dart' as web3; import 'package:web3dart/web3dart.dart' as web3;
import 'package:web3dart/web3dart.dart' as Transaction; 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/services/node_service.dart';
import 'package:stackwallet/utilities/default_nodes.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 //THis is used for mapping transactions per address from the block explorer
class AddressTransaction { class AddressTransaction {
@ -469,11 +467,15 @@ class EthereumWallet extends CoinServiceAPI {
isSendAll = true; isSendAll = true;
} }
print("SATOSHI AMOUNT BEFORE $satoshiAmount");
print("FEE IS $fee");
if (isSendAll) { if (isSendAll) {
//Subtract fee amount from send amount //Subtract fee amount from send amount
satoshiAmount -= feeEstimate; satoshiAmount -= feeEstimate;
} }
print("SATOSHI AMOUNT AFTER $satoshiAmount");
Map<String, dynamic> txData = { Map<String, dynamic> txData = {
"fee": feeEstimate, "fee": feeEstimate,
"feeInWei": fee, "feeInWei": fee,
@ -505,8 +507,6 @@ class EthereumWallet extends CoinServiceAPI {
String privateKey = getPrivateKey(mnemonic); String privateKey = getPrivateKey(mnemonic);
_credentials = EthPrivateKey.fromHex(privateKey); _credentials = EthPrivateKey.fromHex(privateKey);
// _credentials = EthPrivateKey.fromHex(StringToHex.toHexString(mnemonic));
// print(_credentials.address); // print(_credentials.address);
//Get ERC-20 transactions for wallet (So we can get the and save wallet's ERC-20 TOKENS //Get ERC-20 transactions for wallet (So we can get the and save wallet's ERC-20 TOKENS
AddressTransaction tokenTransactions = await fetchAddressTransactions( AddressTransaction tokenTransactions = await fetchAddressTransactions(
@ -1063,8 +1063,6 @@ class EthereumWallet extends CoinServiceAPI {
@override @override
set walletName(String newName) => _walletName = newName; set walletName(String newName) => _walletName = newName;
// Future<String>
void stopNetworkAlivePinging() { void stopNetworkAlivePinging() {
_networkAliveTimer?.cancel(); _networkAliveTimer?.cancel();
_networkAliveTimer = null; _networkAliveTimer = null;

View file

@ -1,30 +1,69 @@
import 'dart:convert';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:http/http.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart'; import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/services/tokens/token_service.dart'; import 'package:stackwallet/services/tokens/token_service.dart';
import 'package:stackwallet/utilities/enums/coin_enum.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/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/services/transaction_notification_tracker.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 { class EthereumToken extends TokenServiceAPI {
@override @override
late bool shouldAutoSync; late bool shouldAutoSync;
late String _walletId;
late String _contractAddress; late String _contractAddress;
late EthPrivateKey _credentials;
late Future<List<String>> _walletMnemonic;
late SecureStorageInterface _secureStore; late SecureStorageInterface _secureStore;
late String _tokenAbi;
late final TransactionNotificationTracker txTracker; late final TransactionNotificationTracker txTracker;
String rpcUrl =
'https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba';
EthereumToken({ EthereumToken({
required String contractAddress, required String contractAddress,
required String walletId, required Future<List<String>> walletMnemonic,
required SecureStorageInterface secureStore, // required SecureStorageInterface secureStore,
required TransactionNotificationTracker tracker,
}) { }) {
txTracker = tracker;
_walletId = walletId;
_contractAddress = contractAddress; _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 @override
@ -64,7 +103,18 @@ class EthereumToken extends TokenServiceAPI {
Future<FeeObject> get fees => throw UnimplementedError(); Future<FeeObject> get fees => throw UnimplementedError();
@override @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 // TODO: implement initializeExisting
throw UnimplementedError(); throw UnimplementedError();
} }

View file

@ -1,6 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart'; 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 { class AccountModule {
final String message; 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 { Future<AccountModule> fetchAccountModule(String action, String address) async {
final response = await get(Uri.parse( 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) { if (response.statusCode == 200) {
return AccountModule.fromJson( return AccountModule.fromJson(
json.decode(response.body) as Map<String, dynamic>); 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 { Future<List<dynamic>> getWalletTokens(String address) async {
AccountModule tokens = await fetchAccountModule("tokenlist", address); AccountModule tokens = await fetchAccountModule("tokentx", address);
//THIS IS ONLY HARD CODED UNTIL API WORKS AGAIN - TODO REMOVE HARDCODED List<dynamic> tokensList = [];
return [ var tokenMap = {};
{
"balance": "369039500000000000",
"contractAddress": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
"decimals": "18",
"name": "Uniswap",
"symbol": "UNI",
"type": "ERC-20"
}
];
if (tokens.message == "OK") { 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);
} }
return <String>[]; }
});
tokenMap.forEach((key, value) {
tokensList.add(value as Map<dynamic, dynamic>);
});
return tokensList;
}
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>);
} }