diff --git a/android/app/build.gradle b/android/app/build.gradle index 8ad1f598f..1f682909a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) { def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") + throw GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') diff --git a/lib/crypto/eosdart/src/client.dart b/lib/crypto/eosdart/src/client.dart index 0ac06f93a..ec998b5af 100644 --- a/lib/crypto/eosdart/src/client.dart +++ b/lib/crypto/eosdart/src/client.dart @@ -224,7 +224,7 @@ class EOSClient { /// Get Key Accounts Future getKeyAccounts(String pubKey) async { return _post('/history/get_key_accounts', {'public_key': pubKey}).then((accountNames) { - return AccountNames.fromJson(accountNames); + return AccountNames.fromJson(accountNames as Map); }); } @@ -314,19 +314,6 @@ class EOSClient { return ser.arrayToHex(buffer.asUint8List()); } -// Future> _getTransactionAbis(Transaction transaction) async { -// Set accounts = Set(); -// List result = []; -// -// for (Action action in transaction.actions) { -// accounts.add(action.account); -// } -// -// for (String accountName in accounts) { -// result.add(await this.getRawAbi(accountName)); -// } -// } - Future _pushTransactionArgs( String? chainId, Type transactionType, Transaction transaction, bool sign) async { final List signatures = []; diff --git a/lib/crypto/eosdart/src/numeric.dart b/lib/crypto/eosdart/src/numeric.dart index c9c2e17e4..ac7f06b63 100644 --- a/lib/crypto/eosdart/src/numeric.dart +++ b/lib/crypto/eosdart/src/numeric.dart @@ -109,7 +109,7 @@ String binaryToDecimal(Uint8List bignum, {minDigits = 1}) { /// @param minDigits 0-pad result to this many digits String signedBinaryToDecimal(Uint8List bignum, {int minDigits = 1}) { if (isNegative(bignum)) { - var x = bignum.getRange(0, 0) as Uint8List; + var x = Uint8List.fromList(bignum.getRange(0, bignum.length).toList()); negate(x); return '-' + binaryToDecimal(x, minDigits: minDigits); } diff --git a/lib/datasource/local/models/token_data_model.dart b/lib/datasource/local/models/token_data_model.dart index b3cc67f4d..b139b4ae8 100644 --- a/lib/datasource/local/models/token_data_model.dart +++ b/lib/datasource/local/models/token_data_model.dart @@ -7,7 +7,8 @@ import 'package:seeds/utils/rate_states_extensions.dart'; class TokenDataModel extends AmountDataModel { String? id; - TokenDataModel(double amount, {TokenModel token = seedsToken}) + final TokenModel token; + TokenDataModel(double amount, {TokenModel this.token = seedsToken}) : super( amount: amount, symbol: token.symbol, @@ -37,7 +38,7 @@ class TokenDataModel extends AmountDataModel { } TokenDataModel copyWith(double amount) { - return TokenDataModel(amount, token: TokenModel.fromId(id!)); + return TokenDataModel(amount, token: token); } } diff --git a/lib/datasource/local/settings_storage.dart b/lib/datasource/local/settings_storage.dart index dd00b7437..a442dcb1d 100644 --- a/lib/datasource/local/settings_storage.dart +++ b/lib/datasource/local/settings_storage.dart @@ -67,7 +67,7 @@ class _SettingsStorage { String get selectedFiatCurrency => _preferences.getString(_kSelectedFiatCurrency) ?? getPlatformCurrency(); - TokenModel get selectedToken => TokenModel.fromId(_preferences.getString(_kSelectedToken) ?? seedsToken.id); + TokenModel get selectedToken => TokenModel.fromId(_preferences.getString(_kSelectedToken) ?? seedsToken.id) ?? seedsToken; bool get inRecoveryMode => _preferences.getBool(_kInRecoveryMode) ?? false; diff --git a/lib/datasource/remote/api/balance_repository.dart b/lib/datasource/remote/api/balance_repository.dart index 9b0d0c534..13692ac48 100644 --- a/lib/datasource/remote/api/balance_repository.dart +++ b/lib/datasource/remote/api/balance_repository.dart @@ -6,7 +6,7 @@ import 'package:seeds/datasource/remote/model/balance_model.dart'; class BalanceRepository extends HttpRepository { Future> getTokenBalance(String userAccount, {required String tokenContract, required String symbol}) { - print('[http] get seeds getTokenBalance $userAccount for $symbol'); + print('[http] get getTokenBalance $userAccount for $symbol'); final String request = ''' { diff --git a/lib/datasource/remote/api/eos_repo/eos_repository.dart b/lib/datasource/remote/api/eos_repo/eos_repository.dart index 18a296450..f859bdc01 100644 --- a/lib/datasource/remote/api/eos_repo/eos_repository.dart +++ b/lib/datasource/remote/api/eos_repo/eos_repository.dart @@ -74,7 +74,12 @@ abstract class EosRepository { } ErrorResult mapEosError(dynamic error) { + final regex = RegExp(r'^.*Internal Service Error.*assertion failure with message: ([^\"]*)'); print('mapEosError: $error'); + final match = regex.firstMatch(error); + if (match != null && match.groupCount == 1) { + return ErrorResult("Transaction error:\n${match.group(1)!}"); + } return ErrorResult(error); } } diff --git a/lib/datasource/remote/api/invoice_repository.dart b/lib/datasource/remote/api/invoice_repository.dart index 3d56782d9..b4c282ab0 100644 --- a/lib/datasource/remote/api/invoice_repository.dart +++ b/lib/datasource/remote/api/invoice_repository.dart @@ -2,6 +2,7 @@ import 'package:async/async.dart'; import 'package:seeds/crypto/dart_esr/dart_esr.dart' as esr; import 'package:seeds/datasource/local/models/token_data_model.dart'; +import 'package:seeds/datasource/remote/model/token_model.dart'; import 'package:seeds/datasource/remote/api/eos_repo/eos_repository.dart'; import 'package:seeds/datasource/remote/api/eos_repo/seeds_eos_actions.dart'; import 'package:seeds/datasource/remote/firebase/firebase_remote_config.dart'; @@ -17,7 +18,7 @@ class InvoiceRepository extends EosRepository { final Map data = { 'from': esr.ESRConstants.PlaceholderName, 'to': accountName, - 'quantity': tokenAmount.asFormattedString(), + 'quantity': TokenModel.getAssetString(tokenAmount.id, tokenAmount.amount), 'memo': memo ?? '' }; diff --git a/lib/datasource/remote/api/key_accounts_repository.dart b/lib/datasource/remote/api/key_accounts_repository.dart index 75338f362..395085a70 100644 --- a/lib/datasource/remote/api/key_accounts_repository.dart +++ b/lib/datasource/remote/api/key_accounts_repository.dart @@ -3,6 +3,7 @@ import 'package:http/http.dart' as http; import 'package:seeds/datasource/remote/api/http_repo/http_repository.dart'; class KeyAccountsRepository extends HttpRepository { + Future> getKeyAccounts(String publicKey) { print('[http] getKeyAccounts'); @@ -14,7 +15,7 @@ class KeyAccountsRepository extends HttpRepository { .then((http.Response response) => mapHttpResponse(response, (dynamic body) { print('result: $body'); - final result = List.from(body['account_names']); + final result = List.from(body['account_names'] as Iterable); result.sort(); @@ -22,20 +23,23 @@ class KeyAccountsRepository extends HttpRepository { })) .catchError((dynamic error) => mapHttpError(error)); } - +} + // Future> getAccountsByKey(String publicKey) { // print('[http] getAccountsByKey'); // final url = Uri.parse('$baseURL/v1/chain/get_accounts_by_authorizers'); - // final body = '{ "accounts": [], "keys": []"$publicKey"] }'; + // final body = '{ "accounts": [], "keys": ["$publicKey"] }'; // return http // .post(url, headers: headers, body: body) // .then((http.Response response) => mapHttpResponse(response, (dynamic body) { // print('result: $body'); - + // // restriction to `active` permission matches ProfileRepository.getAccountPublicKeys // final result = - // List.from(body['accounts']).map((e) => e['account_name']).toList().toSet().toList(); + // List.from(body['accounts'] as List).cast>() + // .where((e) => e['permission_name'] as String == 'active') + // .map((e) => e['account_name'] as String).toList().toSet().toList(); // result.sort(); @@ -43,4 +47,4 @@ class KeyAccountsRepository extends HttpRepository { // })) // .catchError((dynamic error) => mapHttpError(error)); // } -} + diff --git a/lib/datasource/remote/api/profile_repository.dart b/lib/datasource/remote/api/profile_repository.dart index 6d54aa6e6..274781e3a 100644 --- a/lib/datasource/remote/api/profile_repository.dart +++ b/lib/datasource/remote/api/profile_repository.dart @@ -37,8 +37,8 @@ class ProfileRepository extends HttpRepository with EosRepository { // TODO(Raul): Unify this code with _getAccountPermissions in guardians repo // Returns the first active key permission - String - Future getAccountPublicKey(String accountName) async { - print('[http] getAccountPublicKey'); + Future getAccountPublicKeys(String accountName) async { + print('[http] getAccountPublicKeys'); final url = Uri.parse('$host/v1/chain/get_account'); final body = '{ "account_name": "$accountName" }'; @@ -50,7 +50,7 @@ class ProfileRepository extends HttpRepository with EosRepository { final permissions = allAccounts.map((item) => Permission.fromJson(item)).toList(); final Permission activePermission = permissions.firstWhere((element) => element.permName == "active"); final RequiredAuth? activeAuth = activePermission.requiredAuth; - return activeAuth?.keys?.first?.key; + return activeAuth?.keys?.map((e)=>e?.key).toList(); })) .catchError((error) => mapHttpError(error)); } diff --git a/lib/datasource/remote/api/stat_repository.dart b/lib/datasource/remote/api/stat_repository.dart new file mode 100644 index 000000000..462de14db --- /dev/null +++ b/lib/datasource/remote/api/stat_repository.dart @@ -0,0 +1,31 @@ +import 'package:async/async.dart'; +import 'package:http/http.dart' as http; +import 'package:seeds/datasource/remote/api/http_repo/http_repository.dart'; +import 'package:seeds/datasource/remote/model/balance_model.dart'; +import 'package:seeds/datasource/remote/model/token_model.dart'; +import 'package:seeds/datasource/remote/model/stat_model.dart'; + +class StatRepository extends HttpRepository { + Future> getTokenStat( + {required String tokenContract, required String symbol}) { + print('[http] get getTokenStat for $symbol'); + + final String request = ''' + { + "code":"$tokenContract", + "table": "stat", + "scope":"$symbol", + "json": "true" + } + '''; + + final statURL = Uri.parse('$baseURL/v1/chain/get_table_rows'); + + return http + .post(statURL, headers: headers, body: request) + .then((http.Response response) => mapHttpResponse(response, (dynamic body) { + return StatModel.fromJson(body); + })) + .catchError((dynamic error) => mapHttpError(error)); + } +} diff --git a/lib/datasource/remote/api/transactions_repository.dart b/lib/datasource/remote/api/transactions_repository.dart index 55fc8be07..69ee80e16 100644 --- a/lib/datasource/remote/api/transactions_repository.dart +++ b/lib/datasource/remote/api/transactions_repository.dart @@ -1,4 +1,5 @@ import 'package:async/async.dart'; +import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; import 'package:seeds/datasource/remote/api/http_repo/http_repository.dart'; import 'package:seeds/datasource/remote/model/transaction_model.dart'; @@ -13,7 +14,7 @@ class TransactionsListRepository extends HttpRepository { .then((http.Response response) => mapHttpResponse>(response, (dynamic body) { final List transfers = body['actions'].toList(); - return List.of(transfers.map((transfer) => TransactionModel.fromJson(transfer))); + return List.of(transfers.map((transfer) => TransactionModel.fromJson(transfer)).whereNotNull()); })) .catchError((dynamic error) => mapHttpError(error)); } diff --git a/lib/datasource/remote/model/stat_model.dart b/lib/datasource/remote/model/stat_model.dart new file mode 100644 index 000000000..70cf8fbdb --- /dev/null +++ b/lib/datasource/remote/model/stat_model.dart @@ -0,0 +1,20 @@ + +/// Token per USD +class StatModel { + final String supplyString; + final String maxSupplyString; + final String issuer; + + const StatModel(this.supplyString, this.maxSupplyString, this.issuer); + + factory StatModel.fromJson(Map? json) { + if (json != null && json['rows'].isNotEmpty) { + final supplyString = json['rows'][0]['supply'] ?? ''; + final maxSupplyString = json['rows'][0]['max_supply'] ?? ''; + final issuer = json['rows'][0]['issuer'] ?? ''; + return StatModel(supplyString, maxSupplyString, issuer); + } else { + return const StatModel('', '', ''); + } + } +} diff --git a/lib/datasource/remote/model/token_model.dart b/lib/datasource/remote/model/token_model.dart index 926a99af8..85dbf0f51 100644 --- a/lib/datasource/remote/model/token_model.dart +++ b/lib/datasource/remote/model/token_model.dart @@ -14,6 +14,7 @@ class TokenModel extends Equatable { static const seedsEcosysUsecase = 'seedsecosys'; static List allTokens = [seedsToken]; static JsonSchema? tmastrSchema; + static Map contractPrecisions = {"token.seeds#SEEDS": 4}; final String chainName; final String contract; final String symbol; @@ -21,6 +22,7 @@ class TokenModel extends Equatable { final String backgroundImageUrl; final String logoUrl; final String balanceSubTitle; + final String overdraw; final int precision; final List? usecases; @@ -47,6 +49,7 @@ class TokenModel extends Equatable { required this.backgroundImageUrl, required this.logoUrl, required this.balanceSubTitle, + required this.overdraw, this.precision = 4, this.usecases, }); @@ -95,15 +98,16 @@ class TokenModel extends Equatable { symbol: parsedJson["symbol"]!, name: parsedJson["name"]!, logoUrl: parsedJson["logo"]!, - balanceSubTitle: parsedJson["subtitle"], + balanceSubTitle: parsedJson["subtitle"] ?? CurrencyInfoCard.defaultBalanceSubtitle, backgroundImageUrl: parsedJson["bg_image"] ?? CurrencyInfoCard.defaultBgImage, + overdraw: parsedJson["overdraw"] ?? "allow", precision: parsedJson["precision"] ?? 4, usecases: parsedJson["usecases"], ); } - factory TokenModel.fromId(String tokenId) { - return allTokens.firstWhere((e) => e.id == tokenId); + static TokenModel? fromId(String tokenId) { + return allTokens.firstWhereOrNull((e) => e.id == tokenId); } static TokenModel? fromSymbolOrNull(String symbol) { @@ -113,8 +117,37 @@ class TokenModel extends Equatable { @override List get props => [chainName, contract, symbol]; - String getAssetString(double quantity) { - return "${quantity.toStringAsFixed(precision)} $symbol"; + static String getAssetString(String? id, double quantity) { + if (id!=null && TokenModel.fromId(id)!=null && contractPrecisions.containsKey(id)) { + final symbol = TokenModel.fromId(id)!.symbol; + return symbol==null ? "" : "${quantity.toStringAsFixed(contractPrecisions[id]!)} $symbol"; + } else { + return ""; + } + } + + void setPrecisionFromString(String s) { + final amount = s.split(' ')[0]; + final ss = amount.split('.'); + if (ss.isEmpty) { + return; + } + contractPrecisions[this.id] = ss.length==1 ? 0 : ss[1].length; + } + + // enabling 'send' transfer validity checks, e.g. Mutual Credit, + // membership limitations + bool blockTransfer(double insufficiency, String? toAccount) { + if (overdraw == "block") { + return insufficiency > 0; + } else if (overdraw == "allow") { + return false; + } + print("unexpected overdraw field: $overdraw"); + return false; + } + String? warnTransfer(double insufficiency, String? toAccount) { + return insufficiency > 0 ? "insufficient balance" : null; } static Future updateModels(List acceptList, [List? infoList]) async { @@ -142,6 +175,7 @@ class TokenModel extends Equatable { } } allTokens = _staticTokenList; + contractPrecisions = Map.fromEntries(allTokens.map((t) => MapEntry(t.id , t.precision))); } static void pruneRemoving(List useCaseList) { @@ -163,6 +197,7 @@ const seedsToken = TokenModel( backgroundImageUrl: 'assets/images/wallet/currency_info_cards/seeds/background.jpg', logoUrl: 'assets/images/wallet/currency_info_cards/seeds/logo.jpg', balanceSubTitle: 'Wallet Balance', + overdraw: "block", usecases: ["lightwallet", TokenModel.seedsEcosysUsecase], ); @@ -175,6 +210,7 @@ const _husdToken = TokenModel( backgroundImageUrl: 'assets/images/wallet/currency_info_cards/husd/background.jpg', logoUrl: 'assets/images/wallet/currency_info_cards/husd/logo.jpg', balanceSubTitle: 'Wallet Balance', + overdraw: "block", precision: 2, usecases: ["lightwallet", TokenModel.seedsEcosysUsecase], ); @@ -187,6 +223,7 @@ const _hyphaToken = TokenModel( backgroundImageUrl: 'assets/images/wallet/currency_info_cards/hypha/background.jpg', logoUrl: 'assets/images/wallet/currency_info_cards/hypha/logo.jpg', balanceSubTitle: 'Wallet Balance', + overdraw: "block", precision: 2, usecases: ["lightwallet", TokenModel.seedsEcosysUsecase], ); @@ -199,6 +236,7 @@ const _localScaleToken = TokenModel( backgroundImageUrl: 'assets/images/wallet/currency_info_cards/lscl/background.jpg', logoUrl: 'assets/images/wallet/currency_info_cards/lscl/logo.png', balanceSubTitle: 'Wallet Balance', + overdraw: "block", usecases: ["lightwallet"], ); @@ -210,6 +248,7 @@ const _starsToken = TokenModel( backgroundImageUrl: 'assets/images/wallet/currency_info_cards/stars/background.jpg', logoUrl: 'assets/images/wallet/currency_info_cards/stars/logo.jpg', balanceSubTitle: 'Wallet Balance', + overdraw: "block", usecases: ["lightwallet"], ); @@ -221,5 +260,6 @@ const _telosToken = TokenModel( backgroundImageUrl: 'assets/images/wallet/currency_info_cards/tlos/background.png', logoUrl: 'assets/images/wallet/currency_info_cards/tlos/logo.png', balanceSubTitle: 'Wallet Balance', + overdraw: "block", usecases: ["lightwallet"], ); diff --git a/lib/datasource/remote/model/transaction_model.dart b/lib/datasource/remote/model/transaction_model.dart index 1605d848c..c6aa28030 100644 --- a/lib/datasource/remote/model/transaction_model.dart +++ b/lib/datasource/remote/model/transaction_model.dart @@ -25,15 +25,20 @@ class TransactionModel extends Equatable { @override List get props => [transactionId]; - factory TransactionModel.fromJson(Map json) { - return TransactionModel( - from: json['act']['data']['from'], - to: json['act']['data']['to'], - quantity: json['act']['data']['quantity'], - memo: json['act']['data']['memo'], - timestamp: parseTimestamp(json['@timestamp']), - transactionId: json['trx_id'], - ); + static TransactionModel? fromJson(Map json) { + try { + return TransactionModel( + from: json['act']['data']['from'], + to: json['act']['data']['to'], + quantity: json['act']['data']['quantity'], + memo: json['act']['data']['memo'], + timestamp: parseTimestamp(json['@timestamp']), + transactionId: json['trx_id'], + ); + } + catch (e){ + return null; + } } factory TransactionModel.fromJsonMongo(Map json) { diff --git a/lib/domain-shared/shared_use_cases/get_token_models_use_case.dart b/lib/domain-shared/shared_use_cases/get_token_models_use_case.dart index 8ff00fe05..8f6d45659 100644 --- a/lib/domain-shared/shared_use_cases/get_token_models_use_case.dart +++ b/lib/domain-shared/shared_use_cases/get_token_models_use_case.dart @@ -1,7 +1,10 @@ import 'package:collection/collection.dart'; +import 'package:dynamic_parallel_queue/dynamic_parallel_queue.dart'; import 'package:seeds/datasource/remote/api/tokenmodels_repository.dart'; +import 'package:seeds/datasource/remote/api/stat_repository.dart'; import 'package:seeds/datasource/remote/model/token_model.dart'; import 'package:seeds/domain-shared/base_use_case.dart'; +import 'package:seeds/screens/wallet/components/tokens_cards/interactor/usecases/load_token_balances_use_case.dart'; class TokenModelSelector { final List acceptList; @@ -78,8 +81,33 @@ class GetTokenModelsUseCase extends InputUseCase, TokenModelSel for (final token in tokens) { token['usecases'] = useCaseMap[token['id']]; } - final theseTokens =List.from(tokens.map((token) => - TokenModel.fromJson(token))); + final StatRepository _statRepository = StatRepository(); + List theseTokens = []; + /// verify token contract on chain and get contract precision + loadData(token) async { + TokenModel? tm = TokenModel.fromJson(token as Map); + if (tm != null) { + await _statRepository + .getTokenStat(tokenContract: tm.contract, symbol: tm.symbol) + .then( + (stats) async { + if (stats.asValue != null) { + final supply = stats.asValue!.value.supplyString; + tm.setPrecisionFromString(supply); + theseTokens.add(tm); + print("supply: $supply"); + } + }, + ).catchError((dynamic error) => _statRepository.mapHttpError(error)); + } + } + final queue = Queue(parallel: 5); + for (final dynamic token in tokens) { + queue.add(() async { + await loadData(token); + }); + } + await queue.whenComplete(); rv.addAll(theseTokens.whereNotNull()); /// build a TokenModel from each selected token's metadata } diff --git a/lib/main.dart b/lib/main.dart index 4a421e449..3ed105e3c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,7 +26,7 @@ Future main() async { await settingsStorage.initialise(); await PushNotificationService().initialise(); await remoteConfigurations.initialise(); - await TokenModel.installModels(['lightwallet','experimental'], [TokenModel.seedsEcosysUsecase]); + await TokenModel.installModels(['localscale','lightwallet','experimental'], [TokenModel.seedsEcosysUsecase]); await Hive.initFlutter(); Hive.registerAdapter(MemberModelCacheItemAdapter()); Hive.registerAdapter(VoteModelAdapter()); diff --git a/lib/screens/authentication/import_key/interactor/usecases/import_key_use_case.dart b/lib/screens/authentication/import_key/interactor/usecases/import_key_use_case.dart index e54625a70..0dbf91ff4 100644 --- a/lib/screens/authentication/import_key/interactor/usecases/import_key_use_case.dart +++ b/lib/screens/authentication/import_key/interactor/usecases/import_key_use_case.dart @@ -12,7 +12,7 @@ class ImportKeyUseCase { final List items = [accountsResponse]; return items; } else { - final List accounts = accountsResponse.asValue!.value; + final List accounts = accountsResponse.asValue!.value.cast(); final List> futures = accounts.map((String account) => _profileRepository.getProfile(account)).toList(); diff --git a/lib/screens/explore_screens/regions_screens/join_region/interactor/viewmodels/join_region_bloc.dart b/lib/screens/explore_screens/regions_screens/join_region/interactor/viewmodels/join_region_bloc.dart index 72008030b..318ac0d15 100644 --- a/lib/screens/explore_screens/regions_screens/join_region/interactor/viewmodels/join_region_bloc.dart +++ b/lib/screens/explore_screens/regions_screens/join_region/interactor/viewmodels/join_region_bloc.dart @@ -40,7 +40,7 @@ class JoinRegionBloc extends Bloc { Future _onCreateRegionTapped(OnCreateRegionTapped event, Emitter emit) async { emit(state.copyWith(isCreateRegionButtonLoading: true)); - final result = await GetAvailableBalanceUseCase().run(TokenModel.fromId(seedsToken.id)); + final result = await GetAvailableBalanceUseCase().run(seedsToken); emit(CreateRegionBalanceResultStateMapper().mapResultToState(state, result)); } diff --git a/lib/screens/explore_screens/unplant_seeds/interactor/viewmodels/unplant_seeds_state.dart b/lib/screens/explore_screens/unplant_seeds/interactor/viewmodels/unplant_seeds_state.dart index eaf812e75..c2bcd840c 100644 --- a/lib/screens/explore_screens/unplant_seeds/interactor/viewmodels/unplant_seeds_state.dart +++ b/lib/screens/explore_screens/unplant_seeds/interactor/viewmodels/unplant_seeds_state.dart @@ -96,7 +96,7 @@ class UnplantSeedsState extends Equatable { ); } - factory UnplantSeedsState.initial(RatesState ratesState, bool featureFlagDelegateEnabled) { + factory UnplantSeedsState.initial(RatesState ratesState, bool claimUnplantedSeedsEnabled) { return UnplantSeedsState( pageState: PageState.success, ratesState: ratesState, @@ -106,7 +106,7 @@ class UnplantSeedsState extends Equatable { showMinPlantedBalanceAlert: false, isUnplantSeedsButtonEnabled: false, unplantedInputAmount: TokenDataModel(0), - showUnclaimedBalance: featureFlagDelegateEnabled, + showUnclaimedBalance: claimUnplantedSeedsEnabled, isClaimButtonEnabled: false, ); } diff --git a/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/mappers/set_private_key_state_mapper.dart b/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/mappers/set_private_key_state_mapper.dart index 6786a6360..bd06d1d20 100644 --- a/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/mappers/set_private_key_state_mapper.dart +++ b/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/mappers/set_private_key_state_mapper.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:seeds/datasource/local/models/auth_data_model.dart'; import 'package:seeds/domain-shared/page_state.dart'; import 'package:seeds/domain-shared/result_to_state_mapper.dart'; @@ -9,12 +10,19 @@ class SetFoundPrivateKeyStateMapper extends StateMapper { if (result.isError) { return currentState.copyWith(pageState: PageState.failure, error: ImportKeyError.noPublicKeyFound); } else { - ///-------GET PRIVATE KEY - final String publicKey = result.asValue!.value; - // Find the keys pair match the public key - final Keys keys = currentState.keys.singleWhere((i) => i.publicKey == publicKey); - // Set the private key of the match pair - return currentState.copyWith(authDataModel: AuthDataModel.fromKeyAndNoWords(keys.privateKey)); + ///-------GET PRIVATE KEY MATCHING ONE OF THE PUBLIC KEYS + for(final String publicKey in result.asValue!.value) { + // Find the keys pair match the public key + final Keys? keypair = currentState.keys.singleWhereOrNull((i) => + i.publicKey == publicKey); + if(keypair != null) { + // Set the private key of the match pair + return currentState.copyWith( + authDataModel: AuthDataModel.fromKeyAndNoWords( + keypair.privateKey)); + } + } + return currentState.copyWith(pageState: PageState.failure, error: ImportKeyError.noPublicKeyFound); } } } diff --git a/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/usecases/get_public_key_from_account_use_case copy.dart b/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/usecases/get_public_key_from_account_use_case copy.dart index a1949f141..0359a4b6f 100644 --- a/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/usecases/get_public_key_from_account_use_case copy.dart +++ b/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/usecases/get_public_key_from_account_use_case copy.dart @@ -1,6 +1,6 @@ import 'package:async/async.dart'; import 'package:seeds/datasource/remote/api/profile_repository.dart'; -class GetPublicKeyFromAccountUseCase { - Future run(String account) => ProfileRepository().getAccountPublicKey(account); +class GetPublicKeysFromAccountUseCase { + Future run(String account) => ProfileRepository().getAccountPublicKeys(account); } diff --git a/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/usecases/import_accounts_use_case.dart b/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/usecases/import_accounts_use_case.dart index 37a7cc23c..caed4b180 100644 --- a/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/usecases/import_accounts_use_case.dart +++ b/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/usecases/import_accounts_use_case.dart @@ -17,7 +17,7 @@ class ImportAccountsUseCase { return keyAccountsResponse; } else { final List> keyAccountsValues = - keyAccountsResponse.map>((i) => i.asValue!.value).toList(); + keyAccountsResponse.map>((i) => (i.asValue!.value as List).cast()).toList(); final List keyAccounts = keyAccountsValues.expand((i) => i).toList(); final List savedAccounts = settingsStorage.accountsList; // Remove duplicated accounts diff --git a/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/viewmodels/switch_account_bloc.dart b/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/viewmodels/switch_account_bloc.dart index 89cca2d38..910fe7890 100644 --- a/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/viewmodels/switch_account_bloc.dart +++ b/lib/screens/profile_screens/profile/components/switch_account_bottom_sheet/interactor/viewmodels/switch_account_bloc.dart @@ -41,7 +41,7 @@ class SwitchAccountBloc extends Bloc { Future _onAccountSelected(OnAccountSelected event, Emitter emit) async { emit(state.copyWith(currentAcccout: event.profile)); - final result = await GetPublicKeyFromAccountUseCase().run(event.profile.account); + final result = await GetPublicKeysFromAccountUseCase().run(event.profile.account); emit(SetFoundPrivateKeyStateMapper().mapResultToState(state, result)); // Only refresh the current accountName and the privateKey, then fire auth. _authenticationBloc.add(OnSwitchAccount(event.profile.account, state.authDataModel!)); diff --git a/lib/screens/transfer/receive/receive_enter_data/interactor/usecases/receive_seeds_invoice_use_case.dart b/lib/screens/transfer/receive/receive_enter_data/interactor/usecases/receive_seeds_invoice_use_case.dart index f05f9179c..194c2d140 100644 --- a/lib/screens/transfer/receive/receive_enter_data/interactor/usecases/receive_seeds_invoice_use_case.dart +++ b/lib/screens/transfer/receive/receive_enter_data/interactor/usecases/receive_seeds_invoice_use_case.dart @@ -19,7 +19,7 @@ class ReceiveSeedsInvoiceUseCase extends InputUseCase invoice = await _invoiceRepository.createInvoice( tokenAmount: input.tokenAmount, accountName: settingsStorage.accountName, - tokenContract: TokenModel.fromId(input.tokenAmount.id!).contract, + tokenContract: input.tokenAmount.token.contract, memo: input.memo, ); diff --git a/lib/screens/transfer/send/send_confirmation/interactor/mappers/initial_validation_state_mapper.dart b/lib/screens/transfer/send/send_confirmation/interactor/mappers/initial_validation_state_mapper.dart index 2d870da41..addc449dc 100644 --- a/lib/screens/transfer/send/send_confirmation/interactor/mappers/initial_validation_state_mapper.dart +++ b/lib/screens/transfer/send/send_confirmation/interactor/mappers/initial_validation_state_mapper.dart @@ -1,4 +1,5 @@ import 'package:seeds/datasource/remote/model/balance_model.dart'; +import 'package:seeds/datasource/remote/model/token_model.dart'; import 'package:seeds/domain-shared/page_state.dart'; import 'package:seeds/domain-shared/result_to_state_mapper.dart'; import 'package:seeds/screens/transfer/send/send_confirmation/interactor/viewmodels/send_confirmation_bloc.dart'; @@ -11,8 +12,15 @@ class InitialValidationStateMapper extends StateMapper { } else { final BalanceModel balance = result.asValue!.value; final eosAction = currentState.transaction.actions.first; - final amountRequested = (eosAction.data?['quantity'] as String).split(' ').first; - final hasEnoughBalance = (balance.quantity - double.parse(amountRequested)) >= 0; + final contract = eosAction.account; + final quantitySplit = (eosAction.data?['quantity'] as String).split(' '); + final amountRequested = quantitySplit.first; + final symbol = quantitySplit[1]; + final token = TokenModel.fromId("$contract#$symbol"); + final double insufficiency = double.parse(amountRequested) - balance.quantity; + final hasEnoughBalance = (token != null) ? + !token.blockTransfer(insufficiency, eosAction.data?['to']) : + insufficiency <= 0; if (hasEnoughBalance) { return currentState.copyWith(pageState: PageState.success); } else { diff --git a/lib/screens/transfer/send/send_confirmation/interactor/viewmodels/send_confirmation_bloc.dart b/lib/screens/transfer/send/send_confirmation/interactor/viewmodels/send_confirmation_bloc.dart index 08e1494a4..65d6c1adc 100644 --- a/lib/screens/transfer/send/send_confirmation/interactor/viewmodels/send_confirmation_bloc.dart +++ b/lib/screens/transfer/send/send_confirmation/interactor/viewmodels/send_confirmation_bloc.dart @@ -31,13 +31,25 @@ class SendConfirmationBloc extends Bloc i.symbol == symbol); - if (targetToken != null) { - final Result result = await GetAvailableBalanceUseCase().run(targetToken); - emit(InitialValidationStateMapper().mapResultToState(state, result)); - } + final eosAction = state.transaction.actions.first; + final symbol = (eosAction.data?['quantity'] as String).split(' ').last; + final contract = eosAction.account; + var targetToken = TokenModel.allTokens. + singleWhereOrNull((i) => i.symbol == symbol && i.contract == contract); + targetToken ??= TokenModel( + chainName: "Telos", + contract: eosAction.account!, + symbol: symbol, + name: eosAction.name!, + backgroundImageUrl: '', + logoUrl: '', + balanceSubTitle: 'Wallet Balance', + overdraw: '', + precision: 4, + usecases: [], + ); + final Result result = await GetAvailableBalanceUseCase().run(targetToken); + emit(InitialValidationStateMapper().mapResultToState(state, result)); } else { emit(state.copyWith(pageState: PageState.success)); } diff --git a/lib/screens/transfer/send/send_enter_data/interactor/mappers/send_amount_change_mapper.dart b/lib/screens/transfer/send/send_enter_data/interactor/mappers/send_amount_change_mapper.dart index 642fa5886..6b17bf002 100644 --- a/lib/screens/transfer/send/send_enter_data/interactor/mappers/send_amount_change_mapper.dart +++ b/lib/screens/transfer/send/send_enter_data/interactor/mappers/send_amount_change_mapper.dart @@ -1,6 +1,7 @@ import 'package:seeds/blocs/rates/viewmodels/rates_bloc.dart'; import 'package:seeds/datasource/local/models/token_data_model.dart'; import 'package:seeds/datasource/local/settings_storage.dart'; +import 'package:seeds/datasource/remote/model/token_model.dart'; import 'package:seeds/domain-shared/result_to_state_mapper.dart'; import 'package:seeds/screens/transfer/send/send_enter_data/interactor/viewmodels/send_enter_data_bloc.dart'; import 'package:seeds/utils/rate_states_extensions.dart'; @@ -12,17 +13,19 @@ class SendAmountChangeMapper extends StateMapper { final tokenAmount = TokenDataModel(parsedQuantity, token: settingsStorage.selectedToken); final selectedFiat = settingsStorage.selectedFiatCurrency; final fiatAmount = rateState.tokenToFiat(tokenAmount, selectedFiat); + final toAccount = currentState.sendTo.account; final double currentAvailable = currentState.availableBalance?.amount ?? 0; - final bool enoughBalance = parsedQuantity <= currentAvailable || - currentState.availableBalance?.asFormattedString() == tokenAmount.asFormattedString(); + final double insufficiency = + currentState.availableBalance?.asFormattedString() == tokenAmount.asFormattedString() ? 0.0 : + parsedQuantity - currentAvailable; return currentState.copyWith( fiatAmount: fiatAmount, - isNextButtonEnabled: parsedQuantity > 0 && enoughBalance, + isNextButtonEnabled: parsedQuantity > 0 && !settingsStorage.selectedToken.blockTransfer(insufficiency, toAccount), tokenAmount: tokenAmount, - showAlert: !enoughBalance, + showAlert: settingsStorage.selectedToken.warnTransfer(insufficiency, toAccount) != null, ); } } diff --git a/lib/screens/transfer/send/send_enter_data/interactor/viewmodels/send_enter_data_bloc.dart b/lib/screens/transfer/send/send_enter_data/interactor/viewmodels/send_enter_data_bloc.dart index 5a8c054f2..2f7995a0a 100644 --- a/lib/screens/transfer/send/send_enter_data/interactor/viewmodels/send_enter_data_bloc.dart +++ b/lib/screens/transfer/send/send_enter_data/interactor/viewmodels/send_enter_data_bloc.dart @@ -8,6 +8,7 @@ import 'package:seeds/datasource/local/models/token_data_model.dart'; import 'package:seeds/datasource/local/settings_storage.dart'; import 'package:seeds/datasource/remote/model/balance_model.dart'; import 'package:seeds/datasource/remote/model/profile_model.dart'; +import 'package:seeds/datasource/remote/model/token_model.dart'; import 'package:seeds/domain-shared/app_constants.dart'; import 'package:seeds/domain-shared/base_use_case.dart'; import 'package:seeds/domain-shared/page_command.dart'; @@ -67,7 +68,7 @@ class SendEnterDataBloc extends Bloc { data: { 'from': settingsStorage.accountName, 'to': state.sendTo.account, - 'quantity': state.tokenAmount.asFormattedString(), + 'quantity': TokenModel.getAssetString(state.tokenAmount.id, state.tokenAmount.amount), 'memo': state.memo, }, ), diff --git a/lib/screens/transfer/send/send_enter_data/send_enter_data_screen.dart b/lib/screens/transfer/send/send_enter_data/send_enter_data_screen.dart index f6d23ace0..2804fbaaa 100644 --- a/lib/screens/transfer/send/send_enter_data/send_enter_data_screen.dart +++ b/lib/screens/transfer/send/send_enter_data/send_enter_data_screen.dart @@ -87,7 +87,7 @@ class SendEnterDataScreen extends StatelessWidget { ? const SendLoadingIndicator() : const SafeArea(child: FullPageLoadingIndicator()); case PageState.failure: - return const SafeArea(child: FullPageErrorIndicator()); + return SafeArea(child: FullPageErrorIndicator(errorMessage: state.errorMessage)); case PageState.success: return SafeArea( minimum: const EdgeInsets.all(horizontalEdgePadding), diff --git a/lib/screens/wallet/components/tokens_cards/components/currency_info_card.dart b/lib/screens/wallet/components/tokens_cards/components/currency_info_card.dart index 700da212c..059122870 100644 --- a/lib/screens/wallet/components/tokens_cards/components/currency_info_card.dart +++ b/lib/screens/wallet/components/tokens_cards/components/currency_info_card.dart @@ -5,7 +5,8 @@ import 'package:seeds/utils/build_context_extension.dart'; class CurrencyInfoCard extends StatelessWidget { // TODO(chuck): provide default image - static const defaultBgImage = 'assets/images/wallet/currency_info_cards/tlos/background.jpg'; + static const defaultBgImage = 'assets/images/wallet/currency_info_cards/tlos/background.png'; + static const defaultBalanceSubtitle = 'Balance'; final TokenBalanceViewModel tokenBalance; final String fiatBalance; diff --git a/lib/screens/wallet/components/tokens_cards/interactor/mappers/token_balances_state_mapper.dart b/lib/screens/wallet/components/tokens_cards/interactor/mappers/token_balances_state_mapper.dart index b90d1fc88..60131f52b 100644 --- a/lib/screens/wallet/components/tokens_cards/interactor/mappers/token_balances_state_mapper.dart +++ b/lib/screens/wallet/components/tokens_cards/interactor/mappers/token_balances_state_mapper.dart @@ -37,7 +37,7 @@ class TokenBalancesStateMapper { } } else { final BalanceModel balance = result.asValue!.value; - if (whitelisted || balance.quantity > 0) { + if (whitelisted || balance.quantity != 0) { available.add(TokenBalanceViewModel(token, TokenDataModel(balance.quantity, token: token))); newWhitelist.add(token.id); } diff --git a/lib/utils/rate_states_extensions.dart b/lib/utils/rate_states_extensions.dart index b407277e8..b6a276b05 100644 --- a/lib/utils/rate_states_extensions.dart +++ b/lib/utils/rate_states_extensions.dart @@ -27,7 +27,7 @@ extension RatesStateExtensions on RatesState { if (rateModel != null) { final double? tokenValue = rateModel.usdToToken(usdValue); if (tokenValue != null) { - return TokenDataModel(tokenValue, token: TokenModel.fromId(tokenId)); + return TokenDataModel(tokenValue, token: TokenModel.fromId(tokenId)!); } } } diff --git a/pubspec.yaml b/pubspec.yaml index 1b7ae9449..c6c9efb49 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,6 +65,7 @@ dependencies: firebase_app_installations: ^0.2.3+4 package_info_plus: ^4.1.0 flutter_dotenv: ^5.1.0 + dynamic_parallel_queue: ^1.0.3+2 dev_dependencies: flutter_test: