From a8b703f8f76a04da9f408f06d81abb6fce53ec00 Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Tue, 30 Apr 2024 16:03:59 -0700 Subject: [PATCH 01/24] clean up typo etc --- android/app/build.gradle | 2 +- lib/datasource/remote/api/eos_repo/eos_repository.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/datasource/remote/api/eos_repo/eos_repository.dart b/lib/datasource/remote/api/eos_repo/eos_repository.dart index 13ed76617..18a296450 100644 --- a/lib/datasource/remote/api/eos_repo/eos_repository.dart +++ b/lib/datasource/remote/api/eos_repo/eos_repository.dart @@ -10,7 +10,7 @@ abstract class EosRepository { late final String cpuPrivateKey = dotenv.env['PAYCPU_SEEDS_KEY'] ?? ''; late final String onboardingPrivateKey = dotenv.env['ONBOARDING_SEEDS_KEY'] ?? ''; -; + String onboardingAccountName = 'join.seeds'; int guardianRecoveryTimeDelaySec = const Duration(hours: 24).inSeconds; From 64890dbfac7f22d76a00db56b00211b3e2002385 Mon Sep 17 00:00:00 2001 From: chuck Date: Thu, 25 Aug 2022 00:14:07 -0700 Subject: [PATCH 02/24] Allow transfer of unfamiliar tokens (1922) --- .../viewmodels/send_confirmation_bloc.dart | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) 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..5f8fb0514 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,24 @@ 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: symbol, + backgroundImageUrl: '', + logoUrl: '', + balanceSubTitle: 'Wallet Balance', + precision: 4, + usecases: [], + ); + final Result result = await GetAvailableBalanceUseCase().run(targetToken); + emit(InitialValidationStateMapper().mapResultToState(state, result)); } else { emit(state.copyWith(pageState: PageState.success)); } From 7ad2c813fe62b7a2bf61afe0bf3b28dc2af68507 Mon Sep 17 00:00:00 2001 From: chuck Date: Wed, 31 Aug 2022 16:01:34 -0700 Subject: [PATCH 03/24] TokenModel: get acutal contract precision by reading 'stat' table --- .../remote/api/balance_repository.dart | 3 +- .../remote/api/stat_repository.dart | 31 +++++++++++++++++++ lib/datasource/remote/model/stat_model.dart | 21 +++++++++++++ lib/datasource/remote/model/token_model.dart | 20 ++++++++++-- .../get_token_models_use_case.dart | 21 +++++++++++-- .../viewmodels/send_enter_data_bloc.dart | 3 +- 6 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 lib/datasource/remote/api/stat_repository.dart create mode 100644 lib/datasource/remote/model/stat_model.dart diff --git a/lib/datasource/remote/api/balance_repository.dart b/lib/datasource/remote/api/balance_repository.dart index 9b0d0c534..64dddd5d3 100644 --- a/lib/datasource/remote/api/balance_repository.dart +++ b/lib/datasource/remote/api/balance_repository.dart @@ -2,11 +2,12 @@ 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'; 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/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/model/stat_model.dart b/lib/datasource/remote/model/stat_model.dart new file mode 100644 index 000000000..a27de0818 --- /dev/null +++ b/lib/datasource/remote/model/stat_model.dart @@ -0,0 +1,21 @@ +import 'package:seeds/datasource/remote/model/token_model.dart'; + +/// 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..60dd7f246 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; @@ -113,8 +114,22 @@ 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 && 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; } static Future updateModels(List acceptList, [List? infoList]) async { @@ -142,6 +157,7 @@ class TokenModel extends Equatable { } } allTokens = _staticTokenList; + contractPrecisions = Map.fromEntries(allTokens.map((t) => MapEntry(t.id , t.precision))); } static void pruneRemoving(List useCaseList) { 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..267eff7e2 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,5 +1,6 @@ import 'package:collection/collection.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'; @@ -78,8 +79,24 @@ 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 + await Future.forEach(tokens, (token) async { + final tm = TokenModel.fromJson(token as Map); + if (tm != null) { + await _statRepository.getTokenStat( + tokenContract: tm.contract, symbol: tm.symbol).then((stats) { + 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)); + } + }); rv.addAll(theseTokens.whereNotNull()); /// build a TokenModel from each selected token's metadata } 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, }, ), From ef3efbc56d0a4582d5d9aa3c0d4394c70fd8077b Mon Sep 17 00:00:00 2001 From: chuck Date: Fri, 2 Sep 2022 17:09:14 -0700 Subject: [PATCH 04/24] use contract precision from 'stat' table for LW 'receive' function --- lib/datasource/remote/api/invoice_repository.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 ?? '' }; From 5fa701b8534c64ec98bd6ebc81bdc015117e0c12 Mon Sep 17 00:00:00 2001 From: chuck Date: Sat, 3 Sep 2022 23:04:40 -0700 Subject: [PATCH 05/24] add 'overdraw' field to TokenModel, supporting Mutual Credit transfers --- lib/datasource/remote/model/token_model.dart | 23 +++++++++++++++++++ .../viewmodels/send_confirmation_bloc.dart | 1 + .../mappers/send_amount_change_mapper.dart | 10 ++++---- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/datasource/remote/model/token_model.dart b/lib/datasource/remote/model/token_model.dart index 60dd7f246..7d77ec99d 100644 --- a/lib/datasource/remote/model/token_model.dart +++ b/lib/datasource/remote/model/token_model.dart @@ -22,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; @@ -48,6 +49,7 @@ class TokenModel extends Equatable { required this.backgroundImageUrl, required this.logoUrl, required this.balanceSubTitle, + required this.overdraw, this.precision = 4, this.usecases, }); @@ -98,6 +100,7 @@ class TokenModel extends Equatable { logoUrl: parsedJson["logo"]!, balanceSubTitle: parsedJson["subtitle"], backgroundImageUrl: parsedJson["bg_image"] ?? CurrencyInfoCard.defaultBgImage, + overdraw: parsedJson["overdraw"] ?? "allow", precision: parsedJson["precision"] ?? 4, usecases: parsedJson["usecases"], ); @@ -132,6 +135,20 @@ class TokenModel extends Equatable { contractPrecisions[this.id] = ss.length==1 ? 0 : ss[1].length; } + // enabling 'send' overdrafts for Mutual Credit + bool blockAmount(double insufficiency) { + if (overdraw == "block") { + return insufficiency > 0; + } else if (overdraw == "allow") { + return false; + } + print("unexpected overdraw field: $overdraw"); + return false; + } + bool warnAmount(double insufficiency) { + return insufficiency > 0; + } + static Future updateModels(List acceptList, [List? infoList]) async { final selector = TokenModelSelector(acceptList: acceptList, infoList: infoList); final tokenListResult = await GetTokenModelsUseCase().run(selector); @@ -179,6 +196,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], ); @@ -191,6 +209,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], ); @@ -203,6 +222,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], ); @@ -215,6 +235,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"], ); @@ -226,6 +247,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"], ); @@ -237,5 +259,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/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 5f8fb0514..febe77b99 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 @@ -44,6 +44,7 @@ class SendConfirmationBloc extends Bloc 0 && enoughBalance, + isNextButtonEnabled: parsedQuantity > 0 && !settingsStorage.selectedToken.blockAmount(insufficiency), tokenAmount: tokenAmount, - showAlert: !enoughBalance, + showAlert: settingsStorage.selectedToken.warnAmount(insufficiency), ); } } From 8279b6b0e237766113c70024c1ff5c9f57464ead Mon Sep 17 00:00:00 2001 From: chuck Date: Sun, 4 Sep 2022 11:31:19 -0700 Subject: [PATCH 06/24] broaden defn of send xfr validity check; keep neg-balance cards in carousel --- lib/datasource/remote/api/balance_repository.dart | 1 - lib/datasource/remote/model/stat_model.dart | 1 - lib/datasource/remote/model/token_model.dart | 9 +++++---- .../interactor/mappers/send_amount_change_mapper.dart | 5 +++-- .../interactor/mappers/token_balances_state_mapper.dart | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/datasource/remote/api/balance_repository.dart b/lib/datasource/remote/api/balance_repository.dart index 64dddd5d3..13692ac48 100644 --- a/lib/datasource/remote/api/balance_repository.dart +++ b/lib/datasource/remote/api/balance_repository.dart @@ -2,7 +2,6 @@ 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'; class BalanceRepository extends HttpRepository { Future> getTokenBalance(String userAccount, diff --git a/lib/datasource/remote/model/stat_model.dart b/lib/datasource/remote/model/stat_model.dart index a27de0818..70cf8fbdb 100644 --- a/lib/datasource/remote/model/stat_model.dart +++ b/lib/datasource/remote/model/stat_model.dart @@ -1,4 +1,3 @@ -import 'package:seeds/datasource/remote/model/token_model.dart'; /// Token per USD class StatModel { diff --git a/lib/datasource/remote/model/token_model.dart b/lib/datasource/remote/model/token_model.dart index 7d77ec99d..4bbf40ba5 100644 --- a/lib/datasource/remote/model/token_model.dart +++ b/lib/datasource/remote/model/token_model.dart @@ -135,8 +135,9 @@ class TokenModel extends Equatable { contractPrecisions[this.id] = ss.length==1 ? 0 : ss[1].length; } - // enabling 'send' overdrafts for Mutual Credit - bool blockAmount(double insufficiency) { + // 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") { @@ -145,8 +146,8 @@ class TokenModel extends Equatable { print("unexpected overdraw field: $overdraw"); return false; } - bool warnAmount(double insufficiency) { - return insufficiency > 0; + String? warnTransfer(double insufficiency, String toAccount) { + return insufficiency > 0 ? "insufficient balance" : null; } static Future updateModels(List acceptList, [List? infoList]) async { 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 123982186..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 @@ -13,6 +13,7 @@ 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; @@ -22,9 +23,9 @@ class SendAmountChangeMapper extends StateMapper { return currentState.copyWith( fiatAmount: fiatAmount, - isNextButtonEnabled: parsedQuantity > 0 && !settingsStorage.selectedToken.blockAmount(insufficiency), + isNextButtonEnabled: parsedQuantity > 0 && !settingsStorage.selectedToken.blockTransfer(insufficiency, toAccount), tokenAmount: tokenAmount, - showAlert: settingsStorage.selectedToken.warnAmount(insufficiency), + showAlert: settingsStorage.selectedToken.warnTransfer(insufficiency, toAccount) != null, ); } } 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); } From b779b1175d26f9f529bfdc46ebaa38c1798f1b0a Mon Sep 17 00:00:00 2001 From: chuck Date: Sun, 4 Sep 2022 14:25:26 -0700 Subject: [PATCH 07/24] show blockchain errors to user in 'send' flow --- lib/datasource/remote/api/eos_repo/eos_repository.dart | 5 +++++ .../send/send_enter_data/send_enter_data_screen.dart | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) 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/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), From a22c862dbb415bf2aff4e2284e2ac0f7c19e0230 Mon Sep 17 00:00:00 2001 From: chuck Date: Sun, 4 Sep 2022 15:31:23 -0700 Subject: [PATCH 08/24] enable mutual credit validity check in QR transaction flow --- lib/datasource/remote/model/token_model.dart | 4 ++-- .../mappers/initial_validation_state_mapper.dart | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/datasource/remote/model/token_model.dart b/lib/datasource/remote/model/token_model.dart index 4bbf40ba5..41ff2975c 100644 --- a/lib/datasource/remote/model/token_model.dart +++ b/lib/datasource/remote/model/token_model.dart @@ -137,7 +137,7 @@ class TokenModel extends Equatable { // enabling 'send' transfer validity checks, e.g. Mutual Credit, // membership limitations - bool blockTransfer(double insufficiency, String toAccount) { + bool blockTransfer(double insufficiency, String? toAccount) { if (overdraw == "block") { return insufficiency > 0; } else if (overdraw == "allow") { @@ -146,7 +146,7 @@ class TokenModel extends Equatable { print("unexpected overdraw field: $overdraw"); return false; } - String? warnTransfer(double insufficiency, String toAccount) { + String? warnTransfer(double insufficiency, String? toAccount) { return insufficiency > 0 ? "insufficient balance" : null; } 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 { From 12cf9a62c378665b3124a3645a0c10211aba1082 Mon Sep 17 00:00:00 2001 From: chuck Date: Tue, 6 Sep 2022 13:02:01 -0700 Subject: [PATCH 09/24] fix bug in unfamiliar token send path --- .../interactor/viewmodels/send_confirmation_bloc.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 febe77b99..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 @@ -40,7 +40,7 @@ class SendConfirmationBloc extends Bloc Date: Thu, 18 Apr 2024 13:32:41 -0700 Subject: [PATCH 10/24] rename claimUnplantedSeedsEnabled --- .../interactor/viewmodels/unplant_seeds_state.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, ); } From aeab1935a397ec5904734e2ad8503a1d029ff003 Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Sun, 21 Apr 2024 10:37:07 -0700 Subject: [PATCH 11/24] authorize 'localscale' usecase for token display --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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()); From 54b364ca7bfbebe61697b6c984dfce8fe564229a Mon Sep 17 00:00:00 2001 From: chuck Date: Tue, 6 Sep 2022 13:35:10 -0700 Subject: [PATCH 12/24] allow TokenModel.fromId to be nullable --- lib/datasource/local/models/token_data_model.dart | 5 +++-- lib/datasource/local/settings_storage.dart | 2 +- lib/datasource/remote/model/token_model.dart | 8 ++++---- .../interactor/viewmodels/join_region_bloc.dart | 2 +- .../usecases/receive_seeds_invoice_use_case.dart | 2 +- lib/utils/rate_states_extensions.dart | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) 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/model/token_model.dart b/lib/datasource/remote/model/token_model.dart index 41ff2975c..ee0c8afba 100644 --- a/lib/datasource/remote/model/token_model.dart +++ b/lib/datasource/remote/model/token_model.dart @@ -106,8 +106,8 @@ class TokenModel extends Equatable { ); } - 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) { @@ -118,8 +118,8 @@ class TokenModel extends Equatable { List get props => [chainName, contract, symbol]; static String getAssetString(String? id, double quantity) { - if (id!=null && contractPrecisions.containsKey(id)) { - final symbol = TokenModel.fromId(id).symbol; + 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 ""; 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/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/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)!); } } } From 462d43e8f275f991cb8f0f047e8d74ef7fa97e9d Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Tue, 30 Apr 2024 18:49:27 -0700 Subject: [PATCH 13/24] In transaction list, silently skip non-token transfers (e.g. NFT) --- .../remote/api/transactions_repository.dart | 3 ++- .../remote/model/transaction_model.dart | 23 +++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) 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/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) { From 2f3286dcd21504b6883b9ee175c70f5c046124e6 Mon Sep 17 00:00:00 2001 From: NIK Date: Thu, 7 Sep 2023 21:13:08 +0800 Subject: [PATCH 14/24] cherry-pick new import api from master pull/1943 --- lib/crypto/eosdart/src/client.dart | 32 +++++++++---------- .../remote/api/key_accounts_repository.dart | 12 ++++--- .../usecases/import_key_use_case.dart | 2 +- .../usecases/import_accounts_use_case.dart | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/crypto/eosdart/src/client.dart b/lib/crypto/eosdart/src/client.dart index 0ac06f93a..89e4c65ae 100644 --- a/lib/crypto/eosdart/src/client.dart +++ b/lib/crypto/eosdart/src/client.dart @@ -222,10 +222,21 @@ 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); - }); + + // Get Key Accounts using new API: new get_accounts_by_authorizers API + Future getAccountsByKey(String pubKey) async { + try { + return _post('/chain/get_accounts_by_authorizers', { + 'accounts': [], + 'keys': [pubKey] + }).then((response) { + final List accountNames = List.from(response['accounts'].map((e) => e['account_name'])); + return AccountNames()..accountNames = accountNames.toSet().toList(); + }); + } catch (e) { + print("getAccountsByKey error $e"); + return AccountNames(); + } } // Get Key Accounts using new API: new get_accounts_by_authorizers API @@ -314,19 +325,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/datasource/remote/api/key_accounts_repository.dart b/lib/datasource/remote/api/key_accounts_repository.dart index 75338f362..85c61df88 100644 --- a/lib/datasource/remote/api/key_accounts_repository.dart +++ b/lib/datasource/remote/api/key_accounts_repository.dart @@ -3,18 +3,20 @@ 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'); - final url = Uri.parse('$baseURL/v1/history/get_key_accounts'); - final body = '{ "public_key": "$publicKey" }'; + Future> getAccountsByKey(String publicKey) { + print('[http] getAccountsByKey'); + + final url = Uri.parse('$baseURL/v1/chain/get_accounts_by_authorizers'); + final body = '{ "accounts": [], "keys": ["$publicKey"] }'; return http .post(url, headers: headers, body: body) .then((http.Response response) => mapHttpResponse(response, (dynamic body) { print('result: $body'); - final result = List.from(body['account_names']); + final result = + List.from(body['accounts']).map((e) => e['account_name']).toList().toSet().toList(); result.sort(); 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..8c0432c20 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 @@ -7,7 +7,7 @@ class ImportKeyUseCase { final ProfileRepository _profileRepository = ProfileRepository(); Future> run(String publicKey) async { - final accountsResponse = await _keyAccountsRepository.getKeyAccounts(publicKey); + final accountsResponse = await _keyAccountsRepository.getAccountsByKey(publicKey); if (accountsResponse.isError) { final List items = [accountsResponse]; return items; 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..68541301c 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 @@ -10,7 +10,7 @@ class ImportAccountsUseCase { Future> run(List publicKeys) async { final List> getKeyAccountsFutures = - publicKeys.map((i) => _keyAccountsRepository.getKeyAccounts(i)).toList(); + publicKeys.map((i) => _keyAccountsRepository.getAccountsByKey(i)).toList(); final List keyAccountsResponse = await Future.wait(getKeyAccountsFutures); if (keyAccountsResponse.singleWhereOrNull((i) => i.isError) != null) { From 54c6e5b5dd68812c69c8f328d8845de9db06402e Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Tue, 30 Apr 2024 19:22:20 -0700 Subject: [PATCH 15/24] fix eosdart deserialization for negative bignums --- lib/crypto/eosdart/src/numeric.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From fd4e7cf5319c73dcebb282ff0f5c8c203c7fe23b Mon Sep 17 00:00:00 2001 From: chuck Date: Tue, 27 Sep 2022 19:22:27 -0700 Subject: [PATCH 16/24] support accounts with several auth keys --- .../remote/api/profile_repository.dart | 6 +++--- .../mappers/set_private_key_state_mapper.dart | 20 +++++++++++++------ ...public_key_from_account_use_case copy.dart | 4 ++-- .../viewmodels/switch_account_bloc.dart | 2 +- 4 files changed, 20 insertions(+), 12 deletions(-) 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/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/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!)); From 952722d5a2595b4ef54900cf6e979ae208111ec4 Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Tue, 30 Apr 2024 20:39:58 -0700 Subject: [PATCH 17/24] fix dynamic cast in import_accounts --- .../interactor/usecases/import_accounts_use_case.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 68541301c..ae19e40ec 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.map((e) => e as String).toList()).toList(); final List keyAccounts = keyAccountsValues.expand((i) => i).toList(); final List savedAccounts = settingsStorage.accountsList; // Remove duplicated accounts From 76c8b6a907a20a8effb6fb173bccb220f2e00315 Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Tue, 30 Apr 2024 20:59:53 -0700 Subject: [PATCH 18/24] fix dynamic cast in import_key --- .../import_key/interactor/usecases/import_key_use_case.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8c0432c20..ad8eded71 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.map((e) => e as String).toList(); final List> futures = accounts.map((String account) => _profileRepository.getProfile(account)).toList(); From 2990784f87f733d58de73429b81fe06d17be3c3f Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Tue, 30 Apr 2024 21:40:32 -0700 Subject: [PATCH 19/24] cleaner dynamic type casting --- .../import_key/interactor/usecases/import_key_use_case.dart | 2 +- .../interactor/usecases/import_accounts_use_case.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 ad8eded71..446b43484 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.map((e) => e as String).toList(); + final List accounts = accountsResponse.asValue!.value.cast(); final List> futures = accounts.map((String account) => _profileRepository.getProfile(account)).toList(); 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 ae19e40ec..1873437f8 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.map((e) => e as String).toList()).toList(); + keyAccountsResponse.map>((i) => i.asValue!.value.cast>()).toList(); final List keyAccounts = keyAccountsValues.expand((i) => i).toList(); final List savedAccounts = settingsStorage.accountsList; // Remove duplicated accounts From 991f3b76b5ba7b27c1931560ce6a7341bed65f2f Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Tue, 30 Apr 2024 22:13:12 -0700 Subject: [PATCH 20/24] yet another dynamic cast --- .../interactor/usecases/import_accounts_use_case.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1873437f8..0bfb4ff00 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.cast>()).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 From c60c7a82bd0ee9b0459f7a50dd37dc560e54e590 Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Sat, 4 May 2024 21:11:02 -0700 Subject: [PATCH 21/24] getAccountsByKey limited to `active` permission --- lib/datasource/remote/api/key_accounts_repository.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/datasource/remote/api/key_accounts_repository.dart b/lib/datasource/remote/api/key_accounts_repository.dart index 85c61df88..6d1a9bbf8 100644 --- a/lib/datasource/remote/api/key_accounts_repository.dart +++ b/lib/datasource/remote/api/key_accounts_repository.dart @@ -14,9 +14,11 @@ class KeyAccountsRepository extends HttpRepository { .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(); From 8360ccf93bc7f2a1c0060eaa47bc2a7b3959f0a3 Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Sun, 9 Jun 2024 11:18:14 -0700 Subject: [PATCH 22/24] default balance subtitle in currency cards --- lib/datasource/remote/model/token_model.dart | 2 +- .../components/tokens_cards/components/currency_info_card.dart | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/datasource/remote/model/token_model.dart b/lib/datasource/remote/model/token_model.dart index ee0c8afba..85dbf0f51 100644 --- a/lib/datasource/remote/model/token_model.dart +++ b/lib/datasource/remote/model/token_model.dart @@ -98,7 +98,7 @@ 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, 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; From 3e99a299380c77880ed4881facf3c9bc5e682c79 Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Sun, 21 Apr 2024 11:06:39 -0700 Subject: [PATCH 23/24] use parallel queuing to make startup loading faster (ported from Localscale) --- .../get_token_models_use_case.dart | 25 +++++++++++++------ pubspec.yaml | 1 + 2 files changed, 19 insertions(+), 7 deletions(-) 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 267eff7e2..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,8 +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; @@ -82,21 +84,30 @@ class GetTokenModelsUseCase extends InputUseCase, TokenModelSel final StatRepository _statRepository = StatRepository(); List theseTokens = []; /// verify token contract on chain and get contract precision - await Future.forEach(tokens, (token) async { - final tm = TokenModel.fromJson(token as Map); + loadData(token) async { + TokenModel? tm = TokenModel.fromJson(token as Map); if (tm != null) { - await _statRepository.getTokenStat( - tokenContract: tm.contract, symbol: tm.symbol).then((stats) { + 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)); + }, + ).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/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: From b0c0df6a6f658a13e3dccb04c76dd6cb62977e16 Mon Sep 17 00:00:00 2001 From: "C. Harrison" Date: Tue, 11 Jun 2024 12:54:51 -0700 Subject: [PATCH 24/24] revert e6ef68e new import API /chain/get_accounts_by_authorizers --- lib/crypto/eosdart/src/client.dart | 19 +++---------- .../remote/api/key_accounts_repository.dart | 28 +++++++++---------- .../usecases/import_key_use_case.dart | 2 +- .../usecases/import_accounts_use_case.dart | 2 +- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/lib/crypto/eosdart/src/client.dart b/lib/crypto/eosdart/src/client.dart index 89e4c65ae..ec998b5af 100644 --- a/lib/crypto/eosdart/src/client.dart +++ b/lib/crypto/eosdart/src/client.dart @@ -222,21 +222,10 @@ class EOSClient { } /// Get Key Accounts - - // Get Key Accounts using new API: new get_accounts_by_authorizers API - Future getAccountsByKey(String pubKey) async { - try { - return _post('/chain/get_accounts_by_authorizers', { - 'accounts': [], - 'keys': [pubKey] - }).then((response) { - final List accountNames = List.from(response['accounts'].map((e) => e['account_name'])); - return AccountNames()..accountNames = accountNames.toSet().toList(); - }); - } catch (e) { - print("getAccountsByKey error $e"); - return AccountNames(); - } + Future getKeyAccounts(String pubKey) async { + return _post('/history/get_key_accounts', {'public_key': pubKey}).then((accountNames) { + return AccountNames.fromJson(accountNames as Map); + }); } // Get Key Accounts using new API: new get_accounts_by_authorizers API diff --git a/lib/datasource/remote/api/key_accounts_repository.dart b/lib/datasource/remote/api/key_accounts_repository.dart index 6d1a9bbf8..395085a70 100644 --- a/lib/datasource/remote/api/key_accounts_repository.dart +++ b/lib/datasource/remote/api/key_accounts_repository.dart @@ -4,21 +4,18 @@ import 'package:seeds/datasource/remote/api/http_repo/http_repository.dart'; class KeyAccountsRepository extends HttpRepository { - Future> getAccountsByKey(String publicKey) { - print('[http] getAccountsByKey'); + Future> getKeyAccounts(String publicKey) { + print('[http] getKeyAccounts'); - final url = Uri.parse('$baseURL/v1/chain/get_accounts_by_authorizers'); - final body = '{ "accounts": [], "keys": ["$publicKey"] }'; + final url = Uri.parse('$baseURL/v1/history/get_key_accounts'); + final body = '{ "public_key": "$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'] as List).cast>() - .where((e) => e['permission_name'] as String == 'active') - .map((e) => e['account_name'] as String).toList().toSet().toList(); + + final result = List.from(body['account_names'] as Iterable); result.sort(); @@ -26,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(); @@ -47,4 +47,4 @@ class KeyAccountsRepository extends HttpRepository { // })) // .catchError((dynamic error) => mapHttpError(error)); // } -} + 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 446b43484..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 @@ -7,7 +7,7 @@ class ImportKeyUseCase { final ProfileRepository _profileRepository = ProfileRepository(); Future> run(String publicKey) async { - final accountsResponse = await _keyAccountsRepository.getAccountsByKey(publicKey); + final accountsResponse = await _keyAccountsRepository.getKeyAccounts(publicKey); if (accountsResponse.isError) { final List items = [accountsResponse]; return items; 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 0bfb4ff00..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 @@ -10,7 +10,7 @@ class ImportAccountsUseCase { Future> run(List publicKeys) async { final List> getKeyAccountsFutures = - publicKeys.map((i) => _keyAccountsRepository.getAccountsByKey(i)).toList(); + publicKeys.map((i) => _keyAccountsRepository.getKeyAccounts(i)).toList(); final List keyAccountsResponse = await Future.wait(getKeyAccountsFutures); if (keyAccountsResponse.singleWhereOrNull((i) => i.isError) != null) {