mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-23 11:59:30 +00:00
WIP: Add token functionality
This commit is contained in:
parent
abf9f02f8e
commit
d4653ea794
8 changed files with 760 additions and 611 deletions
|
@ -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),
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -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
|
@ -783,8 +783,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
.read(managerProvider)
|
||||
.currentReceivingAddress;
|
||||
|
||||
// String walletTokens = await
|
||||
|
||||
List<dynamic> tokens =
|
||||
await getWalletTokens(await ref
|
||||
.read(managerProvider)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue