diff --git a/.gitignore b/.gitignore
index 2629f97d1..3ffacac45 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,8 @@ lib/generated_plugin_registrant.dart
test/services/coins/bitcoin/bitcoin_wallet_test_parameters.dart
test/services/coins/firo/firo_wallet_test_parameters.dart
test/services/coins/dogecoin/dogecoin_wallet_test_parameters.dart
+test/services/coins/namecoin/namecoin_wallet_test_parameters.dart
+test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart
/integration_test/private.dart
# Exceptions to above rules.
diff --git a/assets/images/bitcoincash.png b/assets/images/bitcoincash.png
new file mode 100644
index 000000000..18552e02e
Binary files /dev/null and b/assets/images/bitcoincash.png differ
diff --git a/assets/svg/coin_icons/Bitcoincash.svg b/assets/svg/coin_icons/Bitcoincash.svg
new file mode 100644
index 000000000..4e700f9e0
--- /dev/null
+++ b/assets/svg/coin_icons/Bitcoincash.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Namecoin.svg b/assets/svg/coin_icons/Namecoin.svg
new file mode 100644
index 000000000..2cda6aaf0
--- /dev/null
+++ b/assets/svg/coin_icons/Namecoin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
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 0d1ce7ad8..0cb47b7ff 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
@@ -113,8 +113,10 @@ class _AddEditNodeViewState extends ConsumerState {
break;
case Coin.bitcoin:
+ case Coin.bitcoincash:
case Coin.dogecoin:
case Coin.firo:
+ case Coin.namecoin:
case Coin.bitcoinTestNet:
case Coin.firoTestNet:
case Coin.dogecoinTestNet:
@@ -527,6 +529,8 @@ class _NodeFormState extends ConsumerState {
case Coin.bitcoin:
case Coin.dogecoin:
case Coin.firo:
+ case Coin.namecoin:
+ case Coin.bitcoincash:
case Coin.bitcoinTestNet:
case Coin.firoTestNet:
case Coin.dogecoinTestNet:
diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart
index e1ea02abe..da3bdfed0 100644
--- a/lib/services/coins/bitcoin/bitcoin_wallet.dart
+++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart
@@ -2250,7 +2250,7 @@ class BitcoinWallet extends CoinServiceAPI {
batches[batchNumber] = {};
}
final scripthash = _convertToScriptHash(allAddresses[i], _network);
- final id = const Uuid().v1();
+ final id = Logger.isTestEnv ? "$i" : const Uuid().v1();
requestIdToAddressMap[id] = allAddresses[i];
batches[batchNumber]!.addAll({
id: [scripthash]
diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart
new file mode 100644
index 000000000..aa83ea4a9
--- /dev/null
+++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart
@@ -0,0 +1,3111 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:bech32/bech32.dart';
+import 'package:bip32/bip32.dart' as bip32;
+import 'package:bip39/bip39.dart' as bip39;
+import 'package:bitcoindart/bitcoindart.dart';
+import 'package:bs58check/bs58check.dart' as bs58check;
+import 'package:crypto/crypto.dart';
+import 'package:decimal/decimal.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:http/http.dart';
+import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
+import 'package:stackwallet/electrumx_rpc/electrumx.dart';
+import 'package:stackwallet/hive/db.dart';
+import 'package:stackwallet/models/models.dart' as models;
+import 'package:stackwallet/models/paymint/fee_object_model.dart';
+import 'package:stackwallet/models/paymint/transactions_model.dart';
+import 'package:stackwallet/models/paymint/utxo_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/refresh_percent_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/node_service.dart';
+import 'package:stackwallet/services/notifications_api.dart';
+import 'package:stackwallet/services/price.dart';
+import 'package:stackwallet/services/transaction_notification_tracker.dart';
+import 'package:stackwallet/utilities/assets.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/format.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/prefs.dart';
+import 'package:tuple/tuple.dart';
+import 'package:uuid/uuid.dart';
+import 'package:bitbox/bitbox.dart' as Bitbox;
+
+const int MINIMUM_CONFIRMATIONS = 3;
+const int DUST_LIMIT = 1000000;
+
+const String GENESIS_HASH_MAINNET =
+ "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
+const String GENESIS_HASH_TESTNET =
+ "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
+
+enum DerivePathType { bip44 }
+
+bip32.BIP32 getBip32Node(int chain, int index, String mnemonic,
+ NetworkType network, DerivePathType derivePathType) {
+ final root = getBip32Root(mnemonic, network);
+
+ final node = getBip32NodeFromRoot(chain, index, root, derivePathType);
+ return node;
+}
+
+/// wrapper for compute()
+bip32.BIP32 getBip32NodeWrapper(
+ Tuple5 args,
+) {
+ return getBip32Node(
+ args.item1,
+ args.item2,
+ args.item3,
+ args.item4,
+ args.item5,
+ );
+}
+
+bip32.BIP32 getBip32NodeFromRoot(
+ int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) {
+ String coinType;
+ switch (root.network.wif) {
+ case 0x80: // bch mainnet wif
+ coinType = "145"; // bch mainnet
+ break;
+ case 0xef: // bch testnet wif
+ coinType = "1"; // bch testnet
+ break;
+ default:
+ throw Exception("Invalid Bitcoincash network type used!");
+ }
+ switch (derivePathType) {
+ case DerivePathType.bip44:
+ return root.derivePath("m/44'/$coinType'/0'/$chain/$index");
+ default:
+ throw Exception("DerivePathType must not be null.");
+ }
+}
+
+/// wrapper for compute()
+bip32.BIP32 getBip32NodeFromRootWrapper(
+ Tuple4 args,
+) {
+ return getBip32NodeFromRoot(
+ args.item1,
+ args.item2,
+ args.item3,
+ args.item4,
+ );
+}
+
+bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) {
+ final seed = bip39.mnemonicToSeed(mnemonic);
+ final networkType = bip32.NetworkType(
+ wif: network.wif,
+ bip32: bip32.Bip32Type(
+ public: network.bip32.public,
+ private: network.bip32.private,
+ ),
+ );
+
+ final root = bip32.BIP32.fromSeed(seed, networkType);
+ return root;
+}
+
+/// wrapper for compute()
+bip32.BIP32 getBip32RootWrapper(Tuple2 args) {
+ return getBip32Root(args.item1, args.item2);
+}
+
+class BitcoinCashWallet extends CoinServiceAPI {
+ static const integrationTestFlag =
+ bool.fromEnvironment("IS_INTEGRATION_TEST");
+ final _prefs = Prefs.instance;
+
+ Timer? timer;
+ late Coin _coin;
+
+ late final TransactionNotificationTracker txTracker;
+
+ NetworkType get _network {
+ switch (coin) {
+ case Coin.bitcoincash:
+ return bitcoincash;
+ case Coin.bitcoincashTestnet:
+ return bitcoincashtestnet;
+ default:
+ throw Exception("Bitcoincash network type not set!");
+ }
+ }
+
+ List outputsList = [];
+
+ @override
+ Coin get coin => _coin;
+
+ @override
+ Future> get allOwnAddresses =>
+ _allOwnAddresses ??= _fetchAllOwnAddresses();
+ Future>? _allOwnAddresses;
+
+ Future? _utxoData;
+ Future get utxoData => _utxoData ??= _fetchUtxoData();
+
+ @override
+ Future> get unspentOutputs async =>
+ (await utxoData).unspentOutputArray;
+
+ @override
+ Future get availableBalance async {
+ final data = await utxoData;
+ return Format.satoshisToAmount(
+ data.satoshiBalance - data.satoshiBalanceUnconfirmed);
+ }
+
+ @override
+ Future get pendingBalance async {
+ final data = await utxoData;
+ return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed);
+ }
+
+ @override
+ Future get balanceMinusMaxFee async =>
+ (await availableBalance) -
+ (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin))
+ .toDecimal();
+
+ @override
+ Future get totalBalance async {
+ if (!isActive) {
+ final totalBalance = DB.instance
+ .get(boxName: walletId, key: 'totalBalance') as int?;
+ if (totalBalance == null) {
+ final data = await utxoData;
+ return Format.satoshisToAmount(data.satoshiBalance);
+ } else {
+ return Format.satoshisToAmount(totalBalance);
+ }
+ }
+ final data = await utxoData;
+ return Format.satoshisToAmount(data.satoshiBalance);
+ }
+
+ @override
+ Future get currentReceivingAddress =>
+ _currentReceivingAddressP2PKH ??=
+ _getCurrentAddressForChain(0, DerivePathType.bip44);
+
+ Future? _currentReceivingAddressP2PKH;
+
+ @override
+ Future exit() async {
+ _hasCalledExit = true;
+ timer?.cancel();
+ timer = null;
+ stopNetworkAlivePinging();
+ }
+
+ bool _hasCalledExit = false;
+
+ @override
+ bool get hasCalledExit => _hasCalledExit;
+
+ @override
+ Future get fees => _feeObject ??= _getFees();
+ Future? _feeObject;
+
+ @override
+ Future get maxFee async {
+ final fee = (await fees).fast;
+ final satsFee =
+ Format.satoshisToAmount(fee) * Decimal.fromInt(Constants.satsPerCoin);
+ return satsFee.floor().toBigInt().toInt();
+ }
+
+ @override
+ Future> get mnemonic => _getMnemonicList();
+
+ Future get chainHeight async {
+ try {
+ final result = await _electrumXClient.getBlockHeadTip();
+ return result["height"] as int;
+ } catch (e, s) {
+ Logging.instance.log("Exception caught in chainHeight: $e\n$s",
+ level: LogLevel.Error);
+ return -1;
+ }
+ }
+
+ Future get storedChainHeight async {
+ final storedHeight = DB.instance
+ .get(boxName: walletId, key: "storedChainHeight") as int?;
+ return storedHeight ?? 0;
+ }
+
+ Future updateStoredChainHeight({required int newHeight}) async {
+ DB.instance.put(
+ boxName: walletId, key: "storedChainHeight", value: newHeight);
+ }
+
+ DerivePathType addressType({required String address}) {
+ Uint8List? decodeBase58;
+ Segwit? decodeBech32;
+ try {
+ decodeBase58 = bs58check.decode(address);
+ } catch (err) {
+ // Base58check decode fail
+ }
+ if (decodeBase58 != null) {
+ if (decodeBase58[0] == _network.pubKeyHash) {
+ // P2PKH
+ return DerivePathType.bip44;
+ }
+ throw ArgumentError('Invalid version or Network mismatch');
+ } else {
+ try {
+ decodeBech32 = segwit.decode(address);
+ } catch (err) {
+ // Bech32 decode fail
+ }
+ if (_network.bech32 != decodeBech32!.hrp) {
+ throw ArgumentError('Invalid prefix or Network mismatch');
+ }
+ if (decodeBech32.version != 0) {
+ throw ArgumentError('Invalid address version');
+ }
+ }
+ throw ArgumentError('$address has no matching Script');
+ }
+
+ bool longMutex = false;
+
+ @override
+ Future recoverFromMnemonic({
+ required String mnemonic,
+ required int maxUnusedAddressGap,
+ required int maxNumberOfIndexesToCheck,
+ required int height,
+ }) async {
+ longMutex = true;
+ final start = DateTime.now();
+ try {
+ Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag",
+ level: LogLevel.Info);
+ if (!integrationTestFlag) {
+ final features = await electrumXClient.getServerFeatures();
+ Logging.instance.log("features: $features", level: LogLevel.Info);
+ switch (coin) {
+ case Coin.bitcoincash:
+ if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
+ throw Exception("genesis hash does not match main net!");
+ }
+ break;
+ case Coin.bitcoincashTestnet:
+ if (features['genesis_hash'] != GENESIS_HASH_TESTNET) {
+ throw Exception("genesis hash does not match test net!");
+ }
+ break;
+ default:
+ throw Exception(
+ "Attempted to generate a BitcoinCashWallet using a non bch coin type: ${coin.name}");
+ }
+ }
+ // check to make sure we aren't overwriting a mnemonic
+ // this should never fail
+ if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) {
+ longMutex = false;
+ throw Exception("Attempted to overwrite mnemonic on restore!");
+ }
+ await _secureStore.write(
+ key: '${_walletId}_mnemonic', value: mnemonic.trim());
+ await _recoverWalletFromBIP32SeedPhrase(
+ mnemonic: mnemonic.trim(),
+ maxUnusedAddressGap: maxUnusedAddressGap,
+ maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
+ );
+ } catch (e, s) {
+ Logging.instance.log(
+ "Exception rethrown from recoverFromMnemonic(): $e\n$s",
+ level: LogLevel.Error);
+ longMutex = false;
+ rethrow;
+ }
+ longMutex = false;
+
+ final end = DateTime.now();
+ Logging.instance.log(
+ "$walletName recovery time: ${end.difference(start).inMilliseconds} millis",
+ level: LogLevel.Info);
+ }
+
+ Future _recoverWalletFromBIP32SeedPhrase({
+ required String mnemonic,
+ int maxUnusedAddressGap = 20,
+ int maxNumberOfIndexesToCheck = 1000,
+ }) async {
+ longMutex = true;
+
+ Map> p2pkhReceiveDerivations = {};
+ Map> p2pkhChangeDerivations = {};
+
+ final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network));
+
+ List p2pkhReceiveAddressArray = [];
+ int p2pkhReceiveIndex = -1;
+
+ List p2pkhChangeAddressArray = [];
+ int p2pkhChangeIndex = -1;
+
+ // The gap limit will be capped at [maxUnusedAddressGap]
+ int receivingGapCounter = 0;
+ int changeGapCounter = 0;
+
+ // actual size is 12 due to p2pkh so 12x1
+ const txCountBatchSize = 12;
+
+ try {
+ // receiving addresses
+ Logging.instance
+ .log("checking receiving addresses...", level: LogLevel.Info);
+ for (int index = 0;
+ index < maxNumberOfIndexesToCheck &&
+ receivingGapCounter < maxUnusedAddressGap;
+ index += txCountBatchSize) {
+ Logging.instance.log(
+ "index: $index, \t receivingGapCounter: $receivingGapCounter",
+ level: LogLevel.Info);
+
+ final receivingP2pkhID = "k_$index";
+ Map txCountCallArgs = {};
+ final Map receivingNodes = {};
+
+ for (int j = 0; j < txCountBatchSize; j++) {
+ // bip44 / P2PKH
+ final node44 = await compute(
+ getBip32NodeFromRootWrapper,
+ Tuple4(
+ 0,
+ index + j,
+ root,
+ DerivePathType.bip44,
+ ),
+ );
+ final p2pkhReceiveAddress = P2PKH(
+ data: PaymentData(pubkey: node44.publicKey),
+ network: _network)
+ .data
+ .address!;
+ receivingNodes.addAll({
+ "${receivingP2pkhID}_$j": {
+ "node": node44,
+ "address": p2pkhReceiveAddress,
+ }
+ });
+ txCountCallArgs.addAll({
+ "${receivingP2pkhID}_$j": p2pkhReceiveAddress,
+ });
+ }
+
+ // get address tx counts
+ final counts = await _getBatchTxCount(addresses: txCountCallArgs);
+
+ // check and add appropriate addresses
+ for (int k = 0; k < txCountBatchSize; k++) {
+ int p2pkhTxCount = counts["${receivingP2pkhID}_$k"]!;
+ if (p2pkhTxCount > 0) {
+ final node = receivingNodes["${receivingP2pkhID}_$k"];
+ // add address to array
+ p2pkhReceiveAddressArray.add(node["address"] as String);
+ // set current index
+ p2pkhReceiveIndex = index + k;
+ // reset counter
+ receivingGapCounter = 0;
+ // add info to derivations
+ p2pkhReceiveDerivations[node["address"] as String] = {
+ "pubKey": Format.uint8listToString(
+ (node["node"] as bip32.BIP32).publicKey),
+ "wif": (node["node"] as bip32.BIP32).toWIF(),
+ };
+ }
+
+ // increase counter when no tx history found
+ if (p2pkhTxCount == 0) {
+ receivingGapCounter++;
+ }
+ }
+ }
+
+ Logging.instance
+ .log("checking change addresses...", level: LogLevel.Info);
+ // change addresses
+ for (int index = 0;
+ index < maxNumberOfIndexesToCheck &&
+ changeGapCounter < maxUnusedAddressGap;
+ index += txCountBatchSize) {
+ Logging.instance.log(
+ "index: $index, \t changeGapCounter: $changeGapCounter",
+ level: LogLevel.Info);
+ final changeP2pkhID = "k_$index";
+ Map args = {};
+ final Map changeNodes = {};
+
+ for (int j = 0; j < txCountBatchSize; j++) {
+ // bip44 / P2PKH
+ final node44 = await compute(
+ getBip32NodeFromRootWrapper,
+ Tuple4(
+ 1,
+ index + j,
+ root,
+ DerivePathType.bip44,
+ ),
+ );
+ final p2pkhChangeAddress = P2PKH(
+ data: PaymentData(pubkey: node44.publicKey),
+ network: _network)
+ .data
+ .address!;
+ changeNodes.addAll({
+ "${changeP2pkhID}_$j": {
+ "node": node44,
+ "address": p2pkhChangeAddress,
+ }
+ });
+ args.addAll({
+ "${changeP2pkhID}_$j": p2pkhChangeAddress,
+ });
+ }
+
+ // get address tx counts
+ final counts = await _getBatchTxCount(addresses: args);
+
+ // check and add appropriate addresses
+ for (int k = 0; k < txCountBatchSize; k++) {
+ int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!;
+ if (p2pkhTxCount > 0) {
+ final node = changeNodes["${changeP2pkhID}_$k"];
+ // add address to array
+ p2pkhChangeAddressArray.add(node["address"] as String);
+ // set current index
+ p2pkhChangeIndex = index + k;
+ // reset counter
+ changeGapCounter = 0;
+ // add info to derivations
+ p2pkhChangeDerivations[node["address"] as String] = {
+ "pubKey": Format.uint8listToString(
+ (node["node"] as bip32.BIP32).publicKey),
+ "wif": (node["node"] as bip32.BIP32).toWIF(),
+ };
+ }
+
+ // increase counter when no tx history found
+ if (p2pkhTxCount == 0) {
+ changeGapCounter++;
+ }
+ }
+ }
+
+ // save the derivations (if any)
+ if (p2pkhReceiveDerivations.isNotEmpty) {
+ await addDerivations(
+ chain: 0,
+ derivePathType: DerivePathType.bip44,
+ derivationsToAdd: p2pkhReceiveDerivations);
+ }
+ if (p2pkhChangeDerivations.isNotEmpty) {
+ await addDerivations(
+ chain: 1,
+ derivePathType: DerivePathType.bip44,
+ derivationsToAdd: p2pkhChangeDerivations);
+ }
+
+ // If restoring a wallet that never received any funds, then set receivingArray manually
+ // If we didn't do this, it'd store an empty array
+ if (p2pkhReceiveIndex == -1) {
+ final address =
+ await _generateAddressForChain(0, 0, DerivePathType.bip44);
+ p2pkhReceiveAddressArray.add(address);
+ p2pkhReceiveIndex = 0;
+ }
+
+ // If restoring a wallet that never sent any funds with change, then set changeArray
+ // manually. If we didn't do this, it'd store an empty array.
+ if (p2pkhChangeIndex == -1) {
+ final address =
+ await _generateAddressForChain(1, 0, DerivePathType.bip44);
+ p2pkhChangeAddressArray.add(address);
+ p2pkhChangeIndex = 0;
+ }
+
+ await DB.instance.put(
+ boxName: walletId,
+ key: 'receivingAddressesP2PKH',
+ value: p2pkhReceiveAddressArray);
+ await DB.instance.put(
+ boxName: walletId,
+ key: 'changeAddressesP2PKH',
+ value: p2pkhChangeAddressArray);
+ await DB.instance.put(
+ boxName: walletId,
+ key: 'receivingIndexP2PKH',
+ value: p2pkhReceiveIndex);
+ await DB.instance.put(
+ boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex);
+ await DB.instance
+ .put(boxName: walletId, key: "id", value: _walletId);
+ await DB.instance
+ .put(boxName: walletId, key: "isFavorite", value: false);
+
+ longMutex = false;
+ } catch (e, s) {
+ Logging.instance.log(
+ "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s",
+ level: LogLevel.Info);
+
+ longMutex = false;
+ rethrow;
+ }
+ }
+
+ Future refreshIfThereIsNewData() async {
+ if (longMutex) return false;
+ if (_hasCalledExit) return false;
+ Logging.instance.log("refreshIfThereIsNewData", level: LogLevel.Info);
+
+ try {
+ bool needsRefresh = false;
+ Logging.instance.log(
+ "notified unconfirmed transactions: ${txTracker.pendings}",
+ level: LogLevel.Info);
+ Set txnsToCheck = {};
+
+ for (final String txid in txTracker.pendings) {
+ if (!txTracker.wasNotifiedConfirmed(txid)) {
+ txnsToCheck.add(txid);
+ }
+ }
+
+ for (String txid in txnsToCheck) {
+ final txn = await electrumXClient.getTransaction(txHash: txid);
+ var confirmations = txn["confirmations"];
+ if (confirmations is! int) continue;
+ bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS;
+ if (!isUnconfirmed) {
+ // unconfirmedTxs = {};
+ needsRefresh = true;
+ break;
+ }
+ }
+ if (!needsRefresh) {
+ var allOwnAddresses = await _fetchAllOwnAddresses();
+ List