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_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),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue