From e0df4723ae6b6b6606a1cecb707df2b83bc83530 Mon Sep 17 00:00:00 2001 From: detherminal <76167420+detherminal@users.noreply.github.com> Date: Wed, 19 Jul 2023 20:08:46 +0300 Subject: [PATCH 01/16] feat: add xlm --- crypto_plugins/flutter_libepiccash | 2 +- crypto_plugins/flutter_liblelantus | 2 +- crypto_plugins/flutter_libmonero | 2 +- .../add_edit_node_view.dart | 5 +- .../manage_nodes_views/node_details_view.dart | 4 +- lib/services/coins/coin_service.dart | 10 + .../coins/stellar/stellar_wallet.dart | 656 ++++++++++++++++++ lib/themes/color_theme.dart | 3 + lib/themes/stack_colors.dart | 2 + lib/utilities/address_utils.dart | 2 + lib/utilities/amount/amount_unit.dart | 1 + lib/utilities/block_explorers.dart | 2 + lib/utilities/constants.dart | 13 + lib/utilities/default_nodes.dart | 15 + lib/utilities/enums/coin_enum.dart | 21 + .../enums/derive_path_type_enum.dart | 1 + lib/widgets/node_card.dart | 4 +- lib/widgets/node_options_sheet.dart | 4 +- macos/Flutter/GeneratedPluginRegistrant.swift | 10 +- pubspec.lock | 260 ++++--- pubspec.yaml | 1 + 21 files changed, 923 insertions(+), 97 deletions(-) create mode 100644 lib/services/coins/stellar/stellar_wallet.dart diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index f677dec0b..cd12741de 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit f677dec0b34d3f9fe8fce2bc8ff5c508c3f3bb9a +Subproject commit cd12741de19e4faef39a23b7d543a2452524990a diff --git a/crypto_plugins/flutter_liblelantus b/crypto_plugins/flutter_liblelantus index 9cd241b5e..ec3cf5e8e 160000 --- a/crypto_plugins/flutter_liblelantus +++ b/crypto_plugins/flutter_liblelantus @@ -1 +1 @@ -Subproject commit 9cd241b5ea142e21c01dd7639b42603281c43287 +Subproject commit ec3cf5e8e1b90e006188aa8c323d4cd52dbfa9b9 diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index e48952185..26a152fea 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit e48952185556a10f182184fd572bcb04365f5831 +Subproject commit 26a152fea3ca4b8c3f1130392a02f579c2ff218c diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 3e5d3e3e4..e7ce8b505 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -196,7 +196,9 @@ class _AddEditNodeViewState extends ConsumerState { case Coin.nano: case Coin.banano: - //TODO: check network/node + case Coin.stellar: + throw UnimplementedError(); + //TODO: check network/node } if (showFlushBar && mounted) { @@ -736,6 +738,7 @@ class _NodeFormState extends ConsumerState { case Coin.nano: case Coin.banano: case Coin.eCash: + case Coin.stellar: return false; case Coin.ethereum: diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index fa69871cb..819b9f5d7 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -172,7 +172,9 @@ class _NodeDetailsViewState extends ConsumerState { case Coin.nano: case Coin.banano: - //TODO: check network/node + case Coin.stellar: + throw UnimplementedError(); + //TODO: check network/node } if (testPassed) { diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 00a52eba5..ffb97a0a7 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -27,6 +27,7 @@ import 'package:stackwallet/services/coins/monero/monero_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/coins/nano/nano_wallet.dart'; import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; +import 'package:stackwallet/services/coins/stellar/stellar_wallet.dart'; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; @@ -218,6 +219,15 @@ abstract class CoinServiceAPI { cachedClient: cachedClient, tracker: tracker); + case Coin.stellar: + return StellarWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + secureStore: secureStorageInterface, + tracker: tracker, + ); + case Coin.wownero: return WowneroWallet( walletId: walletId, diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart new file mode 100644 index 000000000..b7efcce68 --- /dev/null +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -0,0 +1,656 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:decimal/decimal.dart'; +import 'package:http/http.dart' as http; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/balance.dart' as SWBalance; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart' as SWTransaction; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart' as SWAddress; +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart'; +import 'package:stackwallet/services/coins/coin_service.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +import 'package:tuple/tuple.dart'; + +import '../../../db/isar/main_db.dart'; +import '../../../models/node_model.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/default_nodes.dart'; +import '../../../utilities/enums/coin_enum.dart'; +import '../../../utilities/flutter_secure_storage_interface.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/prefs.dart'; +import '../../event_bus/events/global/node_connection_status_changed_event.dart'; +import '../../event_bus/events/global/updated_in_background_event.dart'; +import '../../event_bus/events/global/wallet_sync_status_changed_event.dart'; +import '../../event_bus/global_event_bus.dart'; +import '../../mixins/wallet_cache.dart'; +import '../../mixins/wallet_db.dart'; +import '../../node_service.dart'; +import '../../transaction_notification_tracker.dart'; + +import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart'; + +const int MINIMUM_CONFIRMATIONS = 1; + +class StellarWallet extends CoinServiceAPI + with WalletCache, WalletDB, CoinControlInterface { + + StellarWallet({ + required String walletId, + required String walletName, + required Coin coin, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + } + + late final TransactionNotificationTracker txTracker; + late SecureStorageInterface _secureStore; + + final StellarSDK stellarSdk = StellarSDK.PUBLIC; + + @override + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + bool? _isFavorite; + + @override + set isFavorite(bool isFavorite) { + _isFavorite = isFavorite; + updateCachedIsFavorite(isFavorite); + } + + @override + bool get shouldAutoSync => _shouldAutoSync; + bool _shouldAutoSync = true; + + Timer? timer; + + final _prefs = Prefs.instance; + + @override + set shouldAutoSync(bool shouldAutoSync) { + if (_shouldAutoSync != shouldAutoSync) { + _shouldAutoSync = shouldAutoSync; + if (!shouldAutoSync) { + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } else { + startNetworkAlivePinging(); + refresh(); + } + } + } + + Timer? _networkAliveTimer; + + void startNetworkAlivePinging() { + // call once on start right away + _periodicPingCheck(); + + // then periodically check + _networkAliveTimer = Timer.periodic( + Constants.networkAliveTimerDuration, + (_) async { + _periodicPingCheck(); + }, + ); + } + + void stopNetworkAlivePinging() { + _networkAliveTimer?.cancel(); + _networkAliveTimer = null; + } + + void _periodicPingCheck() async { + bool hasNetwork = await testNetworkConnection(); + + if (_isConnected != hasNetwork) { + NodeConnectionStatus status = hasNetwork + ? NodeConnectionStatus.connected + : NodeConnectionStatus.disconnected; + + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + status, + walletId, + coin, + ), + ); + + _isConnected = hasNetwork; + if (hasNetwork) { + unawaited(refresh()); + } + } + } + + @override + String get walletName => _walletName; + late String _walletName; + + @override + set walletName(String name) => _walletName = name; + + @override + SWBalance.Balance get balance => _balance ??= getCachedBalance(); + SWBalance.Balance? _balance; + + @override + Coin get coin => _coin; + late Coin _coin; + + @override + Future confirmSend({required Map txData}) { + // TODO: implement confirmSend + throw UnimplementedError(); + } + + Future get _currentReceivingAddress => + db.getAddresses(walletId) + .filter() + .typeEqualTo(SWAddress.AddressType.unknown) + .and() + .subTypeEqualTo(SWAddress.AddressSubType.unknown) + .sortByDerivationIndexDesc() + .findFirst(); + + @override + Future get currentReceivingAddress async => + (await _currentReceivingAddress)?.value ?? await getAddressSW(); + + Future getBaseFee() async { + final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); + final httpClient = http.Client(); + FeeStatsResponse fsp = await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); + return int.parse(fsp.lastLedgerBaseFee); + } + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + var baseFee = await getBaseFee(); + int fee = 100; + switch (feeRate) { + case 0: + fee = baseFee * 10; + case 1: + case 2: + fee = baseFee * 50; + case 3: + fee = baseFee * 100; + case 4: + fee = baseFee * 200; + default: + fee = baseFee * 50; + } + return Amount(rawValue: BigInt.from(fee), fractionDigits: coin.decimals); + } + + @override + Future exit() { + // TODO: implement exit + throw UnimplementedError(); + } + + NodeModel? _xlmNode; + + NodeModel getCurrentNode() { + if (_xlmNode != null) { + return _xlmNode!; + } else if (NodeService(secureStorageInterface: _secureStore).getPrimaryNodeFor(coin: coin) != null) { + return NodeService(secureStorageInterface: _secureStore).getPrimaryNodeFor(coin: coin)!; + } else { + return DefaultNodes.getNodeFor(coin); + } + } + + @override + Future get fees async { + final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); + final httpClient = http.Client(); + FeeStatsResponse fsp = await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); + return FeeObject( + numberOfBlocksFast: 0, + numberOfBlocksAverage: 0, + numberOfBlocksSlow: 0, + fast: int.parse(fsp.lastLedgerBaseFee) * 100, + medium: int.parse(fsp.lastLedgerBaseFee) * 50, + slow: int.parse(fsp.lastLedgerBaseFee) * 10 + ); + } + + @override + Future fullRescan(int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) async { + await _prefs.init(); + await updateTransactions(); + await updateChainHeight(); + await updateBalance(); + } + + @override + Future generateNewAddress() { + // TODO: implement generateNewAddress + throw UnimplementedError(); + } + + @override + bool get hasCalledExit => _hasCalledExit; + bool _hasCalledExit = false; + + @override + Future initializeExisting() async { + await _prefs.init(); + } + + @override + Future initializeNew() async { + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + + await _prefs.init(); + + String mnemonic = await Wallet.generate24WordsMnemonic(); + final mnemonicArray = mnemonic.split(" "); + await _secureStore.write( + key: '${_walletId}_mnemonic', + value: mnemonicArray.join(" ") + ); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: "" + ); + + Wallet wallet = await Wallet.from(mnemonic); + KeyPair keyPair = await wallet.getKeyPair(index: 0); + String address = keyPair.accountId; + + final swAddress = SWAddress.Address( + walletId: walletId, + value: address, + publicKey: keyPair.publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown + ); + + await db.putAddress(swAddress); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false) + ]); + } + + Future getAddressSW() async { + var mnemonic = await _secureStore.read( + key: '${_walletId}_mnemonic' + ); + var wallet = await Wallet.from(mnemonic!); + var keyPair = await wallet.getKeyPair(index: 0); + return Future.value(keyPair.accountId); + } + + @override + bool get isConnected => _isConnected; + bool _isConnected = false; + + @override + bool get isRefreshing => refreshMutex; + bool refreshMutex = false; + + @override + // TODO: implement maxFee + Future get maxFee => throw UnimplementedError(); + + @override + Future> get mnemonic => mnemonicString.then((value) => value!.split(" ")); + + @override + Future get mnemonicPassphrase => _secureStore.read(key: '${_walletId}_mnemonicPassphrase'); + + @override + Future get mnemonicString => _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future> prepareSend({required String address, required Amount amount, Map? args}) async { + try { + final feeRate = args?["feeRate"]; + var fee = 1000; + if (feeRate is FeeRateType) { + final theFees = await fees; + switch (feeRate) { + case FeeRateType.fast: + fee = theFees.fast; + case FeeRateType.slow: + fee = theFees.slow; + case FeeRateType.average: + default: + fee = theFees.medium; + } + } + Map txData = { + "fee": fee, + "address": address, + "recipientAmt": amount, + }; + + Logging.instance.log("prepare send: $txData", level: LogLevel.Info); + return txData; + } catch (e, s) { + Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); + rethrow; + } + } + + @override + Future recoverFromMnemonic({required String mnemonic, String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height}) async { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + + var wallet = await Wallet.from(mnemonic); + var keyPair = await wallet.getKeyPair(index: 0); + var address = keyPair.accountId; + + await _secureStore.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + + final swAddress = SWAddress.Address( + walletId: walletId, + value: address, + publicKey: keyPair.publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown + ); + + await db.putAddress(swAddress); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false) + ]); + } + + Future updateChainHeight() async { + final height = await stellarSdk.ledgers + .order(RequestBuilderOrder.DESC) + .limit(1) + .execute() + .then((value) => value.records!.first.sequence); + await updateCachedChainHeight(height); + } + + Future updateTransactions() async { + List> transactionList = []; + Page payments = await stellarSdk.payments.forAccount(await getAddressSW()).order(RequestBuilderOrder.DESC).execute(); + for (OperationResponse response in payments.records!) { + if (response is PaymentOperationResponse) { + var por = response; + SWTransaction.TransactionType type; + if (por.sourceAccount == await getAddressSW()) { + type = SWTransaction.TransactionType.outgoing; + } else { + type = SWTransaction.TransactionType.incoming; + } + final amount = Amount( + rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(7).replaceAll(".", "")), + fractionDigits: 7, + ); + int fee = 0; + int height = 0; + var transaction = por.transaction; + if (transaction != null) { + fee = transaction.feeCharged!; + height = transaction.ledger; + } + var theTransaction = SWTransaction.Transaction( + walletId: walletId, + txid: por.transactionHash!, + timestamp: DateTime.parse(por.createdAt!).millisecondsSinceEpoch ~/ 1000, + type: type, + subType: SWTransaction.TransactionSubType.none, + amount: 0, + amountString: amount.toJsonString(), + fee: fee, + height: height, + isCancelled: false, + isLelantus: false, + slateId: "", + otherData: "", + inputs: [], + outputs: [], + nonce: 0, + numberOfMessages: null, + ); + SWAddress.Address? receivingAddress = await _currentReceivingAddress; + SWAddress.Address address = type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( + walletId: walletId, + value: por.sourceAccount!, + publicKey: KeyPair.fromAccountId(por.sourceAccount!).publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown + ); + Tuple2 tuple = Tuple2(theTransaction, address); + transactionList.add(tuple); + } else if (response is CreateAccountOperationResponse) { + var caor = response; + SWTransaction.TransactionType type; + if (caor.sourceAccount == await getAddressSW()) { + type = SWTransaction.TransactionType.outgoing; + } else { + type = SWTransaction.TransactionType.incoming; + } + final amount = Amount( + rawValue: BigInt.parse(float.parse(caor.startingBalance!).toStringAsFixed(7).replaceAll(".", "")), + fractionDigits: 7, + ); + int fee = 0; + int height = 0; + var transaction = caor.transaction; + if (transaction != null) { + fee = transaction.feeCharged!; + height = transaction.ledger; + } + var theTransaction = SWTransaction.Transaction( + walletId: walletId, + txid: caor.transactionHash!, + timestamp: DateTime.parse(caor.createdAt!).millisecondsSinceEpoch ~/ 1000, + type: type, + subType: SWTransaction.TransactionSubType.none, + amount: 0, + amountString: amount.toJsonString(), + fee: fee, + height: height, + isCancelled: false, + isLelantus: false, + slateId: "", + otherData: "", + inputs: [], + outputs: [], + nonce: 0, + numberOfMessages: null, + ); + SWAddress.Address? receivingAddress = await _currentReceivingAddress; + SWAddress.Address address = type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( + walletId: walletId, + value: caor.sourceAccount!, + publicKey: KeyPair.fromAccountId(caor.sourceAccount!).publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown + ); + Tuple2 tuple = Tuple2(theTransaction, address); + transactionList.add(tuple); + } + } + await db.addNewTransactionData(transactionList, walletId); + } + + Future updateBalance() async { + AccountResponse accountResponse = await stellarSdk.accounts.account(await getAddressSW()); + for (Balance balance in accountResponse.balances) { + switch (balance.assetType) { + case Asset.TYPE_NATIVE: + _balance = SWBalance.Balance( + total: Amount( + rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee + fractionDigits: 7, + ), + spendable: Amount( + rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee + fractionDigits: 7, + ), + blockedTotal: Amount( + rawValue: BigInt.from(0), + fractionDigits: 7, + ), + pendingSpendable: Amount( + rawValue: BigInt.from(0), + fractionDigits: 7, + ), + ); + Logging.instance.log(_balance, level: LogLevel.Info); + await updateCachedBalance(_balance!); + } + } + } + + @override + Future refresh() async { + if (refreshMutex) { + Logging.instance.log( + "$walletId $walletName refreshMutex denied", + level: LogLevel.Info, + ); + return; + } else { + refreshMutex = true; + } + + try { + await _prefs.init(); + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + await updateChainHeight(); + await updateTransactions(); + await updateBalance(); + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { + Logging.instance.log( + "Periodic refresh check for $walletId $walletName in object instance: $hashCode", + level: LogLevel.Info); + + await refresh(); + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New data found in $walletId $walletName in background!", + walletId, + ), + ); + }); + } + } catch (e, s) { + Logging.instance.log( + "Failed to refresh stellar wallet $walletId: '$walletName': $e\n$s", + level: LogLevel.Warning, + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + coin, + ), + ); + } + + refreshMutex = false; + } + + @override + int get storedChainHeight => getCachedChainHeight(); + + @override + Future testNetworkConnection() { + // TODO: implement testNetworkConnection + throw UnimplementedError(); + } + + @override + Future> get transactions => db.getTransactions(walletId).findAll(); + + @override + Future updateNode(bool shouldRefresh) async { + _xlmNode = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + + if (shouldRefresh) { + unawaited(refresh()); + } + } + + @override + Future updateSentCachedTxData(Map txData) { + // TODO: implement updateSentCachedTxData + throw UnimplementedError(); + } + + @override + // TODO: implement utxos + Future> get utxos => throw UnimplementedError(); + + @override + bool validateAddress(String address) { + return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address); + } + + @override + String get walletId => _walletId; + late String _walletId; +} \ No newline at end of file diff --git a/lib/themes/color_theme.dart b/lib/themes/color_theme.dart index bcfa45ac9..87020ada7 100644 --- a/lib/themes/color_theme.dart +++ b/lib/themes/color_theme.dart @@ -28,6 +28,7 @@ class CoinThemeColorDefault { Color get namecoin => const Color(0xFF91B1E1); Color get wownero => const Color(0xFFED80C1); Color get particl => const Color(0xFF8175BD); + Color get stellar => const Color(0xFFE8E8E8); // TODO: find color Color get nano => const Color(0xFF209CE9); Color get banano => const Color(0xFFFBDD11); @@ -62,6 +63,8 @@ class CoinThemeColorDefault { return wownero; case Coin.particl: return particl; + case Coin.stellar: + return stellar; case Coin.nano: return nano; case Coin.banano: diff --git a/lib/themes/stack_colors.dart b/lib/themes/stack_colors.dart index b9e58a5ca..f653b7eac 100644 --- a/lib/themes/stack_colors.dart +++ b/lib/themes/stack_colors.dart @@ -1707,6 +1707,8 @@ class StackColors extends ThemeExtension { return _coin.wownero; case Coin.particl: return _coin.particl; + case Coin.stellar: + return _coin.stellar; case Coin.nano: return _coin.nano; case Coin.banano: diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 0093e1d00..c2e759524 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -105,6 +105,8 @@ class AddressUtils { return Address.validateAddress(address, namecoin, namecoin.bech32!); case Coin.particl: return Address.validateAddress(address, particl); + case Coin.stellar: + return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address); case Coin.nano: return NanoAccounts.isValid(NanoAccountType.NANO, address); case Coin.banano: diff --git a/lib/utilities/amount/amount_unit.dart b/lib/utilities/amount/amount_unit.dart index 91fef8bc7..37939f1cc 100644 --- a/lib/utilities/amount/amount_unit.dart +++ b/lib/utilities/amount/amount_unit.dart @@ -50,6 +50,7 @@ enum AmountUnit { case Coin.dogecoin: case Coin.eCash: case Coin.epicCash: + case Coin.stellar: // TODO: check if this is correct return AmountUnit.values.sublist(0, 4); case Coin.monero: diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index 7ff0cf349..ae27fae02 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -54,6 +54,8 @@ Uri getDefaultBlockExplorerUrlFor({ return Uri.parse("https://chainz.cryptoid.info/nmc/tx.dws?$txid.htm"); case Coin.particl: return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm"); + case Coin.stellar: + return Uri.parse("https://stellarchain.io/tx/$txid"); case Coin.nano: return Uri.parse("https://www.nanolooker.com/block/$txid"); case Coin.banano: diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 2dd7be287..665ae3169 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -43,6 +43,7 @@ abstract class Constants { BigInt.parse("1000000000000000000000000000000"); // 1*10^30 static final BigInt _satsPerCoinBanano = BigInt.parse("100000000000000000000000000000"); // 1*10^29 + static final BigInt _satsPerCoinStellar = BigInt.from(10000000); // https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/assets#amount-precision static final BigInt _satsPerCoin = BigInt.from(100000000); static const int _decimalPlaces = 8; static const int _decimalPlacesNano = 30; @@ -51,6 +52,7 @@ abstract class Constants { static const int _decimalPlacesMonero = 12; static const int _decimalPlacesEthereum = 18; static const int _decimalPlacesECash = 2; + static const int _decimalPlacesStellar = 7; static const int notificationsMax = 0xFFFFFFFF; static const Duration networkAliveTimerDuration = Duration(seconds: 10); @@ -96,6 +98,9 @@ abstract class Constants { case Coin.eCash: return _satsPerCoinECash; + + case Coin.stellar: + return _satsPerCoinStellar; } } @@ -133,6 +138,9 @@ abstract class Constants { case Coin.eCash: return _decimalPlacesECash; + + case Coin.stellar: + return _decimalPlacesStellar; } } @@ -155,6 +163,7 @@ abstract class Constants { case Coin.namecoin: case Coin.particl: case Coin.nano: + case Coin.stellar: values.addAll([24, 12]); break; case Coin.banano: @@ -214,6 +223,9 @@ abstract class Constants { case Coin.nano: // TODO: Verify this case Coin.banano: // TODO: Verify this return 1; + + case Coin.stellar: + return 5; } } @@ -241,6 +253,7 @@ abstract class Constants { case Coin.nano: case Coin.banano: + case Coin.stellar: return 24; case Coin.monero: diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index c8ff94120..5ad9cd39a 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -181,6 +181,18 @@ abstract class DefaultNodes { isFailover: true, isDown: false); + static NodeModel get stellar => NodeModel( + host: "https://horizon.stellar.org", + port: 443, + name: defaultName, + id: _nodeId(Coin.stellar), + useSSL: true, + enabled: true, + coinName: Coin.stellar.name, + isFailover: true, + isDown: false + ); + static NodeModel get nano => NodeModel( host: "https://rainstorm.city/api", port: 443, @@ -301,6 +313,9 @@ abstract class DefaultNodes { case Coin.particl: return particl; + case Coin.stellar: + return stellar; + case Coin.nano: return nano; diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index c656b2c62..e66822738 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -27,6 +27,7 @@ import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' import 'package:stackwallet/services/coins/nano/nano_wallet.dart' as nano; import 'package:stackwallet/services/coins/particl/particl_wallet.dart' as particl; +import 'package:stackwallet/services/coins/stellar/stellar_wallet.dart' as xlm; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow; import 'package:stackwallet/utilities/constants.dart'; @@ -44,6 +45,7 @@ enum Coin { namecoin, nano, particl, + stellar, wownero, /// @@ -84,6 +86,8 @@ extension CoinExt on Coin { return "Monero"; case Coin.particl: return "Particl"; + case Coin.stellar: + return "Stellar"; case Coin.wownero: return "Wownero"; case Coin.namecoin: @@ -127,6 +131,8 @@ extension CoinExt on Coin { return "XMR"; case Coin.particl: return "PART"; + case Coin.stellar: + return "XLM"; case Coin.wownero: return "WOW"; case Coin.namecoin: @@ -171,6 +177,8 @@ extension CoinExt on Coin { return "monero"; case Coin.particl: return "particl"; + case Coin.stellar: + return "stellar"; case Coin.wownero: return "wownero"; case Coin.namecoin: @@ -215,6 +223,7 @@ extension CoinExt on Coin { case Coin.wownero: case Coin.nano: case Coin.banano: + case Coin.stellar: return false; } } @@ -242,6 +251,7 @@ extension CoinExt on Coin { case Coin.firoTestNet: case Coin.nano: case Coin.banano: + case Coin.stellar: return false; } } @@ -262,6 +272,7 @@ extension CoinExt on Coin { case Coin.nano: case Coin.banano: case Coin.eCash: + case Coin.stellar: return false; case Coin.dogecoinTestNet: @@ -289,6 +300,7 @@ extension CoinExt on Coin { case Coin.nano: case Coin.banano: case Coin.eCash: + case Coin.stellar: return this; case Coin.dogecoinTestNet: @@ -345,6 +357,9 @@ extension CoinExt on Coin { case Coin.particl: return particl.MINIMUM_CONFIRMATIONS; + case Coin.stellar: + return xlm.MINIMUM_CONFIRMATIONS; + case Coin.wownero: return wow.MINIMUM_CONFIRMATIONS; @@ -404,6 +419,10 @@ Coin coinFromPrettyName(String name) { case "particl": return Coin.particl; + case "Stellar": + case "stellar": + return Coin.stellar; + case "Namecoin": case "namecoin": return Coin.namecoin; @@ -481,6 +500,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.namecoin; case "part": return Coin.particl; + case "xlm": + return Coin.stellar; case "tltc": return Coin.litecoinTestNet; case "tbtc": diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart index f3e7f86df..1dbd3f79b 100644 --- a/lib/utilities/enums/derive_path_type_enum.dart +++ b/lib/utilities/enums/derive_path_type_enum.dart @@ -49,6 +49,7 @@ extension DerivePathTypeExt on DerivePathType { case Coin.wownero: case Coin.nano: case Coin.banano: + case Coin.stellar: throw UnsupportedError( "$coin does not use bitcoin style derivation paths"); } diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index 9214e3e0a..b906320d3 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -194,7 +194,9 @@ class _NodeCardState extends ConsumerState { case Coin.nano: case Coin.banano: - //TODO: check network/node + case Coin.stellar: + throw UnimplementedError(); + //TODO: check network/node } if (testPassed) { diff --git a/lib/widgets/node_options_sheet.dart b/lib/widgets/node_options_sheet.dart index 16e87581c..f8b788fed 100644 --- a/lib/widgets/node_options_sheet.dart +++ b/lib/widgets/node_options_sheet.dart @@ -177,7 +177,9 @@ class NodeOptionsSheet extends ConsumerWidget { case Coin.nano: case Coin.banano: - //TODO: check network/node + case Coin.stellar: + throw UnimplementedError(); + //TODO: check network/node } if (testPassed) { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b044b4b00..b741c3c0a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,9 +6,6 @@ import FlutterMacOS import Foundation import connectivity_plus -import cw_monero -import cw_shared_external -import cw_wownero import desktop_drop import device_info_plus import devicelocale @@ -16,10 +13,10 @@ import flutter_libepiccash import flutter_local_notifications import flutter_secure_storage_macos import isar_flutter_libs -import lelantus import package_info_plus import path_provider_foundation import share_plus +import shared_preferences_foundation import stack_wallet_backup import url_launcher_macos import wakelock_macos @@ -27,9 +24,6 @@ import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) - CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) - CwSharedExternalPlugin.register(with: registry.registrar(forPlugin: "CwSharedExternalPlugin")) - CwWowneroPlugin.register(with: registry.registrar(forPlugin: "CwWowneroPlugin")) DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) @@ -37,10 +31,10 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) - LelantusPlugin.register(with: registry.registrar(forPlugin: "LelantusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) StackWalletBackupPlugin.register(with: registry.registrar(forPlugin: "StackWalletBackupPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) diff --git a/pubspec.lock b/pubspec.lock index e260cb994..2987cff3e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,18 +37,18 @@ packages: dependency: transitive description: name: args - sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" asn1lib: dependency: transitive description: name: asn1lib - sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + sha256: b74e3842a52c61f8819a1ec8444b4de5419b41a7465e69d4aa681445377398b0 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" async: dependency: "direct main" description: @@ -146,10 +146,10 @@ packages: dependency: transitive description: name: build - sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" build_config: dependency: transitive description: @@ -170,26 +170,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 + sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "220ae4553e50d7c21a17c051afc7b183d28a24a420502e842f303f8e4e6edced" + sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.6" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" url: "https://pub.dev" source: hosted - version: "7.2.9" + version: "7.2.10" built_collection: dependency: transitive description: @@ -202,10 +202,10 @@ packages: dependency: transitive description: name: built_value - sha256: "7dd62d9faf105c434f3d829bbe9c4be02ec67f5ed94832222116122df67c5452" + sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" url: "https://pub.dev" source: hosted - version: "8.6.0" + version: "8.6.1" characters: dependency: transitive description: @@ -242,10 +242,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.5.0" collection: dependency: transitive description: @@ -298,10 +298,10 @@ packages: dependency: "direct main" description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" cryptography: dependency: transitive description: @@ -314,10 +314,10 @@ packages: dependency: transitive description: name: csslib - sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" url: "https://pub.dev" source: hosted - version: "0.17.3" + version: "1.0.0" cw_core: dependency: "direct main" description: @@ -382,18 +382,18 @@ packages: dependency: transitive description: name: dart_style - sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" dartx: dependency: transitive description: name: dartx - sha256: "45d7176701f16c5a5e00a4798791c1964bc231491b879369c818dd9a9c764871" + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" dbus: dependency: transitive description: @@ -406,10 +406,10 @@ packages: dependency: "direct main" description: name: decimal - sha256: eece91944f523657c75a3a008a90ec7f7eb3986191153a78570c7d0ac8ef3d01 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" dependency_validator: dependency: "direct dev" description: @@ -450,14 +450,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + dio: + dependency: transitive + description: + name: dio + sha256: "3866d67f93523161b643187af65f5ac08bc991a5bcdaf41a2d587fe4ccb49993" + url: "https://pub.dev" + source: hosted + version: "5.3.0" dropdown_button2: dependency: "direct main" description: name: dropdown_button2 - sha256: "374f2390161bf782b4896f0b1b24cbb2b5daaa1cfb11047c3307461dcdf44e07" + sha256: "83c54a5022f898d63e3abe21240b64b937e676103207287e6705d3f9bb04d654" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.6" eip1559: dependency: transitive description: @@ -542,10 +550,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf" + sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432 url: "https://pub.dev" source: hosted - version: "5.3.1" + version: "5.3.2" fixnum: dependency: transitive description: @@ -606,10 +614,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_local_notifications: dependency: "direct main" description: @@ -670,10 +678,10 @@ packages: dependency: "direct main" description: name: flutter_rounded_date_picker - sha256: e7143cc5cbf3aec1536286653e38b0809abc99fb76c91bd910dbd98ae003d890 + sha256: e6aa2dc5d3b44e8bbe85ef901be69eac59ba4136427f11f4c8b2a303e1e774e7 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" flutter_secure_storage: dependency: "direct main" description: @@ -821,10 +829,10 @@ packages: dependency: transitive description: name: html - sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" url: "https://pub.dev" source: hosted - version: "0.15.3" + version: "0.15.4" http: dependency: "direct main" description: @@ -948,15 +956,15 @@ packages: path: "crypto_plugins/flutter_liblelantus" relative: true source: path - version: "0.0.2" + version: "0.0.1" lints: dependency: transitive description: name: lints - sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" local_auth: dependency: "direct main" description: @@ -977,10 +985,10 @@ packages: dependency: "direct main" description: name: lottie - sha256: "23522951540d20a57a60202ed7022e6376bed206a4eee1c347a91f58bd57eb9f" + sha256: "0793a5866062e5cc8a8b24892fa94c3095953ea914a7fdf790f550dd7537fe60" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.0" matcher: dependency: transitive description: @@ -1033,10 +1041,10 @@ packages: dependency: "direct dev" description: name: mockito - sha256: "8b46d7eb40abdda92d62edd01546051f0c27365e65608c284de336dccfef88cc" + sha256: "7d5b53bcd556c1bc7ffbe4e4d5a19c3e112b7e925e9e172dd7c6ad0630812616" url: "https://pub.dev" source: hosted - version: "5.4.1" + version: "5.4.2" mocktail: dependency: transitive description: @@ -1145,10 +1153,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.4" path_provider_linux: dependency: transitive description: @@ -1169,50 +1177,50 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" permission_handler: dependency: "direct main" description: name: permission_handler - sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" + sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.4.3" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8 + sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" url: "https://pub.dev" source: hosted - version: "10.2.1" + version: "10.3.3" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "9.0.8" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.11.3" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" petitparser: dependency: transitive description: @@ -1221,6 +1229,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" + pinenacl: + dependency: transitive + description: + name: pinenacl + sha256: "3a5503637587d635647c93ea9a8fecf48a420cc7deebe6f1fc85c2a5637ab327" + url: "https://pub.dev" + source: hosted + version: "0.5.1" platform: dependency: transitive description: @@ -1233,10 +1249,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pointycastle: dependency: "direct main" description: @@ -1349,6 +1365,62 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4 + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d + url: "https://pub.dev" + source: hosted + version: "2.3.0" shelf: dependency: transitive description: @@ -1390,18 +1462,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" source_helper: dependency: transitive description: name: source_helper - sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.4" source_map_stack_trace: dependency: transitive description: @@ -1451,6 +1523,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.2+1" + stellar_flutter_sdk: + dependency: "direct main" + description: + name: stellar_flutter_sdk + sha256: "7a9b7dc76018bbd0b9c828045cf0e26e07ec44208fb1a1733273de2390205475" + url: "https://pub.dev" + source: hosted + version: "1.6.0" stream_channel: dependency: transitive description: @@ -1555,14 +1635,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + toml: + dependency: transitive + description: + name: toml + sha256: "69756bc12eccf279b72217a87310d217efc4b3752f722e890f672801f19ac485" + url: "https://pub.dev" + source: hosted + version: "0.13.1" tuple: dependency: "direct main" description: name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" typed_data: dependency: transitive description: @@ -1579,22 +1667,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + unorm_dart: + dependency: transitive + description: + name: unorm_dart + sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b" + url: "https://pub.dev" + source: hosted + version: "0.2.0" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 + sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" url: "https://pub.dev" source: hosted - version: "6.1.11" + version: "6.1.12" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51 + sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af" url: "https://pub.dev" source: hosted - version: "6.0.35" + version: "6.0.37" url_launcher_ios: dependency: transitive description: @@ -1615,34 +1711,34 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab" + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.18" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" uuid: dependency: "direct main" description: @@ -1768,18 +1864,18 @@ packages: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.0.5" win32_registry: dependency: transitive description: name: win32_registry - sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" window_size: dependency: "direct main" description: @@ -1831,4 +1927,4 @@ packages: version: "1.0.0" sdks: dart: ">=3.0.2 <4.0.0" - flutter: ">=3.10.3" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index f5b4cf27b..3419bc282 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -138,6 +138,7 @@ dependencies: desktop_drop: ^0.4.1 nanodart: ^2.0.0 basic_utils: ^5.5.4 + stellar_flutter_sdk: ^1.5.8 dev_dependencies: flutter_test: From bbd070ba7f80f9fa0dc7b905fe913d902c47c6f0 Mon Sep 17 00:00:00 2001 From: likho Date: Thu, 20 Jul 2023 17:40:49 +0200 Subject: [PATCH 02/16] Fix send --- .../coins/stellar/stellar_wallet.dart | 171 ++++++++++++------ lib/utilities/default_nodes.dart | 4 +- 2 files changed, 118 insertions(+), 57 deletions(-) diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index b7efcce68..23d884ad2 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -60,7 +60,7 @@ class StellarWallet extends CoinServiceAPI late final TransactionNotificationTracker txTracker; late SecureStorageInterface _secureStore; - final StellarSDK stellarSdk = StellarSDK.PUBLIC; + final StellarSDK stellarSdk = StellarSDK.TESTNET; @override bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); @@ -154,9 +154,37 @@ class StellarWallet extends CoinServiceAPI late Coin _coin; @override - Future confirmSend({required Map txData}) { - // TODO: implement confirmSend - throw UnimplementedError(); + Future confirmSend({required Map txData}) async { + print("TX DATA IS $txData"); + final secretSeed = await _secureStore.read( + key: '${_walletId}_secretSeed' + ); + + // final amt = Amount( + // rawValue: BigInt.from(txData['recipientAmt']), + // fractionDigits: coin.decimals, + // ); + // + // print("THIS AMOUNT IS $amount"); + //First check if account exists, can be skipped, but if the account does not exist, + // the transaction fee will be charged when the transaction fails. + AccountResponse receiverAccount = await stellarSdk.accounts + .account(txData['address'] as String).onError( + (error, stackTrace) => throw("Error getting account :: Cannot send transaction")); + + KeyPair senderKeyPair = KeyPair.fromSecretSeed(secretSeed!); + AccountResponse sender = await stellarSdk.accounts.account(senderKeyPair.accountId); + + Transaction transaction = TransactionBuilder(sender) + .addOperation(PaymentOperationBuilder(receiverAccount.accountId, Asset.NATIVE, "100").build()) + .build(); + transaction.sign(senderKeyPair, Network.TESTNET); + SubmitTransactionResponse response = await stellarSdk.submitTransaction(transaction); + + if (!response.success) { + throw("Unable to send transaction"); + } + return response.hash!; } Future get _currentReceivingAddress => @@ -219,9 +247,13 @@ class StellarWallet extends CoinServiceAPI @override Future get fees async { - final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); + // final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); + final nodeURI = Uri.parse(getCurrentNode().host); + + final httpClient = http.Client(); FeeStatsResponse fsp = await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); + return FeeObject( numberOfBlocksFast: 0, numberOfBlocksAverage: 0, @@ -265,10 +297,9 @@ class StellarWallet extends CoinServiceAPI await _prefs.init(); String mnemonic = await Wallet.generate24WordsMnemonic(); - final mnemonicArray = mnemonic.split(" "); await _secureStore.write( key: '${_walletId}_mnemonic', - value: mnemonicArray.join(" ") + value: mnemonic ); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', @@ -278,6 +309,13 @@ class StellarWallet extends CoinServiceAPI Wallet wallet = await Wallet.from(mnemonic); KeyPair keyPair = await wallet.getKeyPair(index: 0); String address = keyPair.accountId; + String secretSeed = keyPair.secretSeed; //This will be required for sending a tx + + + await _secureStore.write( + key: '${_walletId}_secretSeed', + value: secretSeed + ); final swAddress = SWAddress.Address( walletId: walletId, @@ -301,8 +339,10 @@ class StellarWallet extends CoinServiceAPI var mnemonic = await _secureStore.read( key: '${_walletId}_mnemonic' ); - var wallet = await Wallet.from(mnemonic!); - var keyPair = await wallet.getKeyPair(index: 0); + + Wallet wallet = await Wallet.from(mnemonic!); + KeyPair keyPair = await wallet.getKeyPair(index: 0); + return Future.value(keyPair.accountId); } @@ -368,6 +408,7 @@ class StellarWallet extends CoinServiceAPI var wallet = await Wallet.from(mnemonic); var keyPair = await wallet.getKeyPair(index: 0); var address = keyPair.accountId; + var secretSeed = keyPair.secretSeed; await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); @@ -375,6 +416,10 @@ class StellarWallet extends CoinServiceAPI key: '${_walletId}_mnemonicPassphrase', value: mnemonicPassphrase ?? "", ); + await _secureStore.write( + key: '${_walletId}_secretSeed', + value: secretSeed + ); final swAddress = SWAddress.Address( walletId: walletId, @@ -404,20 +449,29 @@ class StellarWallet extends CoinServiceAPI } Future updateTransactions() async { - List> transactionList = []; - Page payments = await stellarSdk.payments.forAccount(await getAddressSW()).order(RequestBuilderOrder.DESC).execute(); - for (OperationResponse response in payments.records!) { - if (response is PaymentOperationResponse) { - var por = response; - SWTransaction.TransactionType type; - if (por.sourceAccount == await getAddressSW()) { - type = SWTransaction.TransactionType.outgoing; - } else { - type = SWTransaction.TransactionType.incoming; - } - final amount = Amount( + + try { + List> transactionList = []; + + Page payments = await stellarSdk.payments + .forAccount(await getAddressSW()).order(RequestBuilderOrder.DESC) + .execute().onError((error, stackTrace) => + throw("Could not fetch transactions") + ); + + for (OperationResponse response in payments.records!) { + if (response is PaymentOperationResponse) { + var por = response; + SWTransaction.TransactionType type; + if (por.sourceAccount == await getAddressSW()) { + type = SWTransaction.TransactionType.outgoing; + } else { + type = SWTransaction.TransactionType.incoming; + } + final amount = Amount( rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(7).replaceAll(".", "")), fractionDigits: 7, +<<<<<<< HEAD ); int fee = 0; int height = 0; @@ -468,39 +522,39 @@ class StellarWallet extends CoinServiceAPI type = SWTransaction.TransactionType.incoming; } final amount = Amount( - rawValue: BigInt.parse(float.parse(caor.startingBalance!).toStringAsFixed(7).replaceAll(".", "")), - fractionDigits: 7, - ); - int fee = 0; - int height = 0; - var transaction = caor.transaction; - if (transaction != null) { - fee = transaction.feeCharged!; - height = transaction.ledger; - } - var theTransaction = SWTransaction.Transaction( - walletId: walletId, - txid: caor.transactionHash!, - timestamp: DateTime.parse(caor.createdAt!).millisecondsSinceEpoch ~/ 1000, - type: type, - subType: SWTransaction.TransactionSubType.none, - amount: 0, - amountString: amount.toJsonString(), - fee: fee, - height: height, - isCancelled: false, - isLelantus: false, - slateId: "", - otherData: "", - inputs: [], - outputs: [], - nonce: 0, - numberOfMessages: null, - ); - SWAddress.Address? receivingAddress = await _currentReceivingAddress; - SWAddress.Address address = type == SWTransaction.TransactionType.incoming - ? receivingAddress! - : SWAddress.Address( + rawValue: BigInt.parse(float.parse(caor.startingBalance!).toStringAsFixed(coin.decimals).replaceAll(".", "")), + fractionDigits: coin.decimals, + ); + int fee = 0; + int height = 0; + var transaction = caor.transaction; + if (transaction != null) { + fee = transaction.feeCharged!; + height = transaction.ledger; + } + var theTransaction = SWTransaction.Transaction( + walletId: walletId, + txid: caor.transactionHash!, + timestamp: DateTime.parse(caor.createdAt!).millisecondsSinceEpoch ~/ 1000, + type: type, + subType: SWTransaction.TransactionSubType.none, + amount: 0, + amountString: amount.toJsonString(), + fee: fee, + height: height, + isCancelled: false, + isLelantus: false, + slateId: "", + otherData: "", + inputs: [], + outputs: [], + nonce: 0, + numberOfMessages: null, + ); + SWAddress.Address? receivingAddress = await _currentReceivingAddress; + SWAddress.Address address = type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( walletId: walletId, value: caor.sourceAccount!, publicKey: KeyPair.fromAccountId(caor.sourceAccount!).publicKey, @@ -511,9 +565,16 @@ class StellarWallet extends CoinServiceAPI ); Tuple2 tuple = Tuple2(theTransaction, address); transactionList.add(tuple); + } } + await db.addNewTransactionData(transactionList, walletId); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from updateTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; } - await db.addNewTransactionData(transactionList, walletId); + } Future updateBalance() async { diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 5ad9cd39a..02bb4ff29 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -182,11 +182,11 @@ abstract class DefaultNodes { isDown: false); static NodeModel get stellar => NodeModel( - host: "https://horizon.stellar.org", + host: "https://horizon-testnet.stellar.org", port: 443, name: defaultName, id: _nodeId(Coin.stellar), - useSSL: true, + useSSL: false, enabled: true, coinName: Coin.stellar.name, isFailover: true, From c68b2e0d02e51e2980e3c1be18a4c10757caf50f Mon Sep 17 00:00:00 2001 From: likho Date: Fri, 21 Jul 2023 14:59:47 +0200 Subject: [PATCH 03/16] allow sending to unfunded account --- .../coins/stellar/stellar_wallet.dart | 274 +++++++++++------- 1 file changed, 170 insertions(+), 104 deletions(-) diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index 23d884ad2..af7399896 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -153,38 +153,65 @@ class StellarWallet extends CoinServiceAPI Coin get coin => _coin; late Coin _coin; + Future accoutExists(String accountId) async { + bool exists = false; + + try { + AccountResponse receiverAccount = await stellarSdk.accounts + .account(accountId); + if (receiverAccount.accountId != "") { + exists = true; + } + } catch(e, s) { + Logging.instance.log("Error getting account ${e.toString()} - ${s.toString()}", level: LogLevel.Error); + } + return exists; + } @override Future confirmSend({required Map txData}) async { - print("TX DATA IS $txData"); final secretSeed = await _secureStore.read( key: '${_walletId}_secretSeed' ); - - // final amt = Amount( - // rawValue: BigInt.from(txData['recipientAmt']), - // fractionDigits: coin.decimals, - // ); - // - // print("THIS AMOUNT IS $amount"); - //First check if account exists, can be skipped, but if the account does not exist, - // the transaction fee will be charged when the transaction fails. - AccountResponse receiverAccount = await stellarSdk.accounts - .account(txData['address'] as String).onError( - (error, stackTrace) => throw("Error getting account :: Cannot send transaction")); - KeyPair senderKeyPair = KeyPair.fromSecretSeed(secretSeed!); AccountResponse sender = await stellarSdk.accounts.account(senderKeyPair.accountId); - Transaction transaction = TransactionBuilder(sender) - .addOperation(PaymentOperationBuilder(receiverAccount.accountId, Asset.NATIVE, "100").build()) - .build(); - transaction.sign(senderKeyPair, Network.TESTNET); - SubmitTransactionResponse response = await stellarSdk.submitTransaction(transaction); + final amountToSend = txData['recipientAmt'] as Amount; - if (!response.success) { - throw("Unable to send transaction"); + //First check if account exists, can be skipped, but if the account does not exist, + // the transaction fee will be charged when the transaction fails. + bool validAccount = await accoutExists(txData['address'] as String); + Transaction transaction; + + if (!validAccount) { + //Fund the account, user must ensure account is correct + CreateAccountOperationBuilder createAccBuilder = + CreateAccountOperationBuilder( + txData['address'] as String, amountToSend.decimal.toString() + ); + transaction = TransactionBuilder(sender) + .addOperation(createAccBuilder.build()) + .build(); + } else { + transaction = TransactionBuilder(sender) + .addOperation(PaymentOperationBuilder( + txData['address'] as String, Asset.NATIVE, + amountToSend.decimal.toString()) + .build() + ).build(); } - return response.hash!; + transaction.sign(senderKeyPair, Network.TESTNET); + try { + SubmitTransactionResponse response = await stellarSdk.submitTransaction(transaction); + + if (!response.success) { + throw("Unable to send transaction"); + } + return response.hash!; + } catch(e, s) { + Logging.instance.log("Error sending TX $e - $s", level: LogLevel.Error); + rethrow; + } + } Future get _currentReceivingAddress => @@ -460,68 +487,72 @@ class StellarWallet extends CoinServiceAPI ); for (OperationResponse response in payments.records!) { + PaymentOperationResponse por; if (response is PaymentOperationResponse) { - var por = response; + por = response; + + Logging.instance.log( + "ALL TRANSACTIONS IS $por", + level: LogLevel.Info); SWTransaction.TransactionType type; if (por.sourceAccount == await getAddressSW()) { type = SWTransaction.TransactionType.outgoing; } else { type = SWTransaction.TransactionType.incoming; } - final amount = Amount( - rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(7).replaceAll(".", "")), - fractionDigits: 7, -<<<<<<< HEAD - ); - int fee = 0; - int height = 0; - var transaction = por.transaction; - if (transaction != null) { - fee = transaction.feeCharged!; - height = transaction.ledger; - } - var theTransaction = SWTransaction.Transaction( - walletId: walletId, - txid: por.transactionHash!, - timestamp: DateTime.parse(por.createdAt!).millisecondsSinceEpoch ~/ 1000, - type: type, - subType: SWTransaction.TransactionSubType.none, - amount: 0, - amountString: amount.toJsonString(), - fee: fee, - height: height, - isCancelled: false, - isLelantus: false, - slateId: "", - otherData: "", - inputs: [], - outputs: [], - nonce: 0, - numberOfMessages: null, - ); - SWAddress.Address? receivingAddress = await _currentReceivingAddress; - SWAddress.Address address = type == SWTransaction.TransactionType.incoming - ? receivingAddress! - : SWAddress.Address( - walletId: walletId, - value: por.sourceAccount!, - publicKey: KeyPair.fromAccountId(por.sourceAccount!).publicKey, - derivationIndex: 0, - derivationPath: null, - type: SWAddress.AddressType.unknown, // TODO: set type - subType: SWAddress.AddressSubType.unknown - ); - Tuple2 tuple = Tuple2(theTransaction, address); - transactionList.add(tuple); - } else if (response is CreateAccountOperationResponse) { - var caor = response; - SWTransaction.TransactionType type; - if (caor.sourceAccount == await getAddressSW()) { - type = SWTransaction.TransactionType.outgoing; - } else { - type = SWTransaction.TransactionType.incoming; - } final amount = Amount( + rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(coin.decimals).replaceAll(".", "")), + fractionDigits: coin.decimals, + ); + int fee = 0; + int height = 0; + var transaction = por.transaction; + if (transaction != null) { + fee = transaction.feeCharged!; + height = transaction.ledger; + } + var theTransaction = SWTransaction.Transaction( + walletId: walletId, + txid: por.transactionHash!, + timestamp: DateTime.parse(por.createdAt!).millisecondsSinceEpoch ~/ 1000, + type: type, + subType: SWTransaction.TransactionSubType.none, + amount: 0, + amountString: amount.toJsonString(), + fee: fee, + height: height, + isCancelled: false, + isLelantus: false, + slateId: "", + otherData: "", + inputs: [], + outputs: [], + nonce: 0, + numberOfMessages: null, + ); + SWAddress.Address? receivingAddress = await _currentReceivingAddress; + SWAddress.Address address = type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( + walletId: walletId, + value: por.sourceAccount!, + publicKey: KeyPair.fromAccountId(por.sourceAccount!).publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown, + ); + Tuple2 tuple = Tuple2(theTransaction, address); + transactionList.add(tuple); + } else if (response is CreateAccountOperationResponse) { + var caor = response; + SWTransaction.TransactionType type; + if (caor.sourceAccount == await getAddressSW()) { + type = SWTransaction.TransactionType.outgoing; + } else { + type = SWTransaction.TransactionType.incoming; + } + final amount = Amount( rawValue: BigInt.parse(float.parse(caor.startingBalance!).toStringAsFixed(coin.decimals).replaceAll(".", "")), fractionDigits: coin.decimals, ); @@ -572,37 +603,43 @@ class StellarWallet extends CoinServiceAPI Logging.instance.log( "Exception rethrown from updateTransactions(): $e\n$s", level: LogLevel.Error); - rethrow; } } Future updateBalance() async { - AccountResponse accountResponse = await stellarSdk.accounts.account(await getAddressSW()); - for (Balance balance in accountResponse.balances) { - switch (balance.assetType) { - case Asset.TYPE_NATIVE: - _balance = SWBalance.Balance( - total: Amount( - rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee - fractionDigits: 7, - ), - spendable: Amount( - rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee - fractionDigits: 7, - ), - blockedTotal: Amount( - rawValue: BigInt.from(0), - fractionDigits: 7, - ), - pendingSpendable: Amount( - rawValue: BigInt.from(0), - fractionDigits: 7, - ), - ); - Logging.instance.log(_balance, level: LogLevel.Info); - await updateCachedBalance(_balance!); + try { + AccountResponse accountResponse = await stellarSdk.accounts.account(await getAddressSW()); + for (Balance balance in accountResponse.balances) { + switch (balance.assetType) { + case Asset.TYPE_NATIVE: + _balance = SWBalance.Balance( + total: Amount( + rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee + fractionDigits: coin.decimals, + ), + spendable: Amount( + rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee + fractionDigits: coin.decimals, + ), + blockedTotal: Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ), + ); + Logging.instance.log(_balance, level: LogLevel.Info); + await updateCachedBalance(_balance!); + } } + } catch(e, s) { + Logging.instance.log( + "ERROR GETTING BALANCE $e\$s", + level: LogLevel.Info, + ); } } @@ -697,9 +734,38 @@ class StellarWallet extends CoinServiceAPI } @override - Future updateSentCachedTxData(Map txData) { - // TODO: implement updateSentCachedTxData - throw UnimplementedError(); + Future updateSentCachedTxData(Map txData) async { + final transaction = SWTransaction.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: SWTransaction.TransactionType.outgoing, + subType: SWTransaction.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, + inputs: [], + outputs: [], + numberOfMessages: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } @override From 4bdda9354c6629c6726e9995d12e05055c764360 Mon Sep 17 00:00:00 2001 From: likho Date: Sat, 22 Jul 2023 09:47:59 +0200 Subject: [PATCH 04/16] WIP: Clean up and fixes --- lib/services/coins/stellar/stellar_wallet.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index af7399896..b629b56ef 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -228,7 +228,8 @@ class StellarWallet extends CoinServiceAPI (await _currentReceivingAddress)?.value ?? await getAddressSW(); Future getBaseFee() async { - final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); + // final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); + final nodeURI = Uri.parse(getCurrentNode().host); final httpClient = http.Client(); FeeStatsResponse fsp = await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); return int.parse(fsp.lastLedgerBaseFee); From 89aba8874ed55c99bfb8f561751775c9052837d7 Mon Sep 17 00:00:00 2001 From: likho Date: Sat, 22 Jul 2023 10:16:15 +0200 Subject: [PATCH 05/16] Clean up/ refactor --- lib/services/coins/stellar/stellar_wallet.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index b629b56ef..5fb8e1b35 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -541,7 +541,7 @@ class StellarWallet extends CoinServiceAPI derivationIndex: 0, derivationPath: null, type: SWAddress.AddressType.unknown, // TODO: set type - subType: SWAddress.AddressSubType.unknown, + subType: SWAddress.AddressSubType.unknown ); Tuple2 tuple = Tuple2(theTransaction, address); transactionList.add(tuple); @@ -593,10 +593,9 @@ class StellarWallet extends CoinServiceAPI derivationIndex: 0, derivationPath: null, type: SWAddress.AddressType.unknown, // TODO: set type - subType: SWAddress.AddressSubType.unknown - ); - Tuple2 tuple = Tuple2(theTransaction, address); - transactionList.add(tuple); + subType: SWAddress.AddressSubType.unknown); + Tuple2 tuple = Tuple2(theTransaction, address); + transactionList.add(tuple); } } await db.addNewTransactionData(transactionList, walletId); From be8ede77503920b26fdf6fae49f9dd810a87e439 Mon Sep 17 00:00:00 2001 From: likho Date: Mon, 24 Jul 2023 16:53:04 +0200 Subject: [PATCH 06/16] Fix txs being sending and receiving, and missing fee and height in tx view --- .../coins/stellar/stellar_wallet.dart | 59 ++++++++++--------- lib/services/price.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index 5fb8e1b35..2b1da76c3 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -16,22 +16,22 @@ import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:tuple/tuple.dart'; -import '../../../db/isar/main_db.dart'; -import '../../../models/node_model.dart'; -import '../../../utilities/constants.dart'; -import '../../../utilities/default_nodes.dart'; -import '../../../utilities/enums/coin_enum.dart'; -import '../../../utilities/flutter_secure_storage_interface.dart'; -import '../../../utilities/logger.dart'; -import '../../../utilities/prefs.dart'; -import '../../event_bus/events/global/node_connection_status_changed_event.dart'; -import '../../event_bus/events/global/updated_in_background_event.dart'; -import '../../event_bus/events/global/wallet_sync_status_changed_event.dart'; -import '../../event_bus/global_event_bus.dart'; -import '../../mixins/wallet_cache.dart'; -import '../../mixins/wallet_db.dart'; -import '../../node_service.dart'; -import '../../transaction_notification_tracker.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/node_service.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart'; @@ -174,7 +174,6 @@ class StellarWallet extends CoinServiceAPI ); KeyPair senderKeyPair = KeyPair.fromSecretSeed(secretSeed!); AccountResponse sender = await stellarSdk.accounts.account(senderKeyPair.accountId); - final amountToSend = txData['recipientAmt'] as Amount; //First check if account exists, can be skipped, but if the account does not exist, @@ -486,14 +485,13 @@ class StellarWallet extends CoinServiceAPI .execute().onError((error, stackTrace) => throw("Could not fetch transactions") ); - for (OperationResponse response in payments.records!) { - PaymentOperationResponse por; + // PaymentOperationResponse por; if (response is PaymentOperationResponse) { - por = response; + PaymentOperationResponse por = response; Logging.instance.log( - "ALL TRANSACTIONS IS $por", + "ALL TRANSACTIONS IS ${por.transactionSuccessful}", level: LogLevel.Info); SWTransaction.TransactionType type; if (por.sourceAccount == await getAddressSW()) { @@ -507,10 +505,17 @@ class StellarWallet extends CoinServiceAPI ); int fee = 0; int height = 0; - var transaction = por.transaction; - if (transaction != null) { - fee = transaction.feeCharged!; - height = transaction.ledger; + TransactionResponse? transaction = por.transaction; + + Logging.instance.log( + "THIS TRANSACTION IS ${transaction?.hash}", + level: LogLevel.Info); + //Query the transaction linked to the payment, + // por.transaction returns a null sometimes + TransactionResponse tx = await stellarSdk.transactions.transaction(por.transactionHash!); + if (tx.hash != "") { + fee = tx.feeCharged!; + height = tx.ledger; } var theTransaction = SWTransaction.Transaction( walletId: walletId, @@ -615,11 +620,11 @@ class StellarWallet extends CoinServiceAPI case Asset.TYPE_NATIVE: _balance = SWBalance.Balance( total: Amount( - rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee + rawValue: BigInt.from(float.parse(balance.balance) * 10000000), fractionDigits: coin.decimals, ), spendable: Amount( - rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee + rawValue: BigInt.from(float.parse(balance.balance) * 10000000), fractionDigits: coin.decimals, ), blockedTotal: Amount( diff --git a/lib/services/price.dart b/lib/services/price.dart index 24d929062..66842c9d8 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -100,7 +100,7 @@ class PriceAPI { Uri.parse("https://api.coingecko.com/api/v3/coins/markets?vs_currency" "=${baseCurrency.toLowerCase()}" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"); final coinGeckoResponse = await client.get( diff --git a/pubspec.yaml b/pubspec.yaml index 3419bc282..609c4ed7d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -138,7 +138,7 @@ dependencies: desktop_drop: ^0.4.1 nanodart: ^2.0.0 basic_utils: ^5.5.4 - stellar_flutter_sdk: ^1.5.8 + stellar_flutter_sdk: ^1.6.0 dev_dependencies: flutter_test: From f7741fa04317b02a035466e7d18f19ab693c0204 Mon Sep 17 00:00:00 2001 From: likho Date: Tue, 25 Jul 2023 15:58:29 +0200 Subject: [PATCH 07/16] Update theme with Stellar images --- assets/default_themes/dark.zip | Bin 616000 -> 659920 bytes assets/default_themes/light.zip | Bin 563892 -> 607924 bytes lib/models/isar/stack_theme.dart | 354 ++++++++++++++++++ .../coins/stellar/stellar_wallet.dart | 33 +- 4 files changed, 372 insertions(+), 15 deletions(-) diff --git a/assets/default_themes/dark.zip b/assets/default_themes/dark.zip index 07c4d6521107c4487dd049b16f8b15fc53235051..9f4e888ef9d1050ec9485dd9c0276f44fa129d1b 100644 GIT binary patch delta 51889 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]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 F{{dfB?2Z5c delta 7793 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 ARsaA1 delta 7716 zcmaKw2{=^y|Hsb^W9DRuELp}imZ->*ElWj<>`Sx|LZ~Pd5n>v$t86t9bx|r>uEcU_Y`TKB!+E~6FqRGr*~%pjdV6DpOs>Td zK`9p;c2@v`U!Bs%}r(xg>q9l%!bM|(SxPYEf(ya=s#aNiVP zs+fQcGPf}v8)IL*5?^pL5rWjY3q#G--Y{UPI1Y_8Y+xoN04P-Hfe9HF#G@V z?Dm!8Bzf+^oHUB$zeXgW3$47Nb3D5$hHakjYZe9Wg z#5>3=nhs%oF&?-zK!8+G;9MsG%Qklsk}L69&2R!|CzyIBur5-Pxhqddl2x8S+?y&#qqY7m^)h|e05h`9am zjwNApi;g8JMd7m{CD`7fXN~n{oV2`HG8#LQB{>90mEE4RT<&>7*fxOmwaSr+aRq$UUyJ(6L!|JkMgcJX9( zn01*!`Aoo1DG*!?MRu$?k)n>qMiVKyg7{b)xVM~Ve?clm1&!TIrT7u?v70nrHDwDJ zs}`I=Q5M0+g0&#-IR!CEW-XHQ$7mqNeoQezjLHlU$1W*rzc_rA zFPk9*K}x)=0&{c)_M3*qkb+^p>~63roQIY2YA|v4m9PRjqsMdMYQCLe|61Z4VotVJ z4yK~xeIf@B@MV<<5cCIo44ka}Dr~$=0u>&`Pt6IG{akw z=Nx2^)k3>3P=XnJF*VR#f!BR5^U`W;1(>5Wcu3Nv96j!zk#j`0g0XX?paMG(=wE!W zqn@M(L9;-~h;0Eo1GIy}Lqj;f^TW*|{drG6|2=z!A%fsrFdfdf5f6vl(t3%;u}hD~ zO`eey6mk{byqZuyvguG>X25X|i@c^+`7yyWv1)`B#JI5creeQ* z+UD8gSeK8+wpw*B9<+b6e_)XAIn#F5-cv?V>UF$4So56O6Vz&BF%zqIA9O9XNmib5 zF_fvWoT9FEYfqM?y&~sQ)6*4Ozmy!L?MOGDGf3~!9#?ISeEgJ_{3R$aC0b7}2|HG^D-`Nx5?`-yksaW9a3hH@jW(S^k~B2F>=t7V5Rp zbeR4{HldB~rB~Op_0agcDvvh(PJ>?9$52dSgJyHj{0Oh8SEE+SZQ&cEeKLi$4A4I;`)dOGQaoh~mMecVf0{oU#ZT5M^_^%)-?*+mBJp(K89AF;~~qdQla z2}}ha*jY0n=Q|==xF(}8LNfH}$lIU_vG~Z$0*S*e4>YX5Ja_%~>V%6$i2t#@wR;=K zl+-$3S(Sv0+kIEPqw`$Amr$Zh^2_{sioB_H*XJB6{btQd+48L?l=~*nY1Yk2k6Wh3 zP}uQMw|Kx;+{*V9BQ5T*`JBo|`Ms7=<CCYVNF0)Ld5EsAglm`C)C%*TI!_rL$)r zjqNEmE!E34tUU`&ubq9Kvco3i?0uq}kxQ|@+1y1-jwCFfoX}giIc%-Qo-<WXUDqJn&du-=sfq13>wgA5-L2>)0dG9n?U4 zx%-99^Q^t^cES0xHO}q)?4o<~HL1fr;Z5HS|J)7*s*7vsJH3Muendbw!w zQL`Pb_eZkt8XD0bX-wQ{jVn-h|0Focp)f6&b6?8_T6A#Y1p{P&6g9u?_S-tKw`(c z)cVC;N{Jtw805s$g*)_C-0(A{`|8j2Fv*swk_TVdl*heQeV4s1sZ95@!Frl| zhQ^2CtR}JJ{&#&}xqCf|JVkHIt%$vPeev!}{p>^SePx5>fhR@gg!=tMEhjY243nD- z?+|)PzVlBi-~FMmE4?bP?s>RpT5738f7sFfYm3cxt_$@m*XgrdyD~dpVXE->w&YKQLE^)os zE%WaAjn5vtOG(orPc~O)#Z~6Z?F@3%4)Tps^=hYG5`ykVL2 zObg}Pg%QUcHBG*gf-l#&4KK0Z^UP?#Q0slh7FJ@^k^^fDSB9OY!AYgza)w$81-EF4 zEe#BoGQqH@CHr8o()0sGg(pu0=s{!dUq8pyW(UMxqUQ!%1T55=+a9i69J;dpf*W05 zX#c7?7j?T&5okHLg*VJop0`56+$&+>iy0rkY+8TpLb;jI*D9;)ZP)FdHi1^~0^N;tKA`qyS`rsXe&YOf0DTeO|?~;FR9&7d%8pgI{}4 zdplUys=ZRWu3m`9J-+FVObToK!o|>TA5mh3(CQ`52i&v{##$6RM>hoSy%zFSCpd*Q zlwur|${I>#1*b8G(pW+1#zX0>;0)uTjN%55P*rJdPhr^wYnp5_5>6yNevn=4ZN6g{ zz1Us%MCz;`D!v(T%MG7Qp>98)4AN7gk1)yMA7fI2O4sq57Ww^)EdO+TTO;17w#yDW zE6KX^V=~e^EdGtTP>)j2k(W9%Gk0fZ=uI&wrL2xkG3(qxo_PE+P3R&q`o^?=?lyYp1U^w zRN!0jWD}}m(}MCz|Ey2%-5K}fH$JG^Cc0&5>biT>hbD5DrbAY>H0)2XJ{{i_!zdeA zW&b6=`TmsA^w=Q5G~SOY>e)3nt?a){>#>pU%gm=HPru#ES>AKrcC+k~F_8^*`G+$@ zw9nep1I%7++sddsYLsifuK7j1(uc1;Or@b&MK9(&@v(NYUQm5*-u&p(zF#MwjLksK zc4W%b8^6A%;7yhNvgB+>AW8}R-)4yYcPjhy!;<~|cbF5QNw%3>gQ1*YUY3 zw9SWwiu-RnK@j4qFs27A9Y=fM%@xW`$-h1_Lx3Q}Tw$DK6zKJf$3h2Laz5!+1ppC` zg@LKjJP>cS(1z}AgXAv2Ayy0H&cy;Q8|S!C<`rep(g_em92dq6$8jfzH(n^VUN^XP zEog-pFAQ5x=V3Vig<_T}*{4hZhWIZGqcV6)iML@WcRBjpf>5y2hz-NIy9vB@I5&o3 zwf>YWCcqFkhGCbIco@! zvyPZE4D!z4G~@SZT(B32wBbkfl#d{dh%^kW$l@&uUaPS|S`O_dOY*f)$m_%}l1T^tG$Q&Mu*Bm()&Q*ju$kxt~;BTUR8b8NJn${w(XAVkSw zjFf-?&p6=)9oj6qpwBEB+yTVTVc=G00CJQP-qxX@x^~=ufvymQ$T|%4>Oerex2q!s{s5(>Y-qIe0#z~Bm^N&4+CZQ zl6W2Qh7Scvqs4ww-~b?k4+H6AT=1tGG7NN|U1i9m0ytQR?!zFVY@y%CKkgMt2J&x} zxwpa6gAjzsKa9*#C37Z-sX)#xUI8p%;5A|Mhv1?h77&A$R|4o4Cx}jN$l#;aUGS7b zj35RkJq94&52B28y-?9MaQhKGh%s0C0E3r>C}SJl)oBbKGl(n1nA8El;LRb*45kOi zeHI31gvJ$z7^D6YFnEoKGCL#dDhtFR2(gG5lQPO>_?;pOe(yXyXbJ|1C`Am+o&g|U zFrrM;6S|nU4+J585o7d;6n@)?GHvg*rp|+x9z-@`%tt@K;MF6_)K4$+Is_QRJYtOG zRxZQuAyIHTyo~S+ObXGE7$_6W1^J~U3f@+h+Ls271>z(z&^H8t9FvGQlqmSov+7(m z01-infnH$ assetsV3 ?? assetsV2 ?? assetsV1!; @@ -1533,6 +1535,12 @@ class StackTheme { themeId: json["id"] as String, ) : null + ..assetsV4 = version >= 4 + ? ThemeAssetsV4.fromJson( + json: Map.from(json["assets"] as Map), + themeId: json["id"] as String, + ) + : null ..themeId = json["id"] as String ..name = json["name"] as String ..brightnessString = json["brightness"] as String @@ -1921,6 +1929,7 @@ class ThemeAssets implements IThemeAssets { late final String wownero; late final String namecoin; late final String particl; + late final String stellar; late final String bitcoinImage; late final String bitcoincashImage; late final String dogecoinImage; @@ -1932,6 +1941,7 @@ class ThemeAssets implements IThemeAssets { late final String wowneroImage; late final String namecoinImage; late final String particlImage; + late final String stellarImage; late final String bitcoinImageSecondary; late final String bitcoincashImageSecondary; late final String dogecoinImageSecondary; @@ -1943,6 +1953,7 @@ class ThemeAssets implements IThemeAssets { late final String wowneroImageSecondary; late final String namecoinImageSecondary; late final String particlImageSecondary; + late final String stellarImageSecondary; @override late final String? loadingGif; @override @@ -1988,6 +1999,7 @@ class ThemeAssets implements IThemeAssets { ..wownero = "$themeId/assets/${json["wownero"] as String}" ..namecoin = "$themeId/assets/${json["namecoin"] as String}" ..particl = "$themeId/assets/${json["particl"] as String}" + ..stellar = "$themeId/assets/${json["stellar"] as String}" ..bitcoinImage = "$themeId/assets/${json["bitcoin_image"] as String}" ..bitcoincashImage = "$themeId/assets/${json["bitcoincash_image"] as String}" @@ -2000,6 +2012,7 @@ class ThemeAssets implements IThemeAssets { ..wowneroImage = "$themeId/assets/${json["wownero_image"] as String}" ..namecoinImage = "$themeId/assets/${json["namecoin_image"] as String}" ..particlImage = "$themeId/assets/${json["particl_image"] as String}" + ..stellarImage = "$themeId/assets/${json["stellar_image"] as String}" ..bitcoinImageSecondary = "$themeId/assets/${json["bitcoin_image_secondary"] as String}" ..bitcoincashImageSecondary = @@ -2022,6 +2035,8 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["namecoin_image_secondary"] as String}" ..particlImageSecondary = "$themeId/assets/${json["particl_image_secondary"] as String}" + ..stellarImageSecondary = + "$themeId/assets/${json["stellar_image_secondary"] as String}" ..loadingGif = json["loading_gif"] is String ? "$themeId/assets/${json["loading_gif"] as String}" : null @@ -2549,6 +2564,345 @@ class ThemeAssetsV3 implements IThemeAssets { } } +@Embedded(inheritance: false) +class ThemeAssetsV4 implements IThemeAssets { + @Name("bellNew") + late final String bellNewRelative; + @override + @ignore + String get bellNew => prependIfNeeded(bellNewRelative); + + @Name("buy") + late final String buyRelative; + @override + @ignore + String get buy => prependIfNeeded(buyRelative); + + @Name("exchange") + late final String exchangeRelative; + @override + @ignore + String get exchange => prependIfNeeded(exchangeRelative); + + @Name("personaIncognito") + late final String personaIncognitoRelative; + @override + @ignore + String get personaIncognito => prependIfNeeded(personaIncognitoRelative); + + @Name("personaEasy") + late final String personaEasyRelative; + @override + @ignore + String get personaEasy => prependIfNeeded(personaEasyRelative); + + @Name("stack") + late final String stackRelative; + @override + @ignore + String get stack => prependIfNeeded(stackRelative); + + @Name("stackIcon") + late final String stackIconRelative; + @override + @ignore + String get stackIcon => prependIfNeeded(stackIconRelative); + + @Name("receive") + late final String receiveRelative; + @override + @ignore + String get receive => prependIfNeeded(receiveRelative); + + @Name("receivePending") + late final String receivePendingRelative; + @override + @ignore + String get receivePending => prependIfNeeded(receivePendingRelative); + + @Name("receiveCancelled") + late final String receiveCancelledRelative; + @override + @ignore + String get receiveCancelled => prependIfNeeded(receiveCancelledRelative); + + @Name("send") + late final String sendRelative; + @override + @ignore + String get send => prependIfNeeded(sendRelative); + + @Name("sendPending") + late final String sendPendingRelative; + @override + @ignore + String get sendPending => prependIfNeeded(sendPendingRelative); + + @Name("sendCancelled") + late final String sendCancelledRelative; + @override + @ignore + String get sendCancelled => prependIfNeeded(sendCancelledRelative); + + @Name("themeSelector") + late final String themeSelectorRelative; + @override + @ignore + String get themeSelector => prependIfNeeded(themeSelectorRelative); + + @Name("themePreview") + late final String themePreviewRelative; + @override + @ignore + String get themePreview => prependIfNeeded(themePreviewRelative); + + @Name("txExchange") + late final String txExchangeRelative; + @override + @ignore + String get txExchange => prependIfNeeded(txExchangeRelative); + + @Name("txExchangePending") + late final String txExchangePendingRelative; + @override + @ignore + String get txExchangePending => prependIfNeeded(txExchangePendingRelative); + + @Name("txExchangeFailed") + late final String txExchangeFailedRelative; + @override + @ignore + String get txExchangeFailed => prependIfNeeded(txExchangeFailedRelative); + + @Name("loadingGif") + late final String? loadingGifRelative; + @override + @ignore + String? get loadingGif => + loadingGifRelative != null ? prependIfNeeded(loadingGifRelative!) : null; + + @Name("background") + late final String? backgroundRelative; + @override + @ignore + String? get background => + backgroundRelative != null ? prependIfNeeded(backgroundRelative!) : null; + + @Name("coinPlaceholder") + late final String coinPlaceholderRelative; + @ignore + String get coinPlaceholder => prependIfNeeded(coinPlaceholderRelative); + + // Added some future proof params in case we want to add anything else + // This should provide some buffer in stead of creating assetsV4 etc + @Name("otherStringParam2") + late final String? dummy2; + @Name("otherStringParam3") + late final String? dummy3; + + @ignore + Map get coinIcons => _coinIcons ??= parseCoinAssetsString( + coinIconsString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinIcons; + late final String coinIconsString; + + @ignore + Map get coinImages => _coinImages ??= parseCoinAssetsString( + coinImagesString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinImages; + late final String coinImagesString; + + @ignore + Map get coinSecondaryImages => + _coinSecondaryImages ??= parseCoinAssetsString( + coinSecondaryImagesString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinSecondaryImages; + late final String coinSecondaryImagesString; + + @ignore + Map? get coinCardImages => + _coinCardImages ??= coinCardImagesString == null + ? null + : parseCoinAssetsString( + coinCardImagesString!, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinCardImages; + late final String? coinCardImagesString; + + @ignore + Map? get coinCardFavoritesImages => + _coinCardFavoritesImages ??= coinCardFavoritesImagesString == null + ? null + : parseCoinAssetsString( + coinCardFavoritesImagesString!, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinCardFavoritesImages; + @Name("otherStringParam1") + late final String? coinCardFavoritesImagesString; + + ThemeAssetsV4(); + + factory ThemeAssetsV4.fromJson({ + required Map json, + required String themeId, + }) { + return ThemeAssetsV4() + ..bellNewRelative = "$themeId/assets/${json["bell_new"] as String}" + ..buyRelative = "$themeId/assets/${json["buy"] as String}" + ..exchangeRelative = "$themeId/assets/${json["exchange"] as String}" + ..personaIncognitoRelative = + "$themeId/assets/${json["persona_incognito"] as String}" + ..personaEasyRelative = + "$themeId/assets/${json["persona_easy"] as String}" + ..stackRelative = "$themeId/assets/${json["stack"] as String}" + ..stackIconRelative = "$themeId/assets/${json["stack_icon"] as String}" + ..receiveRelative = "$themeId/assets/${json["receive"] as String}" + ..receivePendingRelative = + "$themeId/assets/${json["receive_pending"] as String}" + ..receiveCancelledRelative = + "$themeId/assets/${json["receive_cancelled"] as String}" + ..sendRelative = "$themeId/assets/${json["send"] as String}" + ..sendPendingRelative = + "$themeId/assets/${json["send_pending"] as String}" + ..sendCancelledRelative = + "$themeId/assets/${json["send_cancelled"] as String}" + ..themeSelectorRelative = + "$themeId/assets/${json["theme_selector"] as String}" + ..themePreviewRelative = + "$themeId/assets/${json["theme_preview"] as String}" + ..txExchangeRelative = "$themeId/assets/${json["tx_exchange"] as String}" + ..txExchangePendingRelative = + "$themeId/assets/${json["tx_exchange_pending"] as String}" + ..txExchangeFailedRelative = + "$themeId/assets/${json["tx_exchange_failed"] as String}" + ..coinPlaceholderRelative = + "$themeId/assets/${json["coin_placeholder"] as String}" + ..coinIconsString = createCoinAssetsString( + "$themeId/assets", + Map.from(json["coins"]["icons"] as Map), + ) + ..coinImagesString = createCoinAssetsString( + "$themeId/assets", + Map.from(json["coins"]["images"] as Map), + ) + ..coinSecondaryImagesString = createCoinAssetsString( + "$themeId/assets", + Map.from(json["coins"]["secondaries"] as Map), + ) + ..coinCardImagesString = json["coins"]["cards"] is Map + ? createCoinAssetsString( + "$themeId/assets", + Map.from(json["coins"]["cards"] as Map), + ) + : null + ..coinCardFavoritesImagesString = json["coins"]["favoriteCards"] is Map + ? createCoinAssetsString( + "$themeId/assets", + Map.from(json["coins"]["favoriteCards"] as Map), + ) + : null + ..loadingGifRelative = json["loading_gif"] is String + ? "$themeId/assets/${json["loading_gif"] as String}" + : null + ..backgroundRelative = json["background"] is String + ? "$themeId/assets/${json["background"] as String}" + : null + ..dummy2 = null + ..dummy3 = null; + } + + static String prependIfNeeded(String relativePath) { + final path = StackFileSystem.themesDir!.path; + + if (relativePath.startsWith(path)) { + return relativePath; + } else { + if (Platform.isIOS) { + const pattern = "/var/mobile/Containers/Data/Application/"; + if (relativePath.startsWith(pattern)) { + final parts = relativePath.split("/Library/themes/"); + if (parts.isNotEmpty) { + return "$path/${parts.last}"; + } + } + } + + return "$path/$relativePath"; + } + } + + static String createCoinAssetsString(String path, Map json) { + final Map map = {}; + for (final entry in json.entries) { + map[entry.key] = "$path/${entry.value as String}"; + } + return jsonEncode(map); + } + + static Map parseCoinAssetsString( + String jsonString, { + required String placeHolder, + }) { + final json = jsonDecode(jsonString) as Map; + final map = Map.from(json); + + final Map result = {}; + + for (final coin in Coin.values) { + result[coin] = map[coin.name] as String? ?? placeHolder; + + result[coin] = prependIfNeeded(result[coin]!); + } + + return result; + } + + @override + String toString() { + return 'ThemeAssetsV4(' + 'bellNew: $bellNew, ' + 'buy: $buy, ' + 'exchange: $exchange, ' + 'personaIncognito: $personaIncognito, ' + 'personaEasy: $personaEasy, ' + 'stack: $stack, ' + 'stackIcon: $stackIcon, ' + 'receive: $receive, ' + 'receivePending: $receivePending, ' + 'receiveCancelled: $receiveCancelled, ' + 'send: $send, ' + 'sendPending: $sendPending, ' + 'sendCancelled: $sendCancelled, ' + 'themeSelector: $themeSelector, ' + 'themePreview: $themePreview, ' + 'txExchange: $txExchange, ' + 'txExchangePending: $txExchangePending, ' + 'txExchangeFailed: $txExchangeFailed, ' + 'loadingGif: $loadingGif, ' + 'background: $background, ' + 'coinPlaceholder: $coinPlaceholder, ' + 'coinIcons: $coinIcons, ' + 'coinImages: $coinImages, ' + 'coinSecondaryImages: $coinSecondaryImages, ' + 'coinCardImages: $coinCardImages' + 'coinCardFavoritesImages: $coinCardFavoritesImages' + ')'; + } +} + abstract class IThemeAssets { String get bellNew; String get buy; diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index 2b1da76c3..c9fce27b3 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:decimal/decimal.dart'; @@ -485,6 +486,7 @@ class StellarWallet extends CoinServiceAPI .execute().onError((error, stackTrace) => throw("Could not fetch transactions") ); + for (OperationResponse response in payments.records!) { // PaymentOperationResponse por; if (response is PaymentOperationResponse) { @@ -493,27 +495,28 @@ class StellarWallet extends CoinServiceAPI Logging.instance.log( "ALL TRANSACTIONS IS ${por.transactionSuccessful}", level: LogLevel.Info); + + Logging.instance.log( + "THIS TX HASH IS ${por.transactionHash}", + level: LogLevel.Info); + SWTransaction.TransactionType type; if (por.sourceAccount == await getAddressSW()) { type = SWTransaction.TransactionType.outgoing; } else { type = SWTransaction.TransactionType.incoming; } - final amount = Amount( - rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(coin.decimals).replaceAll(".", "")), - fractionDigits: coin.decimals, - ); + final amount = Amount( + rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(coin.decimals).replaceAll(".", "")), + fractionDigits: coin.decimals, + ); int fee = 0; int height = 0; - TransactionResponse? transaction = por.transaction; - - Logging.instance.log( - "THIS TRANSACTION IS ${transaction?.hash}", - level: LogLevel.Info); //Query the transaction linked to the payment, // por.transaction returns a null sometimes TransactionResponse tx = await stellarSdk.transactions.transaction(por.transactionHash!); - if (tx.hash != "") { + + if (tx.hash.isNotEmpty) { fee = tx.feeCharged!; height = tx.ledger; } @@ -551,7 +554,7 @@ class StellarWallet extends CoinServiceAPI Tuple2 tuple = Tuple2(theTransaction, address); transactionList.add(tuple); } else if (response is CreateAccountOperationResponse) { - var caor = response; + CreateAccountOperationResponse caor = response; SWTransaction.TransactionType type; if (caor.sourceAccount == await getAddressSW()) { type = SWTransaction.TransactionType.outgoing; @@ -564,10 +567,10 @@ class StellarWallet extends CoinServiceAPI ); int fee = 0; int height = 0; - var transaction = caor.transaction; - if (transaction != null) { - fee = transaction.feeCharged!; - height = transaction.ledger; + TransactionResponse tx = await stellarSdk.transactions.transaction(caor.transactionHash!); + if (tx.hash.isNotEmpty) { + fee = tx.feeCharged!; + height = tx.ledger; } var theTransaction = SWTransaction.Transaction( walletId: walletId, From 58364a6c8818ec066621f164105574d9687bbe9c Mon Sep 17 00:00:00 2001 From: likho Date: Wed, 26 Jul 2023 16:19:40 +0200 Subject: [PATCH 08/16] Update themes, use PUBNET instead of testnet --- assets/default_themes/dark.zip | Bin 659920 -> 659195 bytes assets/default_themes/light.zip | Bin 607924 -> 607086 bytes lib/models/isar/stack_theme.dart | 756 +++++------------- .../coins/stellar/stellar_wallet.dart | 26 +- lib/utilities/default_nodes.dart | 2 +- 5 files changed, 219 insertions(+), 565 deletions(-) diff --git a/assets/default_themes/dark.zip b/assets/default_themes/dark.zip index 9f4e888ef9d1050ec9485dd9c0276f44fa129d1b..2d8b4528620fd78008303fc5e513c3bfa475aa83 100644 GIT binary patch delta 6748 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 P%)tNCT>An(xuX6LV5v%q delta 7535 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 diff --git a/assets/default_themes/light.zip b/assets/default_themes/light.zip index 75f61b69262ba09089de62b29970a3a2d818b857..be20673a962761be17027b9a16c06716d44bc5a7 100644 GIT binary patch delta 4934 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 _accentColorBlue ??= Color( - accentColorBlueInt, - ); + accentColorBlueInt, + ); @ignore Color? _accentColorBlue; late final int accentColorBlueInt; @@ -141,8 +141,8 @@ class StackTheme { @ignore Color get accentColorGreen => _accentColorGreen ??= Color( - accentColorGreenInt, - ); + accentColorGreenInt, + ); @ignore Color? _accentColorGreen; late final int accentColorGreenInt; @@ -151,8 +151,8 @@ class StackTheme { @ignore Color get accentColorYellow => _accentColorYellow ??= Color( - accentColorYellowInt, - ); + accentColorYellowInt, + ); @ignore Color? _accentColorYellow; late final int accentColorYellowInt; @@ -161,8 +161,8 @@ class StackTheme { @ignore Color get accentColorRed => _accentColorRed ??= Color( - accentColorRedInt, - ); + accentColorRedInt, + ); @ignore Color? _accentColorRed; late final int accentColorRedInt; @@ -171,8 +171,8 @@ class StackTheme { @ignore Color get accentColorOrange => _accentColorOrange ??= Color( - accentColorOrangeInt, - ); + accentColorOrangeInt, + ); @ignore Color? _accentColorOrange; late final int accentColorOrangeInt; @@ -181,8 +181,8 @@ class StackTheme { @ignore Color get accentColorDark => _accentColorDark ??= Color( - accentColorDarkInt, - ); + accentColorDarkInt, + ); @ignore Color? _accentColorDark; late final int accentColorDarkInt; @@ -191,8 +191,8 @@ class StackTheme { @ignore Color get shadow => _shadow ??= Color( - shadowInt, - ); + shadowInt, + ); @ignore Color? _shadow; late final int shadowInt; @@ -201,8 +201,8 @@ class StackTheme { @ignore Color get textDark => _textDark ??= Color( - textDarkInt, - ); + textDarkInt, + ); @ignore Color? _textDark; late final int textDarkInt; @@ -211,8 +211,8 @@ class StackTheme { @ignore Color get textDark2 => _textDark2 ??= Color( - textDark2Int, - ); + textDark2Int, + ); @ignore Color? _textDark2; late final int textDark2Int; @@ -221,8 +221,8 @@ class StackTheme { @ignore Color get textDark3 => _textDark3 ??= Color( - textDark3Int, - ); + textDark3Int, + ); @ignore Color? _textDark3; late final int textDark3Int; @@ -231,8 +231,8 @@ class StackTheme { @ignore Color get textSubtitle1 => _textSubtitle1 ??= Color( - textSubtitle1Int, - ); + textSubtitle1Int, + ); @ignore Color? _textSubtitle1; late final int textSubtitle1Int; @@ -241,8 +241,8 @@ class StackTheme { @ignore Color get textSubtitle2 => _textSubtitle2 ??= Color( - textSubtitle2Int, - ); + textSubtitle2Int, + ); @ignore Color? _textSubtitle2; late final int textSubtitle2Int; @@ -251,8 +251,8 @@ class StackTheme { @ignore Color get textSubtitle3 => _textSubtitle3 ??= Color( - textSubtitle3Int, - ); + textSubtitle3Int, + ); @ignore Color? _textSubtitle3; late final int textSubtitle3Int; @@ -261,8 +261,8 @@ class StackTheme { @ignore Color get textSubtitle4 => _textSubtitle4 ??= Color( - textSubtitle4Int, - ); + textSubtitle4Int, + ); @ignore Color? _textSubtitle4; late final int textSubtitle4Int; @@ -271,8 +271,8 @@ class StackTheme { @ignore Color get textSubtitle5 => _textSubtitle5 ??= Color( - textSubtitle5Int, - ); + textSubtitle5Int, + ); @ignore Color? _textSubtitle5; late final int textSubtitle5Int; @@ -281,8 +281,8 @@ class StackTheme { @ignore Color get textSubtitle6 => _textSubtitle6 ??= Color( - textSubtitle6Int, - ); + textSubtitle6Int, + ); @ignore Color? _textSubtitle6; late final int textSubtitle6Int; @@ -291,8 +291,8 @@ class StackTheme { @ignore Color get textWhite => _textWhite ??= Color( - textWhiteInt, - ); + textWhiteInt, + ); @ignore Color? _textWhite; late final int textWhiteInt; @@ -301,8 +301,8 @@ class StackTheme { @ignore Color get textFavoriteCard => _textFavoriteCard ??= Color( - textFavoriteCardInt, - ); + textFavoriteCardInt, + ); @ignore Color? _textFavoriteCard; late final int textFavoriteCardInt; @@ -311,8 +311,8 @@ class StackTheme { @ignore Color get textError => _textError ??= Color( - textErrorInt, - ); + textErrorInt, + ); @ignore Color? _textError; late final int textErrorInt; @@ -321,8 +321,8 @@ class StackTheme { @ignore Color get textRestore => _textRestore ??= Color( - textRestoreInt, - ); + textRestoreInt, + ); @ignore Color? _textRestore; late final int textRestoreInt; @@ -331,8 +331,8 @@ class StackTheme { @ignore Color get buttonBackPrimary => _buttonBackPrimary ??= Color( - buttonBackPrimaryInt, - ); + buttonBackPrimaryInt, + ); @ignore Color? _buttonBackPrimary; late final int buttonBackPrimaryInt; @@ -341,8 +341,8 @@ class StackTheme { @ignore Color get buttonBackSecondary => _buttonBackSecondary ??= Color( - buttonBackSecondaryInt, - ); + buttonBackSecondaryInt, + ); @ignore Color? _buttonBackSecondary; late final int buttonBackSecondaryInt; @@ -351,8 +351,8 @@ class StackTheme { @ignore Color get buttonBackPrimaryDisabled => _buttonBackPrimaryDisabled ??= Color( - buttonBackPrimaryDisabledInt, - ); + buttonBackPrimaryDisabledInt, + ); @ignore Color? _buttonBackPrimaryDisabled; late final int buttonBackPrimaryDisabledInt; @@ -372,8 +372,8 @@ class StackTheme { @ignore Color get buttonBackBorder => _buttonBackBorder ??= Color( - buttonBackBorderInt, - ); + buttonBackBorderInt, + ); @ignore Color? _buttonBackBorder; late final int buttonBackBorderInt; @@ -382,8 +382,8 @@ class StackTheme { @ignore Color get buttonBackBorderDisabled => _buttonBackBorderDisabled ??= Color( - buttonBackBorderDisabledInt, - ); + buttonBackBorderDisabledInt, + ); @ignore Color? _buttonBackBorderDisabled; late final int buttonBackBorderDisabledInt; @@ -392,8 +392,8 @@ class StackTheme { @ignore Color get buttonBackBorderSecondary => _buttonBackBorderSecondary ??= Color( - buttonBackBorderSecondaryInt, - ); + buttonBackBorderSecondaryInt, + ); @ignore Color? _buttonBackBorderSecondary; late final int buttonBackBorderSecondaryInt; @@ -413,8 +413,8 @@ class StackTheme { @ignore Color get numberBackDefault => _numberBackDefault ??= Color( - numberBackDefaultInt, - ); + numberBackDefaultInt, + ); @ignore Color? _numberBackDefault; late final int numberBackDefaultInt; @@ -423,8 +423,8 @@ class StackTheme { @ignore Color get numpadBackDefault => _numpadBackDefault ??= Color( - numpadBackDefaultInt, - ); + numpadBackDefaultInt, + ); @ignore Color? _numpadBackDefault; late final int numpadBackDefaultInt; @@ -433,8 +433,8 @@ class StackTheme { @ignore Color get bottomNavBack => _bottomNavBack ??= Color( - bottomNavBackInt, - ); + bottomNavBackInt, + ); @ignore Color? _bottomNavBack; late final int bottomNavBackInt; @@ -443,8 +443,8 @@ class StackTheme { @ignore Color get buttonTextPrimary => _buttonTextPrimary ??= Color( - buttonTextPrimaryInt, - ); + buttonTextPrimaryInt, + ); @ignore Color? _buttonTextPrimary; late final int buttonTextPrimaryInt; @@ -453,8 +453,8 @@ class StackTheme { @ignore Color get buttonTextSecondary => _buttonTextSecondary ??= Color( - buttonTextSecondaryInt, - ); + buttonTextSecondaryInt, + ); @ignore Color? _buttonTextSecondary; late final int buttonTextSecondaryInt; @@ -463,8 +463,8 @@ class StackTheme { @ignore Color get buttonTextPrimaryDisabled => _buttonTextPrimaryDisabled ??= Color( - buttonTextPrimaryDisabledInt, - ); + buttonTextPrimaryDisabledInt, + ); @ignore Color? _buttonTextPrimaryDisabled; late final int buttonTextPrimaryDisabledInt; @@ -1498,8 +1498,6 @@ class StackTheme { // cheat build runner into adding this at end of property id list in isar @Name("zAssetsV3") late final ThemeAssetsV3? assetsV3; - @Name("zAssetsV4") - late final ThemeAssetsV4? assetsV4; @ignore IThemeAssets get assets => assetsV3 ?? assetsV2 ?? assetsV1!; @@ -1519,24 +1517,18 @@ class StackTheme { ..version = version ..assetsV1 = version == 1 ? ThemeAssets.fromJson( - json: Map.from(json["assets"] as Map), - themeId: json["id"] as String, - ) + json: Map.from(json["assets"] as Map), + themeId: json["id"] as String, + ) : null ..assetsV2 = version == 2 ? ThemeAssetsV2.fromJson( - json: Map.from(json["assets"] as Map), - themeId: json["id"] as String, - ) + json: Map.from(json["assets"] as Map), + themeId: json["id"] as String, + ) : null ..assetsV3 = version >= 3 ? ThemeAssetsV3.fromJson( - json: Map.from(json["assets"] as Map), - themeId: json["id"] as String, - ) - : null - ..assetsV4 = version >= 4 - ? ThemeAssetsV4.fromJson( json: Map.from(json["assets"] as Map), themeId: json["id"] as String, ) @@ -1546,96 +1538,96 @@ class StackTheme { ..brightnessString = json["brightness"] as String ..backgroundInt = parseColor(json["colors"]["background"] as String) ..backgroundAppBarInt = - parseColor(json["colors"]["background_app_bar"] as String) + parseColor(json["colors"]["background_app_bar"] as String) ..gradientBackgroundString = json["colors"]["gradients"] != null ? jsonEncode(json["colors"]["gradients"]) : null ..standardBoxShadowString = - jsonEncode(json["colors"]["box_shadows"]["standard"] as Map) + jsonEncode(json["colors"]["box_shadows"]["standard"] as Map) ..homeViewButtonBarBoxShadowString = - json["colors"]["box_shadows"]["home_view_button_bar"] == null - ? null - : jsonEncode( - json["colors"]["box_shadows"]["home_view_button_bar"] as Map) + json["colors"]["box_shadows"]["home_view_button_bar"] == null + ? null + : jsonEncode( + json["colors"]["box_shadows"]["home_view_button_bar"] as Map) ..coinColorsJsonString = jsonEncode(json["colors"]['coin'] as Map) ..overlayInt = parseColor(json["colors"]["overlay"] as String) ..accentColorBlueInt = - parseColor(json["colors"]["accent_color_blue"] as String) + parseColor(json["colors"]["accent_color_blue"] as String) ..accentColorGreenInt = - parseColor(json["colors"]["accent_color_green"] as String) + parseColor(json["colors"]["accent_color_green"] as String) ..accentColorYellowInt = - parseColor(json["colors"]["accent_color_yellow"] as String) + parseColor(json["colors"]["accent_color_yellow"] as String) ..accentColorRedInt = - parseColor(json["colors"]["accent_color_red"] as String) + parseColor(json["colors"]["accent_color_red"] as String) ..accentColorOrangeInt = - parseColor(json["colors"]["accent_color_orange"] as String) + parseColor(json["colors"]["accent_color_orange"] as String) ..accentColorDarkInt = - parseColor(json["colors"]["accent_color_dark"] as String) + parseColor(json["colors"]["accent_color_dark"] as String) ..shadowInt = parseColor(json["colors"]["shadow"] as String) ..textDarkInt = parseColor(json["colors"]["text_dark_one"] as String) ..textDark2Int = parseColor(json["colors"]["text_dark_two"] as String) ..textDark3Int = parseColor(json["colors"]["text_dark_three"] as String) ..textWhiteInt = parseColor(json["colors"]["text_white"] as String) ..textFavoriteCardInt = - parseColor(json["colors"]["text_favorite"] as String) + parseColor(json["colors"]["text_favorite"] as String) ..textErrorInt = parseColor(json["colors"]["text_error"] as String) ..textRestoreInt = parseColor(json["colors"]["text_restore"] as String) ..buttonBackPrimaryInt = - parseColor(json["colors"]["button_back_primary"] as String) + parseColor(json["colors"]["button_back_primary"] as String) ..buttonBackSecondaryInt = - parseColor(json["colors"]["button_back_secondary"] as String) + parseColor(json["colors"]["button_back_secondary"] as String) ..buttonBackPrimaryDisabledInt = - parseColor(json["colors"]["button_back_primary_disabled"] as String) + parseColor(json["colors"]["button_back_primary_disabled"] as String) ..buttonBackSecondaryDisabledInt = - parseColor(json["colors"]["button_back_secondary_disabled"] as String) + parseColor(json["colors"]["button_back_secondary_disabled"] as String) ..buttonBackBorderInt = - parseColor(json["colors"]["button_back_border"] as String) + parseColor(json["colors"]["button_back_border"] as String) ..buttonBackBorderDisabledInt = - parseColor(json["colors"]["button_back_border_disabled"] as String) + parseColor(json["colors"]["button_back_border_disabled"] as String) ..buttonBackBorderSecondaryInt = - parseColor(json["colors"]["button_back_border_secondary"] as String) + parseColor(json["colors"]["button_back_border_secondary"] as String) ..buttonBackBorderSecondaryDisabledInt = parseColor( json["colors"]["button_back_border_secondary_disabled"] as String) ..numberBackDefaultInt = - parseColor(json["colors"]["number_back_default"] as String) + parseColor(json["colors"]["number_back_default"] as String) ..numpadBackDefaultInt = - parseColor(json["colors"]["numpad_back_default"] as String) + parseColor(json["colors"]["numpad_back_default"] as String) ..bottomNavBackInt = - parseColor(json["colors"]["bottom_nav_back"] as String) + parseColor(json["colors"]["bottom_nav_back"] as String) ..textSubtitle1Int = - parseColor(json["colors"]["text_subtitle_one"] as String) + parseColor(json["colors"]["text_subtitle_one"] as String) ..textSubtitle2Int = - parseColor(json["colors"]["text_subtitle_two"] as String) + parseColor(json["colors"]["text_subtitle_two"] as String) ..textSubtitle3Int = - parseColor(json["colors"]["text_subtitle_three"] as String) + parseColor(json["colors"]["text_subtitle_three"] as String) ..textSubtitle4Int = - parseColor(json["colors"]["text_subtitle_four"] as String) + parseColor(json["colors"]["text_subtitle_four"] as String) ..textSubtitle5Int = - parseColor(json["colors"]["text_subtitle_five"] as String) + parseColor(json["colors"]["text_subtitle_five"] as String) ..textSubtitle6Int = - parseColor(json["colors"]["text_subtitle_six"] as String) + parseColor(json["colors"]["text_subtitle_six"] as String) ..buttonTextPrimaryInt = - parseColor(json["colors"]["button_text_primary"] as String) + parseColor(json["colors"]["button_text_primary"] as String) ..buttonTextSecondaryInt = - parseColor(json["colors"]["button_text_secondary"] as String) + parseColor(json["colors"]["button_text_secondary"] as String) ..buttonTextPrimaryDisabledInt = - parseColor(json["colors"]["button_text_primary_disabled"] as String) + parseColor(json["colors"]["button_text_primary_disabled"] as String) ..buttonTextSecondaryDisabledInt = - parseColor(json["colors"]["button_text_secondary_disabled"] as String) + parseColor(json["colors"]["button_text_secondary_disabled"] as String) ..buttonTextBorderInt = - parseColor(json["colors"]["button_text_border"] as String) + parseColor(json["colors"]["button_text_border"] as String) ..buttonTextDisabledInt = - parseColor(json["colors"]["button_text_disabled"] as String) + parseColor(json["colors"]["button_text_disabled"] as String) ..buttonTextBorderlessInt = - parseColor(json["colors"]["button_text_borderless"] as String) + parseColor(json["colors"]["button_text_borderless"] as String) ..buttonTextBorderlessDisabledInt = parseColor( json["colors"]["button_text_borderless_disabled"] as String) ..numberTextDefaultInt = - parseColor(json["colors"]["number_text_default"] as String) + parseColor(json["colors"]["number_text_default"] as String) ..numpadTextDefaultInt = - parseColor(json["colors"]["numpad_text_default"] as String) + parseColor(json["colors"]["numpad_text_default"] as String) ..bottomNavTextInt = - parseColor(json["colors"]["bottom_nav_text"] as String) + parseColor(json["colors"]["bottom_nav_text"] as String) ..customTextButtonEnabledTextInt = parseColor( json["colors"]["custom_text_button_enabled_text"] as String) ..customTextButtonDisabledTextInt = parseColor( @@ -1643,87 +1635,87 @@ class StackTheme { ..switchBGOnInt = parseColor(json["colors"]["switch_bg_on"] as String) ..switchBGOffInt = parseColor(json["colors"]["switch_bg_off"] as String) ..switchBGDisabledInt = - parseColor(json["colors"]["switch_bg_disabled"] as String) + parseColor(json["colors"]["switch_bg_disabled"] as String) ..switchCircleOnInt = - parseColor(json["colors"]["switch_circle_on"] as String) + parseColor(json["colors"]["switch_circle_on"] as String) ..switchCircleOffInt = - parseColor(json["colors"]["switch_circle_off"] as String) + parseColor(json["colors"]["switch_circle_off"] as String) ..switchCircleDisabledInt = - parseColor(json["colors"]["switch_circle_disabled"] as String) + parseColor(json["colors"]["switch_circle_disabled"] as String) ..stepIndicatorBGCheckInt = - parseColor(json["colors"]["step_indicator_bg_check"] as String) + parseColor(json["colors"]["step_indicator_bg_check"] as String) ..stepIndicatorBGNumberInt = - parseColor(json["colors"]["step_indicator_bg_number"] as String) + parseColor(json["colors"]["step_indicator_bg_number"] as String) ..stepIndicatorBGInactiveInt = - parseColor(json["colors"]["step_indicator_bg_inactive"] as String) + parseColor(json["colors"]["step_indicator_bg_inactive"] as String) ..stepIndicatorBGLinesInt = - parseColor(json["colors"]["step_indicator_bg_lines"] as String) + parseColor(json["colors"]["step_indicator_bg_lines"] as String) ..stepIndicatorBGLinesInactiveInt = parseColor( json["colors"]["step_indicator_bg_lines_inactive"] as String) ..stepIndicatorIconTextInt = - parseColor(json["colors"]["step_indicator_icon_text"] as String) + parseColor(json["colors"]["step_indicator_icon_text"] as String) ..stepIndicatorIconNumberInt = - parseColor(json["colors"]["step_indicator_icon_number"] as String) + parseColor(json["colors"]["step_indicator_icon_number"] as String) ..stepIndicatorIconInactiveInt = - parseColor(json["colors"]["step_indicator_icon_inactive"] as String) + parseColor(json["colors"]["step_indicator_icon_inactive"] as String) ..checkboxBGCheckedInt = - parseColor(json["colors"]["checkbox_bg_checked"] as String) + parseColor(json["colors"]["checkbox_bg_checked"] as String) ..checkboxBorderEmptyInt = - parseColor(json["colors"]["checkbox_border_empty"] as String) + parseColor(json["colors"]["checkbox_border_empty"] as String) ..checkboxBGDisabledInt = - parseColor(json["colors"]["checkbox_bg_disabled"] as String) + parseColor(json["colors"]["checkbox_bg_disabled"] as String) ..checkboxIconCheckedInt = - parseColor(json["colors"]["checkbox_icon_checked"] as String) + parseColor(json["colors"]["checkbox_icon_checked"] as String) ..checkboxIconDisabledInt = - parseColor(json["colors"]["checkbox_icon_disabled"] as String) + parseColor(json["colors"]["checkbox_icon_disabled"] as String) ..checkboxTextLabelInt = - parseColor(json["colors"]["checkbox_text_label"] as String) + parseColor(json["colors"]["checkbox_text_label"] as String) ..snackBarBackSuccessInt = - parseColor(json["colors"]["snack_bar_back_success"] as String) + parseColor(json["colors"]["snack_bar_back_success"] as String) ..snackBarBackErrorInt = - parseColor(json["colors"]["snack_bar_back_error"] as String) + parseColor(json["colors"]["snack_bar_back_error"] as String) ..snackBarBackInfoInt = - parseColor(json["colors"]["snack_bar_back_info"] as String) + parseColor(json["colors"]["snack_bar_back_info"] as String) ..snackBarTextSuccessInt = - parseColor(json["colors"]["snack_bar_text_success"] as String) + parseColor(json["colors"]["snack_bar_text_success"] as String) ..snackBarTextErrorInt = - parseColor(json["colors"]["snack_bar_text_error"] as String) + parseColor(json["colors"]["snack_bar_text_error"] as String) ..snackBarTextInfoInt = - parseColor(json["colors"]["snack_bar_text_info"] as String) + parseColor(json["colors"]["snack_bar_text_info"] as String) ..bottomNavIconBackInt = - parseColor(json["colors"]["bottom_nav_icon_back"] as String) + parseColor(json["colors"]["bottom_nav_icon_back"] as String) ..bottomNavIconIconInt = - parseColor(json["colors"]["bottom_nav_icon_icon"] as String) + parseColor(json["colors"]["bottom_nav_icon_icon"] as String) ..bottomNavIconIconHighlightedInt = parseColor( json["colors"]["bottom_nav_icon_icon_highlighted"] as String) ..topNavIconPrimaryInt = - parseColor(json["colors"]["top_nav_icon_primary"] as String) + parseColor(json["colors"]["top_nav_icon_primary"] as String) ..topNavIconGreenInt = - parseColor(json["colors"]["top_nav_icon_green"] as String) + parseColor(json["colors"]["top_nav_icon_green"] as String) ..topNavIconYellowInt = - parseColor(json["colors"]["top_nav_icon_yellow"] as String) + parseColor(json["colors"]["top_nav_icon_yellow"] as String) ..topNavIconRedInt = - parseColor(json["colors"]["top_nav_icon_red"] as String) + parseColor(json["colors"]["top_nav_icon_red"] as String) ..settingsIconBackInt = - parseColor(json["colors"]["settings_icon_back"] as String) + parseColor(json["colors"]["settings_icon_back"] as String) ..settingsIconIconInt = - parseColor(json["colors"]["settings_icon_icon"] as String) + parseColor(json["colors"]["settings_icon_icon"] as String) ..settingsIconBack2Int = - parseColor(json["colors"]["settings_icon_back_two"] as String) + parseColor(json["colors"]["settings_icon_back_two"] as String) ..settingsIconElementInt = - parseColor(json["colors"]["settings_icon_element"] as String) + parseColor(json["colors"]["settings_icon_element"] as String) ..textFieldActiveBGInt = - parseColor(json["colors"]["text_field_active_bg"] as String) + parseColor(json["colors"]["text_field_active_bg"] as String) ..textFieldDefaultBGInt = - parseColor(json["colors"]["text_field_default_bg"] as String) + parseColor(json["colors"]["text_field_default_bg"] as String) ..textFieldErrorBGInt = - parseColor(json["colors"]["text_field_error_bg"] as String) + parseColor(json["colors"]["text_field_error_bg"] as String) ..textFieldSuccessBGInt = - parseColor(json["colors"]["text_field_success_bg"] as String) + parseColor(json["colors"]["text_field_success_bg"] as String) ..textFieldErrorBorderInt = - parseColor(json["colors"]["text_field_error_border"] as String) + parseColor(json["colors"]["text_field_error_border"] as String) ..textFieldSuccessBorderInt = - parseColor(json["colors"]["text_field_success_border"] as String) + parseColor(json["colors"]["text_field_success_border"] as String) ..textFieldActiveSearchIconLeftInt = parseColor( json["colors"]["text_field_active_search_icon_left"] as String) ..textFieldDefaultSearchIconLeftInt = parseColor( @@ -1733,19 +1725,19 @@ class StackTheme { ..textFieldSuccessSearchIconLeftInt = parseColor( json["colors"]["text_field_success_search_icon_left"] as String) ..textFieldActiveTextInt = - parseColor(json["colors"]["text_field_active_text"] as String) + parseColor(json["colors"]["text_field_active_text"] as String) ..textFieldDefaultTextInt = - parseColor(json["colors"]["text_field_default_text"] as String) + parseColor(json["colors"]["text_field_default_text"] as String) ..textFieldErrorTextInt = - parseColor(json["colors"]["text_field_error_text"] as String) + parseColor(json["colors"]["text_field_error_text"] as String) ..textFieldSuccessTextInt = - parseColor(json["colors"]["text_field_success_text"] as String) + parseColor(json["colors"]["text_field_success_text"] as String) ..textFieldActiveLabelInt = - parseColor(json["colors"]["text_field_active_label"] as String) + parseColor(json["colors"]["text_field_active_label"] as String) ..textFieldErrorLabelInt = - parseColor(json["colors"]["text_field_error_label"] as String) + parseColor(json["colors"]["text_field_error_label"] as String) ..textFieldSuccessLabelInt = - parseColor(json["colors"]["text_field_success_label"] as String) + parseColor(json["colors"]["text_field_success_label"] as String) ..textFieldActiveSearchIconRightInt = parseColor( json["colors"]["text_field_active_search_icon_right"] as String) ..textFieldDefaultSearchIconRightInt = parseColor( @@ -1761,61 +1753,61 @@ class StackTheme { ..settingsItem2ActiveSubInt = parseColor( json["colors"]["settings_item_level_two_active_sub"] as String) ..radioButtonIconBorderInt = - parseColor(json["colors"]["radio_button_icon_border"] as String) + parseColor(json["colors"]["radio_button_icon_border"] as String) ..radioButtonIconBorderDisabledInt = parseColor( json["colors"]["radio_button_icon_border_disabled"] as String) ..radioButtonBorderEnabledInt = - parseColor(json["colors"]["radio_button_border_enabled"] as String) + parseColor(json["colors"]["radio_button_border_enabled"] as String) ..radioButtonBorderDisabledInt = - parseColor(json["colors"]["radio_button_border_disabled"] as String) + parseColor(json["colors"]["radio_button_border_disabled"] as String) ..radioButtonIconCircleInt = - parseColor(json["colors"]["radio_button_icon_circle"] as String) + parseColor(json["colors"]["radio_button_icon_circle"] as String) ..radioButtonIconEnabledInt = - parseColor(json["colors"]["radio_button_icon_enabled"] as String) + parseColor(json["colors"]["radio_button_icon_enabled"] as String) ..radioButtonTextEnabledInt = - parseColor(json["colors"]["radio_button_text_enabled"] as String) + parseColor(json["colors"]["radio_button_text_enabled"] as String) ..radioButtonTextDisabledInt = - parseColor(json["colors"]["radio_button_text_disabled"] as String) + parseColor(json["colors"]["radio_button_text_disabled"] as String) ..radioButtonLabelEnabledInt = - parseColor(json["colors"]["radio_button_label_enabled"] as String) + parseColor(json["colors"]["radio_button_label_enabled"] as String) ..radioButtonLabelDisabledInt = - parseColor(json["colors"]["radio_button_label_disabled"] as String) + parseColor(json["colors"]["radio_button_label_disabled"] as String) ..infoItemBGInt = parseColor(json["colors"]["info_item_bg"] as String) ..infoItemLabelInt = - parseColor(json["colors"]["info_item_label"] as String) + parseColor(json["colors"]["info_item_label"] as String) ..infoItemTextInt = parseColor(json["colors"]["info_item_text"] as String) ..infoItemIconsInt = - parseColor(json["colors"]["info_item_icons"] as String) + parseColor(json["colors"]["info_item_icons"] as String) ..popupBGInt = parseColor(json["colors"]["popup_bg"] as String) ..currencyListItemBGInt = - parseColor(json["colors"]["currency_list_item_bg"] as String) + parseColor(json["colors"]["currency_list_item_bg"] as String) ..stackWalletBGInt = parseColor(json["colors"]["sw_bg"] as String) ..stackWalletMidInt = parseColor(json["colors"]["sw_mid"] as String) ..stackWalletBottomInt = parseColor(json["colors"]["sw_bottom"] as String) ..bottomNavShadowInt = - parseColor(json["colors"]["bottom_nav_shadow"] as String) + parseColor(json["colors"]["bottom_nav_shadow"] as String) ..splashInt = parseColor(json["colors"]["splash"] as String) ..highlightInt = parseColor(json["colors"]["highlight"] as String) ..warningForegroundInt = - parseColor(json["colors"]["warning_foreground"] as String) + parseColor(json["colors"]["warning_foreground"] as String) ..warningBackgroundInt = - parseColor(json["colors"]["warning_background"] as String) + parseColor(json["colors"]["warning_background"] as String) ..loadingOverlayTextColorInt = - parseColor(json["colors"]["loading_overlay_text_color"] as String) + parseColor(json["colors"]["loading_overlay_text_color"] as String) ..myStackContactIconBGInt = - parseColor(json["colors"]["my_stack_contact_icon_bg"] as String) + parseColor(json["colors"]["my_stack_contact_icon_bg"] as String) ..textConfirmTotalAmountInt = - parseColor(json["colors"]["text_confirm_total_amount"] as String) + parseColor(json["colors"]["text_confirm_total_amount"] as String) ..textSelectedWordTableItemInt = - parseColor(json["colors"]["text_selected_word_table_iterm"] as String) + parseColor(json["colors"]["text_selected_word_table_iterm"] as String) ..favoriteStarActiveInt = - parseColor(json["colors"]["favorite_star_active"] as String) + parseColor(json["colors"]["favorite_star_active"] as String) ..favoriteStarInactiveInt = - parseColor(json["colors"]["favorite_star_inactive"] as String) + parseColor(json["colors"]["favorite_star_inactive"] as String) ..rateTypeToggleColorOnInt = - parseColor(json["colors"]["rate_type_toggle_color_on"] as String) + parseColor(json["colors"]["rate_type_toggle_color_on"] as String) ..rateTypeToggleColorOffInt = - parseColor(json["colors"]["rate_type_toggle_color_off"] as String) + parseColor(json["colors"]["rate_type_toggle_color_off"] as String) ..rateTypeToggleDesktopColorOnInt = parseColor( json["colors"]["rate_type_toggle_desktop_color_on"] as String) ..rateTypeToggleDesktopColorOffInt = parseColor( @@ -1823,19 +1815,19 @@ class StackTheme { ..ethTagTextInt = parseColor(json["colors"]["eth_tag_text"] as String) ..ethTagBGInt = parseColor(json["colors"]["eth_tag_bg"] as String) ..ethWalletTagTextInt = - parseColor(json["colors"]["eth_wallet_tag_text"] as String) + parseColor(json["colors"]["eth_wallet_tag_text"] as String) ..ethWalletTagBGInt = - parseColor(json["colors"]["eth_wallet_tag_bg"] as String) + parseColor(json["colors"]["eth_wallet_tag_bg"] as String) ..tokenSummaryTextPrimaryInt = - parseColor(json["colors"]["token_summary_text_primary"] as String) + parseColor(json["colors"]["token_summary_text_primary"] as String) ..tokenSummaryTextSecondaryInt = - parseColor(json["colors"]["token_summary_text_secondary"] as String) + parseColor(json["colors"]["token_summary_text_secondary"] as String) ..tokenSummaryBGInt = - parseColor(json["colors"]["token_summary_bg"] as String) + parseColor(json["colors"]["token_summary_bg"] as String) ..tokenSummaryButtonBGInt = - parseColor(json["colors"]["token_summary_button_bg"] as String) + parseColor(json["colors"]["token_summary_button_bg"] as String) ..tokenSummaryIconInt = - parseColor(json["colors"]["token_summary_icon"] as String); + parseColor(json["colors"]["token_summary_icon"] as String); } /// Grab the int value of the hex color string. @@ -1848,7 +1840,7 @@ class StackTheme { } else { throw ArgumentError( '"$colorHex" and corresponding int ' - 'value "$colorValue" is not a valid color.', + 'value "$colorValue" is not a valid color.', ); } } catch (_) { @@ -1929,7 +1921,6 @@ class ThemeAssets implements IThemeAssets { late final String wownero; late final String namecoin; late final String particl; - late final String stellar; late final String bitcoinImage; late final String bitcoincashImage; late final String dogecoinImage; @@ -1941,7 +1932,6 @@ class ThemeAssets implements IThemeAssets { late final String wowneroImage; late final String namecoinImage; late final String particlImage; - late final String stellarImage; late final String bitcoinImageSecondary; late final String bitcoincashImageSecondary; late final String dogecoinImageSecondary; @@ -1953,7 +1943,6 @@ class ThemeAssets implements IThemeAssets { late final String wowneroImageSecondary; late final String namecoinImageSecondary; late final String particlImageSecondary; - late final String stellarImageSecondary; @override late final String? loadingGif; @override @@ -1999,7 +1988,6 @@ class ThemeAssets implements IThemeAssets { ..wownero = "$themeId/assets/${json["wownero"] as String}" ..namecoin = "$themeId/assets/${json["namecoin"] as String}" ..particl = "$themeId/assets/${json["particl"] as String}" - ..stellar = "$themeId/assets/${json["stellar"] as String}" ..bitcoinImage = "$themeId/assets/${json["bitcoin_image"] as String}" ..bitcoincashImage = "$themeId/assets/${json["bitcoincash_image"] as String}" @@ -2012,7 +2000,6 @@ class ThemeAssets implements IThemeAssets { ..wowneroImage = "$themeId/assets/${json["wownero_image"] as String}" ..namecoinImage = "$themeId/assets/${json["namecoin_image"] as String}" ..particlImage = "$themeId/assets/${json["particl_image"] as String}" - ..stellarImage = "$themeId/assets/${json["stellar_image"] as String}" ..bitcoinImageSecondary = "$themeId/assets/${json["bitcoin_image_secondary"] as String}" ..bitcoincashImageSecondary = @@ -2035,8 +2022,6 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["namecoin_image_secondary"] as String}" ..particlImageSecondary = "$themeId/assets/${json["particl_image_secondary"] as String}" - ..stellarImageSecondary = - "$themeId/assets/${json["stellar_image_secondary"] as String}" ..loadingGif = json["loading_gif"] is String ? "$themeId/assets/${json["loading_gif"] as String}" : null @@ -2093,18 +2078,18 @@ class ThemeAssetsV2 implements IThemeAssets { @ignore Map get coinIcons => _coinIcons ??= parseCoinAssetsString( - coinIconsString, - placeHolder: coinPlaceholder, - ); + coinIconsString, + placeHolder: coinPlaceholder, + ); @ignore Map? _coinIcons; late final String coinIconsString; @ignore Map get coinImages => _coinImages ??= parseCoinAssetsString( - coinImagesString, - placeHolder: coinPlaceholder, - ); + coinImagesString, + placeHolder: coinPlaceholder, + ); @ignore Map? _coinImages; late final String coinImagesString; @@ -2179,9 +2164,9 @@ class ThemeAssetsV2 implements IThemeAssets { } static Map parseCoinAssetsString( - String jsonString, { - required String placeHolder, - }) { + String jsonString, { + required String placeHolder, + }) { final json = jsonDecode(jsonString) as Map; final map = Map.from(json); @@ -2361,345 +2346,6 @@ class ThemeAssetsV3 implements IThemeAssets { @Name("otherStringParam3") late final String? dummy3; - @ignore - Map get coinIcons => _coinIcons ??= parseCoinAssetsString( - coinIconsString, - placeHolder: coinPlaceholder, - ); - @ignore - Map? _coinIcons; - late final String coinIconsString; - - @ignore - Map get coinImages => _coinImages ??= parseCoinAssetsString( - coinImagesString, - placeHolder: coinPlaceholder, - ); - @ignore - Map? _coinImages; - late final String coinImagesString; - - @ignore - Map get coinSecondaryImages => - _coinSecondaryImages ??= parseCoinAssetsString( - coinSecondaryImagesString, - placeHolder: coinPlaceholder, - ); - @ignore - Map? _coinSecondaryImages; - late final String coinSecondaryImagesString; - - @ignore - Map? get coinCardImages => - _coinCardImages ??= coinCardImagesString == null - ? null - : parseCoinAssetsString( - coinCardImagesString!, - placeHolder: coinPlaceholder, - ); - @ignore - Map? _coinCardImages; - late final String? coinCardImagesString; - - @ignore - Map? get coinCardFavoritesImages => - _coinCardFavoritesImages ??= coinCardFavoritesImagesString == null - ? null - : parseCoinAssetsString( - coinCardFavoritesImagesString!, - placeHolder: coinPlaceholder, - ); - @ignore - Map? _coinCardFavoritesImages; - @Name("otherStringParam1") - late final String? coinCardFavoritesImagesString; - - ThemeAssetsV3(); - - factory ThemeAssetsV3.fromJson({ - required Map json, - required String themeId, - }) { - return ThemeAssetsV3() - ..bellNewRelative = "$themeId/assets/${json["bell_new"] as String}" - ..buyRelative = "$themeId/assets/${json["buy"] as String}" - ..exchangeRelative = "$themeId/assets/${json["exchange"] as String}" - ..personaIncognitoRelative = - "$themeId/assets/${json["persona_incognito"] as String}" - ..personaEasyRelative = - "$themeId/assets/${json["persona_easy"] as String}" - ..stackRelative = "$themeId/assets/${json["stack"] as String}" - ..stackIconRelative = "$themeId/assets/${json["stack_icon"] as String}" - ..receiveRelative = "$themeId/assets/${json["receive"] as String}" - ..receivePendingRelative = - "$themeId/assets/${json["receive_pending"] as String}" - ..receiveCancelledRelative = - "$themeId/assets/${json["receive_cancelled"] as String}" - ..sendRelative = "$themeId/assets/${json["send"] as String}" - ..sendPendingRelative = - "$themeId/assets/${json["send_pending"] as String}" - ..sendCancelledRelative = - "$themeId/assets/${json["send_cancelled"] as String}" - ..themeSelectorRelative = - "$themeId/assets/${json["theme_selector"] as String}" - ..themePreviewRelative = - "$themeId/assets/${json["theme_preview"] as String}" - ..txExchangeRelative = "$themeId/assets/${json["tx_exchange"] as String}" - ..txExchangePendingRelative = - "$themeId/assets/${json["tx_exchange_pending"] as String}" - ..txExchangeFailedRelative = - "$themeId/assets/${json["tx_exchange_failed"] as String}" - ..coinPlaceholderRelative = - "$themeId/assets/${json["coin_placeholder"] as String}" - ..coinIconsString = createCoinAssetsString( - "$themeId/assets", - Map.from(json["coins"]["icons"] as Map), - ) - ..coinImagesString = createCoinAssetsString( - "$themeId/assets", - Map.from(json["coins"]["images"] as Map), - ) - ..coinSecondaryImagesString = createCoinAssetsString( - "$themeId/assets", - Map.from(json["coins"]["secondaries"] as Map), - ) - ..coinCardImagesString = json["coins"]["cards"] is Map - ? createCoinAssetsString( - "$themeId/assets", - Map.from(json["coins"]["cards"] as Map), - ) - : null - ..coinCardFavoritesImagesString = json["coins"]["favoriteCards"] is Map - ? createCoinAssetsString( - "$themeId/assets", - Map.from(json["coins"]["favoriteCards"] as Map), - ) - : null - ..loadingGifRelative = json["loading_gif"] is String - ? "$themeId/assets/${json["loading_gif"] as String}" - : null - ..backgroundRelative = json["background"] is String - ? "$themeId/assets/${json["background"] as String}" - : null - ..dummy2 = null - ..dummy3 = null; - } - - static String prependIfNeeded(String relativePath) { - final path = StackFileSystem.themesDir!.path; - - if (relativePath.startsWith(path)) { - return relativePath; - } else { - if (Platform.isIOS) { - const pattern = "/var/mobile/Containers/Data/Application/"; - if (relativePath.startsWith(pattern)) { - final parts = relativePath.split("/Library/themes/"); - if (parts.isNotEmpty) { - return "$path/${parts.last}"; - } - } - } - - return "$path/$relativePath"; - } - } - - static String createCoinAssetsString(String path, Map json) { - final Map map = {}; - for (final entry in json.entries) { - map[entry.key] = "$path/${entry.value as String}"; - } - return jsonEncode(map); - } - - static Map parseCoinAssetsString( - String jsonString, { - required String placeHolder, - }) { - final json = jsonDecode(jsonString) as Map; - final map = Map.from(json); - - final Map result = {}; - - for (final coin in Coin.values) { - result[coin] = map[coin.name] as String? ?? placeHolder; - - result[coin] = prependIfNeeded(result[coin]!); - } - - return result; - } - - @override - String toString() { - return 'ThemeAssetsV3(' - 'bellNew: $bellNew, ' - 'buy: $buy, ' - 'exchange: $exchange, ' - 'personaIncognito: $personaIncognito, ' - 'personaEasy: $personaEasy, ' - 'stack: $stack, ' - 'stackIcon: $stackIcon, ' - 'receive: $receive, ' - 'receivePending: $receivePending, ' - 'receiveCancelled: $receiveCancelled, ' - 'send: $send, ' - 'sendPending: $sendPending, ' - 'sendCancelled: $sendCancelled, ' - 'themeSelector: $themeSelector, ' - 'themePreview: $themePreview, ' - 'txExchange: $txExchange, ' - 'txExchangePending: $txExchangePending, ' - 'txExchangeFailed: $txExchangeFailed, ' - 'loadingGif: $loadingGif, ' - 'background: $background, ' - 'coinPlaceholder: $coinPlaceholder, ' - 'coinIcons: $coinIcons, ' - 'coinImages: $coinImages, ' - 'coinSecondaryImages: $coinSecondaryImages, ' - 'coinCardImages: $coinCardImages' - 'coinCardFavoritesImages: $coinCardFavoritesImages' - ')'; - } -} - -@Embedded(inheritance: false) -class ThemeAssetsV4 implements IThemeAssets { - @Name("bellNew") - late final String bellNewRelative; - @override - @ignore - String get bellNew => prependIfNeeded(bellNewRelative); - - @Name("buy") - late final String buyRelative; - @override - @ignore - String get buy => prependIfNeeded(buyRelative); - - @Name("exchange") - late final String exchangeRelative; - @override - @ignore - String get exchange => prependIfNeeded(exchangeRelative); - - @Name("personaIncognito") - late final String personaIncognitoRelative; - @override - @ignore - String get personaIncognito => prependIfNeeded(personaIncognitoRelative); - - @Name("personaEasy") - late final String personaEasyRelative; - @override - @ignore - String get personaEasy => prependIfNeeded(personaEasyRelative); - - @Name("stack") - late final String stackRelative; - @override - @ignore - String get stack => prependIfNeeded(stackRelative); - - @Name("stackIcon") - late final String stackIconRelative; - @override - @ignore - String get stackIcon => prependIfNeeded(stackIconRelative); - - @Name("receive") - late final String receiveRelative; - @override - @ignore - String get receive => prependIfNeeded(receiveRelative); - - @Name("receivePending") - late final String receivePendingRelative; - @override - @ignore - String get receivePending => prependIfNeeded(receivePendingRelative); - - @Name("receiveCancelled") - late final String receiveCancelledRelative; - @override - @ignore - String get receiveCancelled => prependIfNeeded(receiveCancelledRelative); - - @Name("send") - late final String sendRelative; - @override - @ignore - String get send => prependIfNeeded(sendRelative); - - @Name("sendPending") - late final String sendPendingRelative; - @override - @ignore - String get sendPending => prependIfNeeded(sendPendingRelative); - - @Name("sendCancelled") - late final String sendCancelledRelative; - @override - @ignore - String get sendCancelled => prependIfNeeded(sendCancelledRelative); - - @Name("themeSelector") - late final String themeSelectorRelative; - @override - @ignore - String get themeSelector => prependIfNeeded(themeSelectorRelative); - - @Name("themePreview") - late final String themePreviewRelative; - @override - @ignore - String get themePreview => prependIfNeeded(themePreviewRelative); - - @Name("txExchange") - late final String txExchangeRelative; - @override - @ignore - String get txExchange => prependIfNeeded(txExchangeRelative); - - @Name("txExchangePending") - late final String txExchangePendingRelative; - @override - @ignore - String get txExchangePending => prependIfNeeded(txExchangePendingRelative); - - @Name("txExchangeFailed") - late final String txExchangeFailedRelative; - @override - @ignore - String get txExchangeFailed => prependIfNeeded(txExchangeFailedRelative); - - @Name("loadingGif") - late final String? loadingGifRelative; - @override - @ignore - String? get loadingGif => - loadingGifRelative != null ? prependIfNeeded(loadingGifRelative!) : null; - - @Name("background") - late final String? backgroundRelative; - @override - @ignore - String? get background => - backgroundRelative != null ? prependIfNeeded(backgroundRelative!) : null; - - @Name("coinPlaceholder") - late final String coinPlaceholderRelative; - @ignore - String get coinPlaceholder => prependIfNeeded(coinPlaceholderRelative); - - // Added some future proof params in case we want to add anything else - // This should provide some buffer in stead of creating assetsV4 etc - @Name("otherStringParam2") - late final String? dummy2; - @Name("otherStringParam3") - late final String? dummy3; - @ignore Map get coinIcons => _coinIcons ??= parseCoinAssetsString( coinIconsString, @@ -2753,13 +2399,13 @@ class ThemeAssetsV4 implements IThemeAssets { @Name("otherStringParam1") late final String? coinCardFavoritesImagesString; - ThemeAssetsV4(); + ThemeAssetsV3(); - factory ThemeAssetsV4.fromJson({ + factory ThemeAssetsV3.fromJson({ required Map json, required String themeId, }) { - return ThemeAssetsV4() + return ThemeAssetsV3() ..bellNewRelative = "$themeId/assets/${json["bell_new"] as String}" ..buyRelative = "$themeId/assets/${json["buy"] as String}" ..exchangeRelative = "$themeId/assets/${json["exchange"] as String}" @@ -2872,7 +2518,7 @@ class ThemeAssetsV4 implements IThemeAssets { @override String toString() { - return 'ThemeAssetsV4(' + return 'ThemeAssetsV3(' 'bellNew: $bellNew, ' 'buy: $buy, ' 'exchange: $exchange, ' @@ -2925,4 +2571,4 @@ abstract class IThemeAssets { String? get loadingGif; String? get background; -} +} \ No newline at end of file diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index c9fce27b3..c1fe652b6 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -61,7 +61,7 @@ class StellarWallet extends CoinServiceAPI late final TransactionNotificationTracker txTracker; late SecureStorageInterface _secureStore; - final StellarSDK stellarSdk = StellarSDK.TESTNET; + final StellarSDK stellarSdk = StellarSDK.PUBLIC; @override bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); @@ -199,7 +199,7 @@ class StellarWallet extends CoinServiceAPI .build() ).build(); } - transaction.sign(senderKeyPair, Network.TESTNET); + transaction.sign(senderKeyPair, Network.PUBLIC); try { SubmitTransactionResponse response = await stellarSdk.submitTransaction(transaction); @@ -256,9 +256,11 @@ class StellarWallet extends CoinServiceAPI } @override - Future exit() { - // TODO: implement exit - throw UnimplementedError(); + Future exit() async { + _hasCalledExit = true; + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); } NodeModel? _xlmNode; @@ -427,7 +429,13 @@ class StellarWallet extends CoinServiceAPI } @override - Future recoverFromMnemonic({required String mnemonic, String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height}) async { + Future recoverFromMnemonic({ + required String mnemonic, + String? mnemonicPassphrase, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height + }) async { if ((await mnemonicString) != null || (await this.mnemonicPassphrase) != null) { throw Exception("Attempted to overwrite mnemonic on restore!"); @@ -472,7 +480,8 @@ class StellarWallet extends CoinServiceAPI .order(RequestBuilderOrder.DESC) .limit(1) .execute() - .then((value) => value.records!.first.sequence); + .then((value) => value.records!.first.sequence).onError((error, stackTrace) => + throw("Error getting chain height")); await updateCachedChainHeight(height); } @@ -484,8 +493,7 @@ class StellarWallet extends CoinServiceAPI Page payments = await stellarSdk.payments .forAccount(await getAddressSW()).order(RequestBuilderOrder.DESC) .execute().onError((error, stackTrace) => - throw("Could not fetch transactions") - ); + throw("Could not fetch transactions")); for (OperationResponse response in payments.records!) { // PaymentOperationResponse por; diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 02bb4ff29..6c8bf08e3 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -182,7 +182,7 @@ abstract class DefaultNodes { isDown: false); static NodeModel get stellar => NodeModel( - host: "https://horizon-testnet.stellar.org", + host: "https://horizon.stellar.org", port: 443, name: defaultName, id: _nodeId(Coin.stellar), From 7061c3c40beb571883a5cb399ce6b11dfcaab5df Mon Sep 17 00:00:00 2001 From: likho Date: Thu, 27 Jul 2023 13:09:56 +0200 Subject: [PATCH 09/16] Add testnet to coin list --- assets/default_themes/dark.zip | Bin 659195 -> 659201 bytes assets/default_themes/light.zip | Bin 607086 -> 607092 bytes .../add_edit_node_view.dart | 2 ++ .../manage_nodes_views/node_details_view.dart | 1 + lib/services/coins/coin_service.dart | 9 ++++++++ .../coins/stellar/stellar_wallet.dart | 15 +++++++++++-- lib/themes/color_theme.dart | 3 ++- lib/themes/stack_colors.dart | 1 + lib/utilities/address_utils.dart | 2 ++ lib/utilities/amount/amount_unit.dart | 1 + lib/utilities/block_explorers.dart | 2 ++ lib/utilities/constants.dart | 5 +++++ lib/utilities/default_nodes.dart | 16 +++++++++++++ lib/utilities/enums/coin_enum.dart | 21 ++++++++++++++++++ .../enums/derive_path_type_enum.dart | 1 + lib/widgets/node_card.dart | 1 + lib/widgets/node_options_sheet.dart | 1 + 17 files changed, 78 insertions(+), 3 deletions(-) diff --git a/assets/default_themes/dark.zip b/assets/default_themes/dark.zip index 2d8b4528620fd78008303fc5e513c3bfa475aa83..fe11a5463f8ae50dad10e51a2f664cbdcc55a25c 100644 GIT binary patch delta 4166 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 delta 4140 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 Ud(*x{9N-0|;f0F;8ynmI0ID=$Y5)KL diff --git a/assets/default_themes/light.zip b/assets/default_themes/light.zip index be20673a962761be17027b9a16c06716d44bc5a7..1453d6ba0606fba827105cb4a76af4df5b6b78dc 100644 GIT binary patch delta 3975 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 delta 3977 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 diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index e7ce8b505..56b008093 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -197,6 +197,7 @@ class _AddEditNodeViewState extends ConsumerState { case Coin.nano: case Coin.banano: case Coin.stellar: + case Coin.stellarTestnet: throw UnimplementedError(); //TODO: check network/node } @@ -739,6 +740,7 @@ class _NodeFormState extends ConsumerState { case Coin.banano: case Coin.eCash: case Coin.stellar: + case Coin.stellarTestnet: return false; case Coin.ethereum: diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index 819b9f5d7..59ff1efad 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -173,6 +173,7 @@ class _NodeDetailsViewState extends ConsumerState { case Coin.nano: case Coin.banano: case Coin.stellar: + case Coin.stellarTestnet: throw UnimplementedError(); //TODO: check network/node } diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index ffb97a0a7..5aa6e382d 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -285,6 +285,15 @@ abstract class CoinServiceAPI { cachedClient: cachedClient, tracker: tracker, ); + + case Coin.stellarTestnet: + return StellarWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + secureStore: secureStorageInterface, + tracker: tracker, + ); } } diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index c1fe652b6..1dd8eae77 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -41,6 +41,9 @@ const int MINIMUM_CONFIRMATIONS = 1; class StellarWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlInterface { + late StellarSDK stellarSdk; + late Network stellarNetwork; + StellarWallet({ required String walletId, required String walletName, @@ -56,12 +59,20 @@ class StellarWallet extends CoinServiceAPI _secureStore = secureStore; initCache(walletId, coin); initWalletDB(mockableOverride: mockableOverride); + + if (coin.name == "stellarTestnet") { + stellarSdk = StellarSDK.TESTNET; + stellarNetwork = Network.TESTNET; + } else { + stellarSdk = StellarSDK.PUBLIC; + stellarNetwork = Network.PUBLIC; + } } late final TransactionNotificationTracker txTracker; late SecureStorageInterface _secureStore; - final StellarSDK stellarSdk = StellarSDK.PUBLIC; + // final StellarSDK stellarSdk = StellarSDK.PUBLIC; @override bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); @@ -199,7 +210,7 @@ class StellarWallet extends CoinServiceAPI .build() ).build(); } - transaction.sign(senderKeyPair, Network.PUBLIC); + transaction.sign(senderKeyPair, stellarNetwork); try { SubmitTransactionResponse response = await stellarSdk.submitTransaction(transaction); diff --git a/lib/themes/color_theme.dart b/lib/themes/color_theme.dart index 87020ada7..c78d35c11 100644 --- a/lib/themes/color_theme.dart +++ b/lib/themes/color_theme.dart @@ -28,7 +28,7 @@ class CoinThemeColorDefault { Color get namecoin => const Color(0xFF91B1E1); Color get wownero => const Color(0xFFED80C1); Color get particl => const Color(0xFF8175BD); - Color get stellar => const Color(0xFFE8E8E8); // TODO: find color + Color get stellar => const Color(0xFF6600FF); Color get nano => const Color(0xFF209CE9); Color get banano => const Color(0xFFFBDD11); @@ -64,6 +64,7 @@ class CoinThemeColorDefault { case Coin.particl: return particl; case Coin.stellar: + case Coin.stellarTestnet: return stellar; case Coin.nano: return nano; diff --git a/lib/themes/stack_colors.dart b/lib/themes/stack_colors.dart index f653b7eac..5dbd55fe1 100644 --- a/lib/themes/stack_colors.dart +++ b/lib/themes/stack_colors.dart @@ -1708,6 +1708,7 @@ class StackColors extends ThemeExtension { case Coin.particl: return _coin.particl; case Coin.stellar: + case Coin.stellarTestnet: return _coin.stellar; case Coin.nano: return _coin.nano; diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index c2e759524..8766d706a 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -141,6 +141,8 @@ class AddressUtils { return Address.validateAddress(address, firoTestNetwork); case Coin.dogecoinTestNet: return Address.validateAddress(address, dogecointestnet); + case Coin.stellarTestnet: + return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address); } } diff --git a/lib/utilities/amount/amount_unit.dart b/lib/utilities/amount/amount_unit.dart index 37939f1cc..ccdd4e095 100644 --- a/lib/utilities/amount/amount_unit.dart +++ b/lib/utilities/amount/amount_unit.dart @@ -51,6 +51,7 @@ enum AmountUnit { case Coin.eCash: case Coin.epicCash: case Coin.stellar: // TODO: check if this is correct + case Coin.stellarTestnet: return AmountUnit.values.sublist(0, 4); case Coin.monero: diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index ae27fae02..68c23479d 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -60,6 +60,8 @@ Uri getDefaultBlockExplorerUrlFor({ return Uri.parse("https://www.nanolooker.com/block/$txid"); case Coin.banano: return Uri.parse("https://www.bananolooker.com/block/$txid"); + case Coin.stellarTestnet: + return Uri.parse("https://horizon-testnet.stellar.org/transactions/$txid"); } } diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 665ae3169..3e6f814a2 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -100,6 +100,7 @@ abstract class Constants { return _satsPerCoinECash; case Coin.stellar: + case Coin.stellarTestnet: return _satsPerCoinStellar; } } @@ -140,6 +141,7 @@ abstract class Constants { return _decimalPlacesECash; case Coin.stellar: + case Coin.stellarTestnet: return _decimalPlacesStellar; } } @@ -164,6 +166,7 @@ abstract class Constants { case Coin.particl: case Coin.nano: case Coin.stellar: + case Coin.stellarTestnet: values.addAll([24, 12]); break; case Coin.banano: @@ -225,6 +228,7 @@ abstract class Constants { return 1; case Coin.stellar: + case Coin.stellarTestnet: return 5; } } @@ -254,6 +258,7 @@ abstract class Constants { case Coin.nano: case Coin.banano: case Coin.stellar: + case Coin.stellarTestnet: return 24; case Coin.monero: diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 6c8bf08e3..9a6a0d914 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -34,6 +34,7 @@ abstract class DefaultNodes { bitcoincashTestnet, dogecoinTestnet, firoTestnet, + stellarTestnet, ]; static NodeModel get bitcoin => NodeModel( @@ -275,6 +276,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get stellarTestnet => NodeModel( + host: "https://horizon-testnet.stellar.org/", + port: 50022, + name: defaultName, + id: _nodeId(Coin.stellarTestnet), + useSSL: true, + enabled: true, + coinName: Coin.stellarTestnet.name, + isFailover: true, + isDown: false, + ); + static NodeModel getNodeFor(Coin coin) { switch (coin) { case Coin.bitcoin: @@ -336,6 +349,9 @@ abstract class DefaultNodes { case Coin.dogecoinTestNet: return dogecoinTestnet; + + case Coin.stellarTestnet: + return stellarTestnet; } } } diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index e66822738..677d474e7 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -58,6 +58,7 @@ enum Coin { dogecoinTestNet, firoTestNet, litecoinTestNet, + stellarTestnet, } final int kTestNetCoinCount = 4; // Util.isDesktop ? 5 : 4; @@ -106,6 +107,8 @@ extension CoinExt on Coin { return "tFiro"; case Coin.dogecoinTestNet: return "tDogecoin"; + case Coin.stellarTestnet: + return "tStellar"; } } @@ -151,6 +154,8 @@ extension CoinExt on Coin { return "tFIRO"; case Coin.dogecoinTestNet: return "tDOGE"; + case Coin.stellarTestnet: + return "tXLM"; } } @@ -197,6 +202,8 @@ extension CoinExt on Coin { return "firo"; case Coin.dogecoinTestNet: return "dogecoin"; + case Coin.stellarTestnet: + return "stellar"; } } @@ -224,6 +231,7 @@ extension CoinExt on Coin { case Coin.nano: case Coin.banano: case Coin.stellar: + case Coin.stellarTestnet: return false; } } @@ -252,6 +260,7 @@ extension CoinExt on Coin { case Coin.nano: case Coin.banano: case Coin.stellar: + case Coin.stellarTestnet: return false; } } @@ -280,6 +289,7 @@ extension CoinExt on Coin { case Coin.litecoinTestNet: case Coin.bitcoincashTestnet: case Coin.firoTestNet: + case Coin.stellarTestnet: return true; } } @@ -317,6 +327,9 @@ extension CoinExt on Coin { case Coin.firoTestNet: return Coin.firo; + + case Coin.stellarTestnet: + return Coin.stellar; } } @@ -358,6 +371,7 @@ extension CoinExt on Coin { return particl.MINIMUM_CONFIRMATIONS; case Coin.stellar: + case Coin.stellarTestnet: return xlm.MINIMUM_CONFIRMATIONS; case Coin.wownero: @@ -467,6 +481,11 @@ Coin coinFromPrettyName(String name) { case "banano": return Coin.banano; + case "Stellar Testnet": + case "stellarTestnet": + case "tStellar": + return Coin.stellarTestnet; + default: throw ArgumentError.value( name, @@ -518,6 +537,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.nano; case "ban": return Coin.banano; + case "txlm": + return Coin.stellarTestnet; default: throw ArgumentError.value( ticker, "name", "No Coin enum value with that ticker"); diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart index 1dbd3f79b..07785a5ee 100644 --- a/lib/utilities/enums/derive_path_type_enum.dart +++ b/lib/utilities/enums/derive_path_type_enum.dart @@ -50,6 +50,7 @@ extension DerivePathTypeExt on DerivePathType { case Coin.nano: case Coin.banano: case Coin.stellar: + case Coin.stellarTestnet: throw UnsupportedError( "$coin does not use bitcoin style derivation paths"); } diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index b906320d3..57482119c 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -195,6 +195,7 @@ class _NodeCardState extends ConsumerState { case Coin.nano: case Coin.banano: case Coin.stellar: + case Coin.stellarTestnet: throw UnimplementedError(); //TODO: check network/node } diff --git a/lib/widgets/node_options_sheet.dart b/lib/widgets/node_options_sheet.dart index f8b788fed..b55f818a2 100644 --- a/lib/widgets/node_options_sheet.dart +++ b/lib/widgets/node_options_sheet.dart @@ -178,6 +178,7 @@ class NodeOptionsSheet extends ConsumerWidget { case Coin.nano: case Coin.banano: case Coin.stellar: + case Coin.stellarTestnet: throw UnimplementedError(); //TODO: check network/node } From e8b91c52877025b0c65737b36c631697e4435d9a Mon Sep 17 00:00:00 2001 From: likho Date: Thu, 27 Jul 2023 13:20:19 +0200 Subject: [PATCH 10/16] Update testnet block explorer --- lib/utilities/block_explorers.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index 68c23479d..831332320 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -61,7 +61,7 @@ Uri getDefaultBlockExplorerUrlFor({ case Coin.banano: return Uri.parse("https://www.bananolooker.com/block/$txid"); case Coin.stellarTestnet: - return Uri.parse("https://horizon-testnet.stellar.org/transactions/$txid"); + return Uri.parse("https://testnet.stellarchain.io/transactions/$txid"); } } From a21781a33b60dafe5f052aa04186a52e45fd720f Mon Sep 17 00:00:00 2001 From: likho Date: Thu, 27 Jul 2023 14:54:42 +0200 Subject: [PATCH 11/16] Update price tests to include Stellar --- test/price_test.dart | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/test/price_test.dart b/test/price_test.dart index f611c5450..6741d5445 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -28,7 +28,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,bitcoin-cash" - ",namecoin,wownero,ethereum,particl,nano,banano&order=market_cap_desc&per_page=50" + ",namecoin,wownero,ethereum,particl,nano,banano,stellar&order=market_cap_desc&per_page=50" "&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -113,19 +113,21 @@ void main() { 'Coin.namecoin: [0, 0.0], ' 'Coin.nano: [0, 0.0], ' 'Coin.particl: [0, 0.0], ' + 'Coin.stellar: [0, 0.0], ' 'Coin.wownero: [0, 0.0], ' 'Coin.bitcoinTestNet: [0, 0.0], ' 'Coin.bitcoincashTestnet: [0, 0.0], ' 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0]' + 'Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.stellarTestnet: [0, 0.0]' '}', ); verify(client.get( Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false", ), headers: {'Content-Type': 'application/json'})).called(1); @@ -140,7 +142,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&" "ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -219,23 +221,20 @@ void main() { '{' 'Coin.bitcoin: [1, 0.0], ' 'Coin.monero: [0.00717236, -0.77656], ' - 'Coin.banano: [0, 0.0], ' - 'Coin.bitcoincash: [0, 0.0], ' + 'Coin.banano: [0, 0.0], Coin.bitcoincash: [0, 0.0], ' 'Coin.dogecoin: [0.00000315, -2.68533], ' 'Coin.eCash: [0, 0.0], ' - 'Coin.epicCash: [0.00002803, 7.27524], ' - 'Coin.ethereum: [0, 0.0], ' + 'Coin.epicCash: [0.00002803, 7.27524], Coin.ethereum: [0, 0.0], ' 'Coin.firo: [0.0001096, -0.89304], ' 'Coin.litecoin: [0, 0.0], ' 'Coin.namecoin: [0, 0.0], ' - 'Coin.nano: [0, 0.0], ' - 'Coin.particl: [0, 0.0], ' + 'Coin.nano: [0, 0.0], Coin.particl: [0, 0.0], Coin.stellar: [0, 0.0], ' 'Coin.wownero: [0, 0.0], ' 'Coin.bitcoinTestNet: [0, 0.0], ' - 'Coin.bitcoincashTestnet: [0, 0.0], ' - 'Coin.dogecoinTestNet: [0, 0.0], ' + 'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0]' + 'Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.stellarTestnet: [0, 0.0]' '}', ); @@ -244,7 +243,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: {'Content-Type': 'application/json'})).called(1); @@ -258,7 +257,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -331,8 +330,7 @@ void main() { expect( price.toString(), '{' - 'Coin.bitcoin: [0, 0.0], ' - 'Coin.monero: [0, 0.0], ' + 'Coin.bitcoin: [0, 0.0], Coin.monero: [0, 0.0], ' 'Coin.banano: [0, 0.0], ' 'Coin.bitcoincash: [0, 0.0], ' 'Coin.dogecoin: [0, 0.0], ' @@ -344,12 +342,14 @@ void main() { 'Coin.namecoin: [0, 0.0], ' 'Coin.nano: [0, 0.0], ' 'Coin.particl: [0, 0.0], ' + 'Coin.stellar: [0, 0.0], ' 'Coin.wownero: [0, 0.0], ' 'Coin.bitcoinTestNet: [0, 0.0], ' 'Coin.bitcoincashTestnet: [0, 0.0], ' 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0]' + 'Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.stellarTestnet: [0, 0.0]' '}', ); }); @@ -361,7 +361,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -389,12 +389,14 @@ void main() { 'Coin.namecoin: [0, 0.0], ' 'Coin.nano: [0, 0.0], ' 'Coin.particl: [0, 0.0], ' + 'Coin.stellar: [0, 0.0], ' 'Coin.wownero: [0, 0.0], ' 'Coin.bitcoinTestNet: [0, 0.0], ' 'Coin.bitcoincashTestnet: [0, 0.0], ' 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0]' + 'Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.stellarTestnet: [0, 0.0]' '}', ); }); From c73e41b2cb3e98fa227cdcfcc0b5146a384816b2 Mon Sep 17 00:00:00 2001 From: likho Date: Thu, 27 Jul 2023 15:28:33 +0200 Subject: [PATCH 12/16] Resolved conflicts with flutterlibmonero --- .gitmodules | 2 +- crypto_plugins/flutter_libmonero | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 crypto_plugins/flutter_libmonero diff --git a/.gitmodules b/.gitmodules index 7474c8a54..bd9a5c271 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/cypherstack/flutter_libepiccash.git [submodule "crypto_plugins/flutter_libmonero"] path = crypto_plugins/flutter_libmonero - url = https://github.com/cypherstack/flutter_libmonero.git + url = git@github.com:cypherstack/flutter_libmonero.git [submodule "crypto_plugins/flutter_liblelantus"] path = crypto_plugins/flutter_liblelantus url = https://github.com/cypherstack/flutter_liblelantus.git diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero new file mode 160000 index 000000000..407425c9f --- /dev/null +++ b/crypto_plugins/flutter_libmonero @@ -0,0 +1 @@ +Subproject commit 407425c9fcf7a30c81f1345246c7225bc18b5cd5 From 3b5cea8ee1f622a2bc8668afb668938084ef9613 Mon Sep 17 00:00:00 2001 From: likho Date: Thu, 27 Jul 2023 15:34:08 +0200 Subject: [PATCH 13/16] Fix submodule url for libmonero --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index bd9a5c271..7474c8a54 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/cypherstack/flutter_libepiccash.git [submodule "crypto_plugins/flutter_libmonero"] path = crypto_plugins/flutter_libmonero - url = git@github.com:cypherstack/flutter_libmonero.git + url = https://github.com/cypherstack/flutter_libmonero.git [submodule "crypto_plugins/flutter_liblelantus"] path = crypto_plugins/flutter_liblelantus url = https://github.com/cypherstack/flutter_liblelantus.git From 526025f0f0730cb2ee0483bcc689b690826e4eb6 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 27 Jul 2023 07:59:50 -0600 Subject: [PATCH 14/16] ensure refs are pointing to main --- crypto_plugins/flutter_libepiccash | 2 +- crypto_plugins/flutter_liblelantus | 2 +- crypto_plugins/flutter_libmonero | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index cd12741de..f677dec0b 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit cd12741de19e4faef39a23b7d543a2452524990a +Subproject commit f677dec0b34d3f9fe8fce2bc8ff5c508c3f3bb9a diff --git a/crypto_plugins/flutter_liblelantus b/crypto_plugins/flutter_liblelantus index ec3cf5e8e..9cd241b5e 160000 --- a/crypto_plugins/flutter_liblelantus +++ b/crypto_plugins/flutter_liblelantus @@ -1 +1 @@ -Subproject commit ec3cf5e8e1b90e006188aa8c323d4cd52dbfa9b9 +Subproject commit 9cd241b5ea142e21c01dd7639b42603281c43287 diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 407425c9f..e48952185 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 407425c9fcf7a30c81f1345246c7225bc18b5cd5 +Subproject commit e48952185556a10f182184fd572bcb04365f5831 From 6f2ddf74235778bec46ff69d4f5e4e1a43fbea26 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 27 Jul 2023 08:11:20 -0600 Subject: [PATCH 15/16] fix: auto format code and remove electrumx coin control --- .../coins/stellar/stellar_wallet.dart | 313 +++++++++--------- pubspec.lock | 60 +--- 2 files changed, 158 insertions(+), 215 deletions(-) diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index 1dd8eae77..c4c92373f 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -1,30 +1,17 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:decimal/decimal.dart'; import 'package:http/http.dart' as http; import 'package:isar/isar.dart'; -import 'package:stackwallet/models/balance.dart' as SWBalance; -import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart' as SWTransaction; -import 'package:stackwallet/models/isar/models/blockchain_data/address.dart' as SWAddress; -import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; -import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart'; -import 'package:stackwallet/services/coins/coin_service.dart'; -import 'package:stackwallet/services/mixins/coin_control_interface.dart'; -import 'package:stackwallet/utilities/amount/amount.dart'; -import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:tuple/tuple.dart'; - import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/balance.dart' as SWBalance; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart' + as SWAddress; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart' + as SWTransaction; +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; import 'package:stackwallet/models/node_model.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/default_nodes.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; @@ -33,14 +20,20 @@ import 'package:stackwallet/services/mixins/wallet_cache.dart'; import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; - +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart'; +import 'package:tuple/tuple.dart'; const int MINIMUM_CONFIRMATIONS = 1; -class StellarWallet extends CoinServiceAPI - with WalletCache, WalletDB, CoinControlInterface { - +class StellarWallet extends CoinServiceAPI with WalletCache, WalletDB { late StellarSDK stellarSdk; late Network stellarNetwork; @@ -116,7 +109,7 @@ class StellarWallet extends CoinServiceAPI // then periodically check _networkAliveTimer = Timer.periodic( Constants.networkAliveTimerDuration, - (_) async { + (_) async { _periodicPingCheck(); }, ); @@ -165,68 +158,68 @@ class StellarWallet extends CoinServiceAPI Coin get coin => _coin; late Coin _coin; - Future accoutExists(String accountId) async { + Future _accountExists(String accountId) async { bool exists = false; try { - AccountResponse receiverAccount = await stellarSdk.accounts - .account(accountId); + AccountResponse receiverAccount = + await stellarSdk.accounts.account(accountId); if (receiverAccount.accountId != "") { exists = true; } - } catch(e, s) { - Logging.instance.log("Error getting account ${e.toString()} - ${s.toString()}", level: LogLevel.Error); + } catch (e, s) { + Logging.instance.log( + "Error getting account ${e.toString()} - ${s.toString()}", + level: LogLevel.Error); } return exists; } + @override Future confirmSend({required Map txData}) async { - final secretSeed = await _secureStore.read( - key: '${_walletId}_secretSeed' - ); + final secretSeed = await _secureStore.read(key: '${_walletId}_secretSeed'); KeyPair senderKeyPair = KeyPair.fromSecretSeed(secretSeed!); - AccountResponse sender = await stellarSdk.accounts.account(senderKeyPair.accountId); + AccountResponse sender = + await stellarSdk.accounts.account(senderKeyPair.accountId); final amountToSend = txData['recipientAmt'] as Amount; //First check if account exists, can be skipped, but if the account does not exist, // the transaction fee will be charged when the transaction fails. - bool validAccount = await accoutExists(txData['address'] as String); + bool validAccount = await _accountExists(txData['address'] as String); Transaction transaction; if (!validAccount) { //Fund the account, user must ensure account is correct CreateAccountOperationBuilder createAccBuilder = - CreateAccountOperationBuilder( - txData['address'] as String, amountToSend.decimal.toString() - ); + CreateAccountOperationBuilder( + txData['address'] as String, amountToSend.decimal.toString()); transaction = TransactionBuilder(sender) .addOperation(createAccBuilder.build()) .build(); } else { transaction = TransactionBuilder(sender) - .addOperation(PaymentOperationBuilder( - txData['address'] as String, Asset.NATIVE, - amountToSend.decimal.toString()) - .build() - ).build(); + .addOperation(PaymentOperationBuilder(txData['address'] as String, + Asset.NATIVE, amountToSend.decimal.toString()) + .build()) + .build(); } transaction.sign(senderKeyPair, stellarNetwork); try { - SubmitTransactionResponse response = await stellarSdk.submitTransaction(transaction); + SubmitTransactionResponse response = + await stellarSdk.submitTransaction(transaction); if (!response.success) { - throw("Unable to send transaction"); + throw ("Unable to send transaction"); } return response.hash!; - } catch(e, s) { + } catch (e, s) { Logging.instance.log("Error sending TX $e - $s", level: LogLevel.Error); rethrow; } - } - Future get _currentReceivingAddress => - db.getAddresses(walletId) + Future get _currentReceivingAddress => db + .getAddresses(walletId) .filter() .typeEqualTo(SWAddress.AddressType.unknown) .and() @@ -242,7 +235,8 @@ class StellarWallet extends CoinServiceAPI // final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); final nodeURI = Uri.parse(getCurrentNode().host); final httpClient = http.Client(); - FeeStatsResponse fsp = await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); + FeeStatsResponse fsp = + await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); return int.parse(fsp.lastLedgerBaseFee); } @@ -279,8 +273,11 @@ class StellarWallet extends CoinServiceAPI NodeModel getCurrentNode() { if (_xlmNode != null) { return _xlmNode!; - } else if (NodeService(secureStorageInterface: _secureStore).getPrimaryNodeFor(coin: coin) != null) { - return NodeService(secureStorageInterface: _secureStore).getPrimaryNodeFor(coin: coin)!; + } else if (NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) != + null) { + return NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin)!; } else { return DefaultNodes.getNodeFor(coin); } @@ -291,9 +288,9 @@ class StellarWallet extends CoinServiceAPI // final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); final nodeURI = Uri.parse(getCurrentNode().host); - final httpClient = http.Client(); - FeeStatsResponse fsp = await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); + FeeStatsResponse fsp = + await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); return FeeObject( numberOfBlocksFast: 0, @@ -301,12 +298,12 @@ class StellarWallet extends CoinServiceAPI numberOfBlocksSlow: 0, fast: int.parse(fsp.lastLedgerBaseFee) * 100, medium: int.parse(fsp.lastLedgerBaseFee) * 50, - slow: int.parse(fsp.lastLedgerBaseFee) * 10 - ); + slow: int.parse(fsp.lastLedgerBaseFee) * 10); } @override - Future fullRescan(int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) async { + Future fullRescan( + int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) async { await _prefs.init(); await updateTransactions(); await updateChainHeight(); @@ -338,25 +335,16 @@ class StellarWallet extends CoinServiceAPI await _prefs.init(); String mnemonic = await Wallet.generate24WordsMnemonic(); - await _secureStore.write( - key: '${_walletId}_mnemonic', - value: mnemonic - ); - await _secureStore.write( - key: '${_walletId}_mnemonicPassphrase', - value: "" - ); + await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); + await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); Wallet wallet = await Wallet.from(mnemonic); KeyPair keyPair = await wallet.getKeyPair(index: 0); String address = keyPair.accountId; - String secretSeed = keyPair.secretSeed; //This will be required for sending a tx + String secretSeed = + keyPair.secretSeed; //This will be required for sending a tx - - await _secureStore.write( - key: '${_walletId}_secretSeed', - value: secretSeed - ); + await _secureStore.write(key: '${_walletId}_secretSeed', value: secretSeed); final swAddress = SWAddress.Address( walletId: walletId, @@ -365,21 +353,16 @@ class StellarWallet extends CoinServiceAPI derivationIndex: 0, derivationPath: null, type: SWAddress.AddressType.unknown, // TODO: set type - subType: SWAddress.AddressSubType.unknown - ); - + subType: SWAddress.AddressSubType.unknown); + await db.putAddress(swAddress); - - await Future.wait([ - updateCachedId(walletId), - updateCachedIsFavorite(false) - ]); + + await Future.wait( + [updateCachedId(walletId), updateCachedIsFavorite(false)]); } Future getAddressSW() async { - var mnemonic = await _secureStore.read( - key: '${_walletId}_mnemonic' - ); + var mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); Wallet wallet = await Wallet.from(mnemonic!); KeyPair keyPair = await wallet.getKeyPair(index: 0); @@ -400,16 +383,22 @@ class StellarWallet extends CoinServiceAPI Future get maxFee => throw UnimplementedError(); @override - Future> get mnemonic => mnemonicString.then((value) => value!.split(" ")); + Future> get mnemonic => + mnemonicString.then((value) => value!.split(" ")); @override - Future get mnemonicPassphrase => _secureStore.read(key: '${_walletId}_mnemonicPassphrase'); + Future get mnemonicPassphrase => + _secureStore.read(key: '${_walletId}_mnemonicPassphrase'); @override - Future get mnemonicString => _secureStore.read(key: '${_walletId}_mnemonic'); + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); @override - Future> prepareSend({required String address, required Amount amount, Map? args}) async { + Future> prepareSend( + {required String address, + required Amount amount, + Map? args}) async { try { final feeRate = args?["feeRate"]; var fee = 1000; @@ -440,13 +429,12 @@ class StellarWallet extends CoinServiceAPI } @override - Future recoverFromMnemonic({ - required String mnemonic, - String? mnemonicPassphrase, - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height - }) async { + Future recoverFromMnemonic( + {required String mnemonic, + String? mnemonicPassphrase, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height}) async { if ((await mnemonicString) != null || (await this.mnemonicPassphrase) != null) { throw Exception("Attempted to overwrite mnemonic on restore!"); @@ -463,10 +451,7 @@ class StellarWallet extends CoinServiceAPI key: '${_walletId}_mnemonicPassphrase', value: mnemonicPassphrase ?? "", ); - await _secureStore.write( - key: '${_walletId}_secretSeed', - value: secretSeed - ); + await _secureStore.write(key: '${_walletId}_secretSeed', value: secretSeed); final swAddress = SWAddress.Address( walletId: walletId, @@ -475,15 +460,12 @@ class StellarWallet extends CoinServiceAPI derivationIndex: 0, derivationPath: null, type: SWAddress.AddressType.unknown, // TODO: set type - subType: SWAddress.AddressSubType.unknown - ); + subType: SWAddress.AddressSubType.unknown); await db.putAddress(swAddress); - await Future.wait([ - updateCachedId(walletId), - updateCachedIsFavorite(false) - ]); + await Future.wait( + [updateCachedId(walletId), updateCachedIsFavorite(false)]); } Future updateChainHeight() async { @@ -491,20 +473,22 @@ class StellarWallet extends CoinServiceAPI .order(RequestBuilderOrder.DESC) .limit(1) .execute() - .then((value) => value.records!.first.sequence).onError((error, stackTrace) => - throw("Error getting chain height")); + .then((value) => value.records!.first.sequence) + .onError((error, stackTrace) => throw ("Error getting chain height")); await updateCachedChainHeight(height); } Future updateTransactions() async { - try { - List> transactionList = []; + List> + transactionList = []; Page payments = await stellarSdk.payments - .forAccount(await getAddressSW()).order(RequestBuilderOrder.DESC) - .execute().onError((error, stackTrace) => - throw("Could not fetch transactions")); + .forAccount(await getAddressSW()) + .order(RequestBuilderOrder.DESC) + .execute() + .onError( + (error, stackTrace) => throw ("Could not fetch transactions")); for (OperationResponse response in payments.records!) { // PaymentOperationResponse por; @@ -515,8 +499,7 @@ class StellarWallet extends CoinServiceAPI "ALL TRANSACTIONS IS ${por.transactionSuccessful}", level: LogLevel.Info); - Logging.instance.log( - "THIS TX HASH IS ${por.transactionHash}", + Logging.instance.log("THIS TX HASH IS ${por.transactionHash}", level: LogLevel.Info); SWTransaction.TransactionType type; @@ -526,14 +509,18 @@ class StellarWallet extends CoinServiceAPI type = SWTransaction.TransactionType.incoming; } final amount = Amount( - rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(coin.decimals).replaceAll(".", "")), - fractionDigits: coin.decimals, - ); + rawValue: BigInt.parse(float + .parse(por.amount!) + .toStringAsFixed(coin.decimals) + .replaceAll(".", "")), + fractionDigits: coin.decimals, + ); int fee = 0; int height = 0; //Query the transaction linked to the payment, // por.transaction returns a null sometimes - TransactionResponse tx = await stellarSdk.transactions.transaction(por.transactionHash!); + TransactionResponse tx = + await stellarSdk.transactions.transaction(por.transactionHash!); if (tx.hash.isNotEmpty) { fee = tx.feeCharged!; @@ -542,7 +529,8 @@ class StellarWallet extends CoinServiceAPI var theTransaction = SWTransaction.Transaction( walletId: walletId, txid: por.transactionHash!, - timestamp: DateTime.parse(por.createdAt!).millisecondsSinceEpoch ~/ 1000, + timestamp: + DateTime.parse(por.createdAt!).millisecondsSinceEpoch ~/ 1000, type: type, subType: SWTransaction.TransactionSubType.none, amount: 0, @@ -559,18 +547,20 @@ class StellarWallet extends CoinServiceAPI numberOfMessages: null, ); SWAddress.Address? receivingAddress = await _currentReceivingAddress; - SWAddress.Address address = type == SWTransaction.TransactionType.incoming - ? receivingAddress! - : SWAddress.Address( - walletId: walletId, - value: por.sourceAccount!, - publicKey: KeyPair.fromAccountId(por.sourceAccount!).publicKey, - derivationIndex: 0, - derivationPath: null, - type: SWAddress.AddressType.unknown, // TODO: set type - subType: SWAddress.AddressSubType.unknown - ); - Tuple2 tuple = Tuple2(theTransaction, address); + SWAddress.Address address = + type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( + walletId: walletId, + value: por.sourceAccount!, + publicKey: + KeyPair.fromAccountId(por.sourceAccount!).publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown); + Tuple2 tuple = + Tuple2(theTransaction, address); transactionList.add(tuple); } else if (response is CreateAccountOperationResponse) { CreateAccountOperationResponse caor = response; @@ -581,12 +571,16 @@ class StellarWallet extends CoinServiceAPI type = SWTransaction.TransactionType.incoming; } final amount = Amount( - rawValue: BigInt.parse(float.parse(caor.startingBalance!).toStringAsFixed(coin.decimals).replaceAll(".", "")), + rawValue: BigInt.parse(float + .parse(caor.startingBalance!) + .toStringAsFixed(coin.decimals) + .replaceAll(".", "")), fractionDigits: coin.decimals, ); int fee = 0; int height = 0; - TransactionResponse tx = await stellarSdk.transactions.transaction(caor.transactionHash!); + TransactionResponse tx = + await stellarSdk.transactions.transaction(caor.transactionHash!); if (tx.hash.isNotEmpty) { fee = tx.feeCharged!; height = tx.ledger; @@ -594,7 +588,8 @@ class StellarWallet extends CoinServiceAPI var theTransaction = SWTransaction.Transaction( walletId: walletId, txid: caor.transactionHash!, - timestamp: DateTime.parse(caor.createdAt!).millisecondsSinceEpoch ~/ 1000, + timestamp: + DateTime.parse(caor.createdAt!).millisecondsSinceEpoch ~/ 1000, type: type, subType: SWTransaction.TransactionSubType.none, amount: 0, @@ -611,17 +606,20 @@ class StellarWallet extends CoinServiceAPI numberOfMessages: null, ); SWAddress.Address? receivingAddress = await _currentReceivingAddress; - SWAddress.Address address = type == SWTransaction.TransactionType.incoming - ? receivingAddress! - : SWAddress.Address( - walletId: walletId, - value: caor.sourceAccount!, - publicKey: KeyPair.fromAccountId(caor.sourceAccount!).publicKey, - derivationIndex: 0, - derivationPath: null, - type: SWAddress.AddressType.unknown, // TODO: set type - subType: SWAddress.AddressSubType.unknown); - Tuple2 tuple = Tuple2(theTransaction, address); + SWAddress.Address address = + type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( + walletId: walletId, + value: caor.sourceAccount!, + publicKey: + KeyPair.fromAccountId(caor.sourceAccount!).publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown); + Tuple2 tuple = + Tuple2(theTransaction, address); transactionList.add(tuple); } } @@ -631,12 +629,12 @@ class StellarWallet extends CoinServiceAPI "Exception rethrown from updateTransactions(): $e\n$s", level: LogLevel.Error); } - } Future updateBalance() async { try { - AccountResponse accountResponse = await stellarSdk.accounts.account(await getAddressSW()); + AccountResponse accountResponse = + await stellarSdk.accounts.account(await getAddressSW()); for (Balance balance in accountResponse.balances) { switch (balance.assetType) { case Asset.TYPE_NATIVE: @@ -662,9 +660,9 @@ class StellarWallet extends CoinServiceAPI await updateCachedBalance(_balance!); } } - } catch(e, s) { + } catch (e, s) { Logging.instance.log( - "ERROR GETTING BALANCE $e\$s", + "ERROR GETTING BALANCE $e\n$s", level: LogLevel.Info, ); } @@ -747,12 +745,13 @@ class StellarWallet extends CoinServiceAPI } @override - Future> get transactions => db.getTransactions(walletId).findAll(); + Future> get transactions => + db.getTransactions(walletId).findAll(); @override Future updateNode(bool shouldRefresh) async { _xlmNode = NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); if (shouldRefresh) { @@ -784,14 +783,14 @@ class StellarWallet extends CoinServiceAPI ); final address = txData["address"] is String - ? await db.getAddress(walletId, txData["address"] as String) - : null; + ? await db.getAddress(walletId, txData["address"] as String) + : null; await db.addNewTransactionData( - [ - Tuple2(transaction, address), - ], - walletId, + [ + Tuple2(transaction, address), + ], + walletId, ); } @@ -807,4 +806,4 @@ class StellarWallet extends CoinServiceAPI @override String get walletId => _walletId; late String _walletId; -} \ No newline at end of file +} diff --git a/pubspec.lock b/pubspec.lock index 2987cff3e..cfdf7a710 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -956,7 +956,7 @@ packages: path: "crypto_plugins/flutter_liblelantus" relative: true source: path - version: "0.0.1" + version: "0.0.2" lints: dependency: transitive description: @@ -1365,62 +1365,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" - shared_preferences: - dependency: transitive - description: - name: shared_preferences - sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 - url: "https://pub.dev" - source: hosted - version: "2.2.0" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4 - url: "https://pub.dev" - source: hosted - version: "2.3.2" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d - url: "https://pub.dev" - source: hosted - version: "2.3.0" shelf: dependency: transitive description: @@ -1927,4 +1871,4 @@ packages: version: "1.0.0" sdks: dart: ">=3.0.2 <4.0.0" - flutter: ">=3.10.0" + flutter: ">=3.10.3" From c178ed425d5e46779f7595e32b8ebe9a20ae4331 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 27 Jul 2023 08:12:12 -0600 Subject: [PATCH 16/16] fix: ???? --- macos/Flutter/GeneratedPluginRegistrant.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b741c3c0a..b044b4b00 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,9 @@ import FlutterMacOS import Foundation import connectivity_plus +import cw_monero +import cw_shared_external +import cw_wownero import desktop_drop import device_info_plus import devicelocale @@ -13,10 +16,10 @@ import flutter_libepiccash import flutter_local_notifications import flutter_secure_storage_macos import isar_flutter_libs +import lelantus import package_info_plus import path_provider_foundation import share_plus -import shared_preferences_foundation import stack_wallet_backup import url_launcher_macos import wakelock_macos @@ -24,6 +27,9 @@ import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) + CwSharedExternalPlugin.register(with: registry.registrar(forPlugin: "CwSharedExternalPlugin")) + CwWowneroPlugin.register(with: registry.registrar(forPlugin: "CwWowneroPlugin")) DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) @@ -31,10 +37,10 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) + LelantusPlugin.register(with: registry.registrar(forPlugin: "LelantusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) - SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) StackWalletBackupPlugin.register(with: registry.registrar(forPlugin: "StackWalletBackupPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))