mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-09 12:19:24 +00:00
various address and SWB fixes, as well as some electrumx_interface unused function cleanup
This commit is contained in:
parent
f1f8a0c49a
commit
f319aaf594
18 changed files with 153 additions and 418 deletions
|
@ -27,6 +27,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
|
@ -98,15 +99,21 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
|||
final walletId = widget.routeOnSuccessArguments as String;
|
||||
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
if (wallet.info.coin == Coin.monero) {
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
loadFuture = wallet.init();
|
||||
}
|
||||
|
||||
await showLoading(
|
||||
opaqueBG: true,
|
||||
whileFuture: wallet.init(),
|
||||
whileFuture: loadFuture,
|
||||
context: context,
|
||||
message: "Loading ${wallet.info.coin.prettyName} wallet...",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
|
|
|
@ -368,7 +368,7 @@ abstract class SWB {
|
|||
return backupJson;
|
||||
}
|
||||
|
||||
static Future<bool> asyncRestore(
|
||||
static Future<bool> _asyncRestore(
|
||||
Tuple2<dynamic, WalletInfo> tuple,
|
||||
Prefs prefs,
|
||||
NodeService nodeService,
|
||||
|
@ -757,7 +757,7 @@ abstract class SWB {
|
|||
)) {
|
||||
return false;
|
||||
}
|
||||
final bools = await asyncRestore(
|
||||
final bools = await _asyncRestore(
|
||||
tuple,
|
||||
_prefs,
|
||||
nodeService,
|
||||
|
@ -796,10 +796,10 @@ abstract class SWB {
|
|||
}
|
||||
|
||||
Logging.instance.log("done with SWB restore", level: LogLevel.Warning);
|
||||
if (Util.isDesktop) {
|
||||
|
||||
await Wallets.sharedInstance
|
||||
.loadAfterStackRestore(_prefs, uiState?.wallets ?? []);
|
||||
}
|
||||
.loadAfterStackRestore(_prefs, uiState?.wallets ?? [], Util.isDesktop);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ class _StackRestoreProgressViewState
|
|||
ref.read(pWallets).loadAfterStackRestore(
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
ref.read(stackRestoringUIStateProvider).wallets,
|
||||
);
|
||||
Util.isDesktop);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -115,17 +115,21 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
|||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
await wallet.init();
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (mounted) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
loadFuture = wallet.init();
|
||||
}
|
||||
await showLoading(
|
||||
whileFuture: wallet.open(),
|
||||
whileFuture: loadFuture,
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
isDesktop: Util.isDesktop,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
if (Util.isDesktop) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -63,17 +63,19 @@ class WalletListItem extends ConsumerWidget {
|
|||
.read(pWallets)
|
||||
.wallets
|
||||
.firstWhere((e) => e.info.coin == coin);
|
||||
await wallet.init();
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (context.mounted) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
loadFuture = wallet.init();
|
||||
}
|
||||
await showLoading(
|
||||
whileFuture: wallet.open(),
|
||||
whileFuture: loadFuture,
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
isDesktop: Util.isDesktop,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -80,17 +80,20 @@ class CoinWalletsTable extends ConsumerWidget {
|
|||
|
||||
final wallet =
|
||||
ref.read(pWallets).getWallet(walletIds[i]);
|
||||
await wallet.init();
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (context.mounted) {
|
||||
loadFuture = wallet
|
||||
.init()
|
||||
.then((value) async => await (wallet).open());
|
||||
} else {
|
||||
loadFuture = wallet.init();
|
||||
}
|
||||
await showLoading(
|
||||
whileFuture: wallet.open(),
|
||||
whileFuture: loadFuture,
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
isDesktop: Util.isDesktop,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -25,6 +25,7 @@ import 'package:stackwallet/utilities/prefs.dart';
|
|||
import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
|
||||
class Wallets {
|
||||
Wallets._private();
|
||||
|
@ -192,8 +193,7 @@ class Wallets {
|
|||
final shouldSetAutoSync = shouldAutoSyncAll ||
|
||||
walletIdsToEnableAutoSync.contains(walletInfo.walletId);
|
||||
|
||||
if (walletInfo.coin == Coin.monero ||
|
||||
walletInfo.coin == Coin.wownero) {
|
||||
if (wallet is CwBasedInterface) {
|
||||
// walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
|
||||
} else {
|
||||
walletInitFutures.add(wallet.init().then((_) {
|
||||
|
@ -230,6 +230,7 @@ class Wallets {
|
|||
Future<void> loadAfterStackRestore(
|
||||
Prefs prefs,
|
||||
List<Wallet> wallets,
|
||||
bool isDesktop,
|
||||
) async {
|
||||
List<Future<void>> walletInitFutures = [];
|
||||
List<({Wallet wallet, bool shouldAutoSync})> walletsToInitLinearly = [];
|
||||
|
@ -259,8 +260,8 @@ class Wallets {
|
|||
final shouldSetAutoSync = shouldAutoSyncAll ||
|
||||
walletIdsToEnableAutoSync.contains(wallet.walletId);
|
||||
|
||||
if (wallet.info.coin == Coin.monero ||
|
||||
wallet.info.coin == Coin.wownero) {
|
||||
if (isDesktop) {
|
||||
if (wallet is CwBasedInterface) {
|
||||
// walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
|
||||
} else {
|
||||
walletInitFutures.add(wallet.init().then((value) {
|
||||
|
@ -269,6 +270,7 @@ class Wallets {
|
|||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
_wallets[wallet.walletId] = wallet;
|
||||
} else {
|
||||
|
@ -278,6 +280,7 @@ class Wallets {
|
|||
}
|
||||
}
|
||||
|
||||
if (isDesktop) {
|
||||
if (walletInitFutures.isNotEmpty && walletsToInitLinearly.isNotEmpty) {
|
||||
await Future.wait([
|
||||
_initLinearly(walletsToInitLinearly),
|
||||
|
@ -289,6 +292,7 @@ class Wallets {
|
|||
await _initLinearly(walletsToInitLinearly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initLinearly(
|
||||
List<({Wallet wallet, bool shouldAutoSync})> dataList,
|
||||
|
|
|
@ -484,6 +484,11 @@ class EpiccashWallet extends Bip39Wallet {
|
|||
FilterOperation? get receivingAddressFilterOperation =>
|
||||
FilterGroup.and(standardReceivingAddressFilters);
|
||||
|
||||
@override
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
// epiccash seems ok with nothing here?
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init({bool? isRestore}) async {
|
||||
if (isRestore != true) {
|
||||
|
|
|
@ -86,25 +86,6 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
|
|||
_credentials = web3.EthPrivateKey.fromHex(privateKey);
|
||||
}
|
||||
|
||||
Future<void> _generateAndSaveAddress(
|
||||
String mnemonic,
|
||||
String mnemonicPassphrase,
|
||||
) async {
|
||||
await _initCredentials(mnemonic, mnemonicPassphrase);
|
||||
|
||||
final address = Address(
|
||||
walletId: walletId,
|
||||
value: _credentials.address.hexEip55,
|
||||
publicKey: [], // maybe store address bytes here? seems a waste of space though
|
||||
derivationIndex: 0,
|
||||
derivationPath: DerivationPath()..value = "$hdPathEthereum/0",
|
||||
type: AddressType.ethereum,
|
||||
subType: AddressSubType.receiving,
|
||||
);
|
||||
|
||||
await mainDB.updateOrPutAddresses([address]);
|
||||
}
|
||||
|
||||
// ==================== Overrides ============================================
|
||||
|
||||
@override
|
||||
|
@ -127,15 +108,27 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
|
|||
FilterGroup.and(standardReceivingAddressFilters);
|
||||
|
||||
@override
|
||||
Future<void> init({bool? isRestore}) async {
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
final address = await getCurrentReceivingAddress();
|
||||
if (address == null) {
|
||||
await _generateAndSaveAddress(
|
||||
await _initCredentials(
|
||||
await getMnemonic(),
|
||||
await getMnemonicPassphrase(),
|
||||
);
|
||||
|
||||
final address = Address(
|
||||
walletId: walletId,
|
||||
value: _credentials.address.hexEip55,
|
||||
publicKey: [],
|
||||
// maybe store address bytes here? seems a waste of space though
|
||||
derivationIndex: 0,
|
||||
derivationPath: DerivationPath()..value = "$hdPathEthereum/0",
|
||||
type: AddressType.ethereum,
|
||||
subType: AddressSubType.receiving,
|
||||
);
|
||||
|
||||
await mainDB.updateOrPutAddresses([address]);
|
||||
}
|
||||
return super.init();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -475,17 +468,11 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
|
|||
await refreshMutex.protect(() async {
|
||||
if (isRescan) {
|
||||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
await _generateAndSaveAddress(
|
||||
await getMnemonic(),
|
||||
await getMnemonicPassphrase(),
|
||||
);
|
||||
await checkSaveInitialReceivingAddress();
|
||||
await updateBalance();
|
||||
await updateTransactions(isRescan: true);
|
||||
} else {
|
||||
await _generateAndSaveAddress(
|
||||
await getMnemonic(),
|
||||
await getMnemonicPassphrase(),
|
||||
);
|
||||
await checkSaveInitialReceivingAddress();
|
||||
unawaited(updateBalance());
|
||||
unawaited(updateTransactions());
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
|||
FilterGroup.and(standardReceivingAddressFilters);
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
try {
|
||||
final address = await getCurrentReceivingAddress();
|
||||
if (address == null) {
|
||||
|
@ -121,11 +121,10 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
|||
} catch (e, s) {
|
||||
// do nothing, still allow user into wallet
|
||||
Logging.instance.log(
|
||||
"$runtimeType init() failed: $e\n$s",
|
||||
"$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
return super.init();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -527,4 +527,9 @@ class EthTokenWallet extends Wallet {
|
|||
value: TransactionSubType.ethToken,
|
||||
),
|
||||
]);
|
||||
|
||||
@override
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
await ethWallet.checkSaveInitialReceivingAddress();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ class TezosWallet extends Bip39Wallet<Tezos> {
|
|||
// ===========================================================================
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
try {
|
||||
final _address = await getCurrentReceivingAddress();
|
||||
if (_address == null) {
|
||||
|
@ -155,12 +155,10 @@ class TezosWallet extends Bip39Wallet<Tezos> {
|
|||
} catch (e, s) {
|
||||
// do nothing, still allow user into wallet
|
||||
Logging.instance.log(
|
||||
"$runtimeType init() failed: $e\n$s",
|
||||
"$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
|
||||
await super.init();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -61,6 +61,20 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
|||
await mainDB.updateOrPutAddresses([address]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
final current = await getCurrentChangeAddress();
|
||||
if (current == null) {
|
||||
final address = await _generateAddress(
|
||||
chain: 0, // receiving
|
||||
index: 0, // initial index
|
||||
derivePathType: DerivePathTypeExt.primaryFor(info.coin),
|
||||
);
|
||||
|
||||
await mainDB.updateOrPutAddresses([address]);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Subclasses may override ========================================
|
||||
|
||||
/// To be overridden by crypto currencies that do extra address conversions
|
||||
|
|
|
@ -435,6 +435,8 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
|
||||
Future<bool> pingCheck();
|
||||
|
||||
Future<void> checkSaveInitialReceivingAddress();
|
||||
|
||||
//===========================================
|
||||
/// add transaction to local db temporarily. Used for quickly updating ui
|
||||
/// before refresh can fetch data from server
|
||||
|
@ -600,6 +602,7 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
|
||||
@mustCallSuper
|
||||
Future<void> init() async {
|
||||
await checkSaveInitialReceivingAddress();
|
||||
final address = await getCurrentReceivingAddress();
|
||||
if (address != null) {
|
||||
await info.updateReceivingAddress(
|
||||
|
@ -607,9 +610,6 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
isar: mainDB.isar,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: make sure subclasses override this if they require some set up
|
||||
// especially xmr/wow/epiccash
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
|
|
@ -179,6 +179,11 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
// this doesn't work without opening the wallet first which takes a while
|
||||
}
|
||||
|
||||
// ============ Interface ====================================================
|
||||
|
||||
Future<Amount> get availableBalance;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:bip47/src/util.dart';
|
||||
import 'package:bitcoindart/bitcoindart.dart' as bitcoindart;
|
||||
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||
|
@ -831,55 +829,6 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<({Transaction transaction, Address address})>>
|
||||
fetchTransactionsV1({
|
||||
required List<Address> addresses,
|
||||
required int currentChainHeight,
|
||||
}) async {
|
||||
final List<({String txHash, int height, String address})> allTxHashes =
|
||||
(await fetchHistory(addresses.map((e) => e.value).toList()))
|
||||
.map(
|
||||
(e) => (
|
||||
txHash: e["tx_hash"] as String,
|
||||
height: e["height"] as int,
|
||||
address: e["address"] as String,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
List<Map<String, dynamic>> allTransactions = [];
|
||||
|
||||
for (final data in allTxHashes) {
|
||||
final tx = await electrumXCachedClient.getTransaction(
|
||||
txHash: data.txHash,
|
||||
verbose: true,
|
||||
coin: cryptoCurrency.coin,
|
||||
);
|
||||
|
||||
// check for duplicates before adding to list
|
||||
if (allTransactions
|
||||
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
||||
-1) {
|
||||
tx["address"] = addresses.firstWhere((e) => e.value == data.address);
|
||||
tx["height"] = data.height;
|
||||
allTransactions.add(tx);
|
||||
}
|
||||
}
|
||||
|
||||
final List<({Transaction transaction, Address address})> txnsData = [];
|
||||
|
||||
for (final txObject in allTransactions) {
|
||||
final data = await _parseTransactionV1(
|
||||
txObject,
|
||||
addresses,
|
||||
);
|
||||
|
||||
txnsData.add(data);
|
||||
}
|
||||
|
||||
return txnsData;
|
||||
}
|
||||
|
||||
Future<ElectrumXNode> getCurrentElectrumXNode() async {
|
||||
final node = getCurrentNode();
|
||||
|
||||
|
@ -1185,257 +1134,6 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
return utxo;
|
||||
}
|
||||
|
||||
Future<({Transaction transaction, Address address})> _parseTransactionV1(
|
||||
Map<String, dynamic> txData,
|
||||
List<Address> myAddresses,
|
||||
) async {
|
||||
Set<String> receivingAddresses = myAddresses
|
||||
.where((e) =>
|
||||
e.subType == AddressSubType.receiving ||
|
||||
e.subType == AddressSubType.paynymReceive ||
|
||||
e.subType == AddressSubType.paynymNotification)
|
||||
.map((e) => e.value)
|
||||
.toSet();
|
||||
Set<String> changeAddresses = myAddresses
|
||||
.where((e) => e.subType == AddressSubType.change)
|
||||
.map((e) => e.value)
|
||||
.toSet();
|
||||
|
||||
Set<String> inputAddresses = {};
|
||||
Set<String> outputAddresses = {};
|
||||
|
||||
Amount totalInputValue = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
Amount totalOutputValue = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
|
||||
Amount amountSentFromWallet = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
Amount amountReceivedInWallet = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
Amount changeAmount = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
|
||||
// parse inputs
|
||||
for (final input in txData["vin"] as List) {
|
||||
final prevTxid = input["txid"] as String;
|
||||
final prevOut = input["vout"] as int;
|
||||
|
||||
// fetch input tx to get address
|
||||
final inputTx = await electrumXCachedClient.getTransaction(
|
||||
txHash: prevTxid,
|
||||
coin: cryptoCurrency.coin,
|
||||
);
|
||||
|
||||
for (final output in inputTx["vout"] as List) {
|
||||
// check matching output
|
||||
if (prevOut == output["n"]) {
|
||||
// get value
|
||||
final value = Amount.fromDecimal(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalInputValue += value;
|
||||
|
||||
// get input(prevOut) address
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
|
||||
if (address != null) {
|
||||
inputAddresses.add(address);
|
||||
|
||||
// if input was from my wallet, add value to amount sent
|
||||
if (receivingAddresses.contains(address) ||
|
||||
changeAddresses.contains(address)) {
|
||||
amountSentFromWallet += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse outputs
|
||||
for (final output in txData["vout"] as List) {
|
||||
// get value
|
||||
final value = Amount.fromDecimal(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalOutputValue += value;
|
||||
|
||||
// get output address
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
if (address != null) {
|
||||
outputAddresses.add(address);
|
||||
|
||||
// if output was to my wallet, add value to amount received
|
||||
if (receivingAddresses.contains(address)) {
|
||||
amountReceivedInWallet += value;
|
||||
} else if (changeAddresses.contains(address)) {
|
||||
changeAmount += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final mySentFromAddresses = [
|
||||
...receivingAddresses.intersection(inputAddresses),
|
||||
...changeAddresses.intersection(inputAddresses)
|
||||
];
|
||||
final myReceivedOnAddresses =
|
||||
receivingAddresses.intersection(outputAddresses);
|
||||
final myChangeReceivedOnAddresses =
|
||||
changeAddresses.intersection(outputAddresses);
|
||||
|
||||
final fee = totalInputValue - totalOutputValue;
|
||||
|
||||
// this is the address initially used to fetch the txid
|
||||
Address transactionAddress = txData["address"] as Address;
|
||||
|
||||
TransactionType type;
|
||||
Amount amount;
|
||||
if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) {
|
||||
// tx is sent to self
|
||||
type = TransactionType.sentToSelf;
|
||||
|
||||
// should be 0
|
||||
amount =
|
||||
amountSentFromWallet - amountReceivedInWallet - fee - changeAmount;
|
||||
} else if (mySentFromAddresses.isNotEmpty) {
|
||||
// outgoing tx
|
||||
type = TransactionType.outgoing;
|
||||
amount = amountSentFromWallet - changeAmount - fee;
|
||||
|
||||
// non wallet addresses found in tx outputs
|
||||
final nonWalletOutAddresses = outputAddresses.difference(
|
||||
myChangeReceivedOnAddresses,
|
||||
);
|
||||
|
||||
if (nonWalletOutAddresses.isNotEmpty) {
|
||||
final possible = nonWalletOutAddresses.first;
|
||||
|
||||
if (transactionAddress.value != possible) {
|
||||
transactionAddress = Address(
|
||||
walletId: myAddresses.first.walletId,
|
||||
value: possible,
|
||||
derivationIndex: -1,
|
||||
derivationPath: null,
|
||||
subType: AddressSubType.nonWallet,
|
||||
type: AddressType.nonWallet,
|
||||
publicKey: [],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// some other type of tx where the receiving address is
|
||||
// one of my change addresses
|
||||
|
||||
type = TransactionType.sentToSelf;
|
||||
amount = changeAmount;
|
||||
}
|
||||
} else {
|
||||
// incoming tx
|
||||
type = TransactionType.incoming;
|
||||
amount = amountReceivedInWallet;
|
||||
}
|
||||
|
||||
List<Output> outs = [];
|
||||
List<Input> ins = [];
|
||||
|
||||
for (final json in txData["vin"] as List) {
|
||||
bool isCoinBase = json['coinbase'] != null;
|
||||
String? witness;
|
||||
if (json['witness'] != null && json['witness'] is String) {
|
||||
witness = json['witness'] as String;
|
||||
} else if (json['txinwitness'] != null) {
|
||||
if (json['txinwitness'] is List) {
|
||||
witness = jsonEncode(json['txinwitness']);
|
||||
}
|
||||
}
|
||||
final input = Input(
|
||||
txid: json['txid'] as String,
|
||||
vout: json['vout'] as int? ?? -1,
|
||||
scriptSig: json['scriptSig']?['hex'] as String?,
|
||||
scriptSigAsm: json['scriptSig']?['asm'] as String?,
|
||||
isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?,
|
||||
sequence: json['sequence'] as int?,
|
||||
innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?,
|
||||
witness: witness,
|
||||
);
|
||||
ins.add(input);
|
||||
}
|
||||
|
||||
for (final json in txData["vout"] as List) {
|
||||
final output = Output(
|
||||
scriptPubKey: json['scriptPubKey']?['hex'] as String?,
|
||||
scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?,
|
||||
scriptPubKeyType: json['scriptPubKey']?['type'] as String?,
|
||||
scriptPubKeyAddress:
|
||||
json["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
json['scriptPubKey']?['type'] as String? ??
|
||||
"",
|
||||
value: Amount.fromDecimal(
|
||||
Decimal.parse(json["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.coin.decimals,
|
||||
).raw.toInt(),
|
||||
);
|
||||
outs.add(output);
|
||||
}
|
||||
|
||||
TransactionSubType txSubType = TransactionSubType.none;
|
||||
if (this is PaynymInterface && outs.length > 1 && ins.isNotEmpty) {
|
||||
for (int i = 0; i < outs.length; i++) {
|
||||
List<String>? scriptChunks = outs[i].scriptPubKeyAsm?.split(" ");
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.fromHex;
|
||||
|
||||
// https://en.bitcoin.it/wiki/BIP_0047#Sending
|
||||
if (bytes.length == 80 && bytes.first == 1) {
|
||||
txSubType = TransactionSubType.bip47Notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final tx = Transaction(
|
||||
walletId: myAddresses.first.walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: txData["blocktime"] as int? ??
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
type: type,
|
||||
subType: txSubType,
|
||||
// amount may overflow. Deprecated. Use amountString
|
||||
amount: amount.raw.toInt(),
|
||||
amountString: amount.toJsonString(),
|
||||
fee: fee.raw.toInt(),
|
||||
height: txData["height"] as int?,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
slateId: null,
|
||||
otherData: null,
|
||||
nonce: null,
|
||||
inputs: ins,
|
||||
outputs: outs,
|
||||
numberOfMessages: null,
|
||||
);
|
||||
|
||||
return (transaction: tx, address: transactionAddress);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
@override
|
||||
|
@ -1993,12 +1691,15 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkSaveInitialReceivingAddress() async {}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
try {
|
||||
final features = await electrumXClient
|
||||
.getServerFeatures()
|
||||
.timeout(const Duration(seconds: 2));
|
||||
.timeout(const Duration(seconds: 4));
|
||||
|
||||
Logging.instance.log("features: $features", level: LogLevel.Info);
|
||||
|
||||
|
|
|
@ -315,22 +315,20 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
try {
|
||||
_cachedAddress = await getCurrentReceivingAddress();
|
||||
if (_cachedAddress == null) {
|
||||
_cachedAddress = await _getAddressFromMnemonic();
|
||||
await mainDB.putAddress(_cachedAddress!);
|
||||
await mainDB.updateOrPutAddresses([_cachedAddress!]);
|
||||
}
|
||||
} catch (e, s) {
|
||||
// do nothing, still allow user into wallet
|
||||
Logging.instance.log(
|
||||
"$runtimeType init() failed: $e\n$s",
|
||||
"$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
|
||||
return super.init();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -93,17 +93,20 @@ class SimpleWalletCard extends ConsumerWidget {
|
|||
final nav = Navigator.of(context);
|
||||
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
await wallet.init();
|
||||
|
||||
if (context.mounted) {
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
loadFuture = wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
loadFuture = wallet.init();
|
||||
}
|
||||
await showLoading(
|
||||
whileFuture: wallet.open(),
|
||||
whileFuture: loadFuture,
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
isDesktop: Util.isDesktop,
|
||||
);
|
||||
}
|
||||
if (popPrevious) nav.pop();
|
||||
|
||||
if (desktopNavigatorState != null) {
|
||||
|
|
Loading…
Reference in a new issue