mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 01:37:54 +00:00
Untested ecash fusion port. Manual port of https://github.com/cypherstack/stack_wallet/pull/705 combined with manual port to v2 transactions for ecash as well as a couple other changes ported from the wallets_refactor branch
This commit is contained in:
parent
747565fa16
commit
9a9c9550ee
10 changed files with 426 additions and 234 deletions
|
@ -61,10 +61,11 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> {
|
||||||
FusionOption _option = FusionOption.continuous;
|
FusionOption _option = FusionOption.continuous;
|
||||||
|
|
||||||
Future<void> _startFusion() async {
|
Future<void> _startFusion() async {
|
||||||
final fusionWallet = ref
|
final wallet = ref
|
||||||
.read(walletsChangeNotifierProvider)
|
.read(walletsChangeNotifierProvider)
|
||||||
.getManager(widget.walletId)
|
.getManager(widget.walletId)
|
||||||
.wallet as FusionWalletInterface;
|
.wallet;
|
||||||
|
final fusionWallet = wallet as FusionWalletInterface;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fusionWallet.uiState = ref.read(
|
fusionWallet.uiState = ref.read(
|
||||||
|
@ -89,7 +90,9 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// update user prefs (persistent)
|
// update user prefs (persistent)
|
||||||
ref.read(prefsChangeNotifierProvider).fusionServerInfo = newInfo;
|
ref
|
||||||
|
.read(prefsChangeNotifierProvider)
|
||||||
|
.setFusionServerInfo(wallet.coin, newInfo);
|
||||||
|
|
||||||
unawaited(
|
unawaited(
|
||||||
fusionWallet.fuse(
|
fusionWallet.fuse(
|
||||||
|
@ -113,7 +116,11 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> {
|
||||||
portFocusNode = FocusNode();
|
portFocusNode = FocusNode();
|
||||||
fusionRoundFocusNode = FocusNode();
|
fusionRoundFocusNode = FocusNode();
|
||||||
|
|
||||||
final info = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
|
final info = ref.read(prefsChangeNotifierProvider).getFusionServerInfo(ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet
|
||||||
|
.coin);
|
||||||
serverController.text = info.host;
|
serverController.text = info.host;
|
||||||
portController.text = info.port.toString();
|
portController.text = info.port.toString();
|
||||||
_enableSSLCheckbox = info.ssl;
|
_enableSSLCheckbox = info.ssl;
|
||||||
|
@ -150,7 +157,7 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> {
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
leading: const AppBarBackButton(),
|
leading: const AppBarBackButton(),
|
||||||
title: Text(
|
title: Text(
|
||||||
"CashFusion",
|
"Fusion",
|
||||||
style: STextStyles.navBarTitle(context),
|
style: STextStyles.navBarTitle(context),
|
||||||
),
|
),
|
||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
|
@ -189,7 +196,7 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> {
|
||||||
children: [
|
children: [
|
||||||
RoundedWhiteContainer(
|
RoundedWhiteContainer(
|
||||||
child: Text(
|
child: Text(
|
||||||
"CashFusion allows you to anonymize your BCH coins.",
|
"Fusion helps anonymize your coins by mixing them.",
|
||||||
style: STextStyles.w500_12(context).copyWith(
|
style: STextStyles.w500_12(context).copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
|
@ -214,7 +221,11 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> {
|
||||||
CustomTextButton(
|
CustomTextButton(
|
||||||
text: "Default",
|
text: "Default",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const def = FusionInfo.DEFAULTS;
|
final def = kFusionServerInfoDefaults[ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet
|
||||||
|
.coin]!;
|
||||||
serverController.text = def.host;
|
serverController.text = def.host;
|
||||||
portController.text = def.port.toString();
|
portController.text = def.port.toString();
|
||||||
fusionRoundController.text =
|
fusionRoundController.text =
|
||||||
|
|
|
@ -18,6 +18,7 @@ import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/show_loading.dart';
|
import 'package:stackwallet/utilities/show_loading.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
@ -43,6 +44,8 @@ class FusionProgressView extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
||||||
|
late final Coin coin;
|
||||||
|
|
||||||
Future<bool> _requestAndProcessCancel() async {
|
Future<bool> _requestAndProcessCancel() async {
|
||||||
final shouldCancel = await showDialog<bool?>(
|
final shouldCancel = await showDialog<bool?>(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -88,6 +91,16 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
coin = ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet
|
||||||
|
.coin;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bool _succeeded =
|
final bool _succeeded =
|
||||||
|
@ -230,7 +243,8 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
||||||
.getManager(widget.walletId)
|
.getManager(widget.walletId)
|
||||||
.wallet as FusionWalletInterface;
|
.wallet as FusionWalletInterface;
|
||||||
|
|
||||||
final fusionInfo = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
|
final fusionInfo =
|
||||||
|
ref.read(prefsChangeNotifierProvider).getFusionServerInfo(coin);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fusionWallet.uiState = ref.read(
|
fusionWallet.uiState = ref.read(
|
||||||
|
|
|
@ -845,7 +845,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
coin == Coin.bitcoincash ||
|
coin == Coin.bitcoincash ||
|
||||||
coin == Coin.bitcoincashTestnet
|
coin == Coin.bitcoincashTestnet ||
|
||||||
|
coin == Coin.eCash
|
||||||
? AllTransactionsV2View.routeName
|
? AllTransactionsV2View.routeName
|
||||||
: AllTransactionsView.routeName,
|
: AllTransactionsView.routeName,
|
||||||
arguments: walletId,
|
arguments: walletId,
|
||||||
|
@ -902,7 +903,9 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: coin == Coin.bitcoincash ||
|
child: coin == Coin.bitcoincash ||
|
||||||
coin == Coin.bitcoincashTestnet
|
coin ==
|
||||||
|
Coin.bitcoincashTestnet ||
|
||||||
|
coin == Coin.eCash
|
||||||
? TransactionsV2List(
|
? TransactionsV2List(
|
||||||
walletId: widget.walletId,
|
walletId: widget.walletId,
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||||
|
@ -58,6 +59,7 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||||
late final FocusNode portFocusNode;
|
late final FocusNode portFocusNode;
|
||||||
late final TextEditingController fusionRoundController;
|
late final TextEditingController fusionRoundController;
|
||||||
late final FocusNode fusionRoundFocusNode;
|
late final FocusNode fusionRoundFocusNode;
|
||||||
|
late final Coin coin;
|
||||||
|
|
||||||
bool _enableStartButton = false;
|
bool _enableStartButton = false;
|
||||||
bool _enableSSLCheckbox = false;
|
bool _enableSSLCheckbox = false;
|
||||||
|
@ -93,7 +95,7 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// update user prefs (persistent)
|
// update user prefs (persistent)
|
||||||
ref.read(prefsChangeNotifierProvider).fusionServerInfo = newInfo;
|
ref.read(prefsChangeNotifierProvider).setFusionServerInfo(coin, newInfo);
|
||||||
|
|
||||||
unawaited(
|
unawaited(
|
||||||
fusionWallet.fuse(
|
fusionWallet.fuse(
|
||||||
|
@ -121,8 +123,14 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||||
serverFocusNode = FocusNode();
|
serverFocusNode = FocusNode();
|
||||||
portFocusNode = FocusNode();
|
portFocusNode = FocusNode();
|
||||||
fusionRoundFocusNode = FocusNode();
|
fusionRoundFocusNode = FocusNode();
|
||||||
|
coin = ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet
|
||||||
|
.coin;
|
||||||
|
|
||||||
final info = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
|
final info =
|
||||||
|
ref.read(prefsChangeNotifierProvider).getFusionServerInfo(coin);
|
||||||
serverController.text = info.host;
|
serverController.text = info.host;
|
||||||
portController.text = info.port.toString();
|
portController.text = info.port.toString();
|
||||||
_enableSSLCheckbox = info.ssl;
|
_enableSSLCheckbox = info.ssl;
|
||||||
|
@ -197,7 +205,7 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||||
width: 12,
|
width: 12,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"CashFusion",
|
"Fusion",
|
||||||
style: STextStyles.desktopH3(context),
|
style: STextStyles.desktopH3(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -219,7 +227,7 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||||
),
|
),
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: "What is CashFusion?",
|
text: "What is Fusion?",
|
||||||
style: STextStyles.richLink(context).copyWith(
|
style: STextStyles.richLink(context).copyWith(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
|
@ -248,7 +256,7 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||||
.spaceBetween,
|
.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"What is CashFusion?",
|
"What is Fusion?",
|
||||||
style: STextStyles.desktopH2(
|
style: STextStyles.desktopH2(
|
||||||
context),
|
context),
|
||||||
),
|
),
|
||||||
|
@ -308,7 +316,7 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"CashFusion allows you to anonymize your BCH coins.",
|
"Fusion helps anonymize your coins by mixing them.",
|
||||||
style:
|
style:
|
||||||
STextStyles.desktopTextExtraExtraSmall(context),
|
STextStyles.desktopTextExtraExtraSmall(context),
|
||||||
),
|
),
|
||||||
|
@ -336,7 +344,11 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||||
CustomTextButton(
|
CustomTextButton(
|
||||||
text: "Default",
|
text: "Default",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const def = FusionInfo.DEFAULTS;
|
final def = kFusionServerInfoDefaults[ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet
|
||||||
|
.coin]!;
|
||||||
serverController.text = def.host;
|
serverController.text = def.host;
|
||||||
portController.text = def.port.toString();
|
portController.text = def.port.toString();
|
||||||
fusionRoundController.text =
|
fusionRoundController.text =
|
||||||
|
|
|
@ -283,12 +283,14 @@ class _FusionDialogViewState extends ConsumerState<FusionDialogView> {
|
||||||
|
|
||||||
/// Fuse again.
|
/// Fuse again.
|
||||||
void _fuseAgain() async {
|
void _fuseAgain() async {
|
||||||
final fusionWallet = ref
|
final wallet = ref
|
||||||
.read(walletsChangeNotifierProvider)
|
.read(walletsChangeNotifierProvider)
|
||||||
.getManager(widget.walletId)
|
.getManager(widget.walletId)
|
||||||
.wallet as FusionWalletInterface;
|
.wallet;
|
||||||
|
final fusionWallet = wallet as FusionWalletInterface;
|
||||||
|
|
||||||
final fusionInfo = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
|
final fusionInfo =
|
||||||
|
ref.read(prefsChangeNotifierProvider).getFusionServerInfo(wallet.coin);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fusionWallet.uiState = ref.read(
|
fusionWallet.uiState = ref.read(
|
||||||
|
|
|
@ -482,7 +482,8 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
||||||
} else {
|
} else {
|
||||||
await Navigator.of(context).pushNamed(
|
await Navigator.of(context).pushNamed(
|
||||||
coin == Coin.bitcoincash ||
|
coin == Coin.bitcoincash ||
|
||||||
coin == Coin.bitcoincashTestnet
|
coin == Coin.bitcoincashTestnet ||
|
||||||
|
coin == Coin.eCash
|
||||||
? AllTransactionsV2View.routeName
|
? AllTransactionsV2View.routeName
|
||||||
: AllTransactionsView.routeName,
|
: AllTransactionsView.routeName,
|
||||||
arguments: widget.walletId,
|
arguments: widget.walletId,
|
||||||
|
@ -520,7 +521,8 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
||||||
walletId: widget.walletId,
|
walletId: widget.walletId,
|
||||||
)
|
)
|
||||||
: coin == Coin.bitcoincash ||
|
: coin == Coin.bitcoincash ||
|
||||||
coin == Coin.bitcoincashTestnet
|
coin == Coin.bitcoincashTestnet ||
|
||||||
|
coin == Coin.eCash
|
||||||
? TransactionsV2List(
|
? TransactionsV2List(
|
||||||
walletId: widget.walletId,
|
walletId: widget.walletId,
|
||||||
)
|
)
|
||||||
|
|
|
@ -125,8 +125,8 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
||||||
),
|
),
|
||||||
if (manager.hasFusionSupport)
|
if (manager.hasFusionSupport)
|
||||||
_MoreFeaturesItem(
|
_MoreFeaturesItem(
|
||||||
label: "CashFusion",
|
label: "Fusion",
|
||||||
detail: "Decentralized Bitcoin Cash mixing protocol",
|
detail: "Decentralized mixing protocol",
|
||||||
iconAsset: Assets.svg.cashFusion,
|
iconAsset: Assets.svg.cashFusion,
|
||||||
onPressed: () => widget.onFusionPressed?.call(),
|
onPressed: () => widget.onFusionPressed?.call(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -26,9 +26,13 @@ import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||||
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
|
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
|
||||||
import 'package:stackwallet/models/balance.dart';
|
import 'package:stackwallet/models/balance.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/models/signing_data.dart';
|
import 'package:stackwallet/models/signing_data.dart';
|
||||||
|
import 'package:stackwallet/services/coins/bitcoincash/bch_utils.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.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/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/refresh_percent_changed_event.dart';
|
||||||
|
@ -37,6 +41,7 @@ import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_
|
||||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||||
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
|
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
|
||||||
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
|
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
|
||||||
|
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||||
import 'package:stackwallet/services/mixins/xpubable.dart';
|
import 'package:stackwallet/services/mixins/xpubable.dart';
|
||||||
|
@ -50,6 +55,7 @@ import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/extensions/extensions.dart';
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/prefs.dart';
|
import 'package:stackwallet/utilities/prefs.dart';
|
||||||
|
@ -130,7 +136,12 @@ String constructDerivePath({
|
||||||
}
|
}
|
||||||
|
|
||||||
class ECashWallet extends CoinServiceAPI
|
class ECashWallet extends CoinServiceAPI
|
||||||
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface
|
with
|
||||||
|
WalletCache,
|
||||||
|
WalletDB,
|
||||||
|
ElectrumXParsing,
|
||||||
|
CoinControlInterface,
|
||||||
|
FusionWalletInterface
|
||||||
implements XPubAble {
|
implements XPubAble {
|
||||||
ECashWallet({
|
ECashWallet({
|
||||||
required String walletId,
|
required String walletId,
|
||||||
|
@ -162,6 +173,19 @@ class ECashWallet extends CoinServiceAPI
|
||||||
await updateCachedBalance(_balance!);
|
await updateCachedBalance(_balance!);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
initFusionInterface(
|
||||||
|
walletId: walletId,
|
||||||
|
coin: coin,
|
||||||
|
db: db,
|
||||||
|
getWalletCachedElectrumX: () => cachedElectrumXClient,
|
||||||
|
getNextUnusedChangeAddress: _getUnusedChangeAddresses,
|
||||||
|
getChainHeight: () async => chainHeight,
|
||||||
|
updateWalletUTXOS: _updateUTXOs,
|
||||||
|
mnemonic: mnemonicString,
|
||||||
|
mnemonicPassphrase: mnemonicPassphrase,
|
||||||
|
network: _network,
|
||||||
|
convertToScriptHash: _convertToScriptHash,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const integrationTestFlag =
|
static const integrationTestFlag =
|
||||||
|
@ -185,6 +209,81 @@ class ECashWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<isar_models.Address>> _getUnusedChangeAddresses({
|
||||||
|
int numberOfAddresses = 1,
|
||||||
|
}) async {
|
||||||
|
if (numberOfAddresses < 1) {
|
||||||
|
throw ArgumentError.value(
|
||||||
|
numberOfAddresses,
|
||||||
|
"numberOfAddresses",
|
||||||
|
"Must not be less than 1",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final changeAddresses = await db
|
||||||
|
.getAddresses(walletId)
|
||||||
|
.filter()
|
||||||
|
.typeEqualTo(isar_models.AddressType.p2pkh)
|
||||||
|
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||||
|
.derivationPath((q) => q.not().valueStartsWith("m/44'/0'"))
|
||||||
|
.sortByDerivationIndex()
|
||||||
|
.findAll();
|
||||||
|
|
||||||
|
final List<isar_models.Address> unused = [];
|
||||||
|
|
||||||
|
for (final addr in changeAddresses) {
|
||||||
|
if (await _isUnused(addr.value)) {
|
||||||
|
unused.add(addr);
|
||||||
|
if (unused.length == numberOfAddresses) {
|
||||||
|
return unused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not returned by now, we need to create more addresses
|
||||||
|
int countMissing = numberOfAddresses - unused.length;
|
||||||
|
|
||||||
|
int nextIndex =
|
||||||
|
changeAddresses.isEmpty ? 0 : changeAddresses.last.derivationIndex + 1;
|
||||||
|
|
||||||
|
while (countMissing > 0) {
|
||||||
|
// create a new address
|
||||||
|
final address = await _generateAddressForChain(
|
||||||
|
1,
|
||||||
|
nextIndex,
|
||||||
|
DerivePathTypeExt.primaryFor(coin),
|
||||||
|
);
|
||||||
|
nextIndex++;
|
||||||
|
await db.updateOrPutAddresses([address]);
|
||||||
|
|
||||||
|
// check if it has been used before adding
|
||||||
|
if (await _isUnused(address.value)) {
|
||||||
|
unused.add(address);
|
||||||
|
countMissing--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unused;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _isUnused(String address) async {
|
||||||
|
final txCountInDB = await db
|
||||||
|
.getTransactions(_walletId)
|
||||||
|
.filter()
|
||||||
|
.address((q) => q.valueEqualTo(address))
|
||||||
|
.count();
|
||||||
|
if (txCountInDB == 0) {
|
||||||
|
// double check via electrumx
|
||||||
|
// _getTxCountForAddress can throw!
|
||||||
|
// final count = await getTxCount(address: address);
|
||||||
|
// if (count == 0) {
|
||||||
|
return true;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set isFavorite(bool markFavorite) {
|
set isFavorite(bool markFavorite) {
|
||||||
_isFavorite = markFavorite;
|
_isFavorite = markFavorite;
|
||||||
|
@ -1160,6 +1259,8 @@ class ECashWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}).toSet();
|
}).toSet();
|
||||||
|
|
||||||
|
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
|
||||||
|
|
||||||
final List<Map<String, dynamic>> allTxHashes =
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
await _fetchHistory([...receivingAddresses, ...changeAddresses]);
|
await _fetchHistory([...receivingAddresses, ...changeAddresses]);
|
||||||
|
|
||||||
|
@ -1194,207 +1295,168 @@ class ECashWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Tuple2<isar_models.Transaction, isar_models.Address?>> txns = [];
|
final List<TransactionV2> txns = [];
|
||||||
|
|
||||||
for (final txData in allTransactions) {
|
for (final txData in allTransactions) {
|
||||||
Set<String> inputAddresses = {};
|
// set to true if any inputs were detected as owned by this wallet
|
||||||
Set<String> outputAddresses = {};
|
bool wasSentFromThisWallet = false;
|
||||||
|
|
||||||
Logging.instance.log(txData, level: LogLevel.Fatal);
|
// set to true if any outputs were detected as owned by this wallet
|
||||||
|
bool wasReceivedInThisWallet = false;
|
||||||
Amount totalInputValue = Amount(
|
BigInt amountReceivedInThisWallet = BigInt.zero;
|
||||||
rawValue: BigInt.from(0),
|
BigInt changeAmountReceivedInThisWallet = BigInt.zero;
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
Amount totalOutputValue = Amount(
|
|
||||||
rawValue: BigInt.from(0),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
|
|
||||||
Amount amountSentFromWallet = Amount(
|
|
||||||
rawValue: BigInt.from(0),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
Amount amountReceivedInWallet = Amount(
|
|
||||||
rawValue: BigInt.from(0),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
Amount changeAmount = Amount(
|
|
||||||
rawValue: BigInt.from(0),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
|
|
||||||
// parse inputs
|
// parse inputs
|
||||||
for (final input in txData["vin"] as List) {
|
final List<InputV2> inputs = [];
|
||||||
final prevTxid = input["txid"] as String;
|
for (final jsonInput in txData["vin"] as List) {
|
||||||
final prevOut = input["vout"] as int;
|
final map = Map<String, dynamic>.from(jsonInput as Map);
|
||||||
|
|
||||||
// fetch input tx to get address
|
final List<String> addresses = [];
|
||||||
final inputTx = await cachedElectrumXClient.getTransaction(
|
String valueStringSats = "0";
|
||||||
txHash: prevTxid,
|
OutpointV2? outpoint;
|
||||||
coin: coin,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (final output in inputTx["vout"] as List) {
|
final coinbase = map["coinbase"] as String?;
|
||||||
// check matching output
|
|
||||||
if (prevOut == output["n"]) {
|
|
||||||
// get value
|
|
||||||
final value = Amount.fromDecimal(
|
|
||||||
Decimal.parse(output["value"].toString()),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
|
|
||||||
// add value to total
|
if (coinbase == null) {
|
||||||
totalInputValue = totalInputValue + value;
|
final txid = map["txid"] as String;
|
||||||
|
final vout = map["vout"] as int;
|
||||||
|
|
||||||
// get input(prevOut) address
|
final inputTx = await cachedElectrumXClient.getTransaction(
|
||||||
final address =
|
txHash: txid,
|
||||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
coin: coin,
|
||||||
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 = amountSentFromWallet + value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse outputs
|
|
||||||
for (final output in txData["vout"] as List) {
|
|
||||||
// get value
|
|
||||||
final value = Amount.fromDecimal(
|
|
||||||
Decimal.parse(output["value"].toString()),
|
|
||||||
fractionDigits: 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
|
|
||||||
isar_models.Address transactionAddress =
|
|
||||||
txData["address"] as isar_models.Address;
|
|
||||||
|
|
||||||
isar_models.TransactionType type;
|
|
||||||
Amount amount;
|
|
||||||
if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) {
|
|
||||||
// tx is sent to self
|
|
||||||
type = isar_models.TransactionType.sentToSelf;
|
|
||||||
amount =
|
|
||||||
amountSentFromWallet - amountReceivedInWallet - fee - changeAmount;
|
|
||||||
} else if (mySentFromAddresses.isNotEmpty) {
|
|
||||||
// outgoing tx
|
|
||||||
type = isar_models.TransactionType.outgoing;
|
|
||||||
amount = amountSentFromWallet - changeAmount - fee;
|
|
||||||
final possible =
|
|
||||||
outputAddresses.difference(myChangeReceivedOnAddresses).first;
|
|
||||||
|
|
||||||
if (transactionAddress.value != possible) {
|
|
||||||
transactionAddress = isar_models.Address(
|
|
||||||
walletId: walletId,
|
|
||||||
value: possible,
|
|
||||||
publicKey: [],
|
|
||||||
type: isar_models.AddressType.nonWallet,
|
|
||||||
derivationIndex: -1,
|
|
||||||
derivationPath: null,
|
|
||||||
subType: isar_models.AddressSubType.nonWallet,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final prevOutJson = Map<String, dynamic>.from(
|
||||||
|
(inputTx["vout"] as List).firstWhere((e) => e["n"] == vout)
|
||||||
|
as Map);
|
||||||
|
|
||||||
|
final prevOut = OutputV2.fromElectrumXJson(
|
||||||
|
prevOutJson,
|
||||||
|
decimalPlaces: coin.decimals,
|
||||||
|
walletOwns: false, // doesn't matter here as this is not saved
|
||||||
|
);
|
||||||
|
|
||||||
|
outpoint = OutpointV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
|
txid: txid,
|
||||||
|
vout: vout,
|
||||||
|
);
|
||||||
|
valueStringSats = prevOut.valueStringSats;
|
||||||
|
addresses.addAll(prevOut.addresses);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// incoming tx
|
|
||||||
type = isar_models.TransactionType.incoming;
|
|
||||||
amount = amountReceivedInWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<isar_models.Input> inputs = [];
|
InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
List<isar_models.Output> outputs = [];
|
scriptSigHex: map["scriptSig"]?["hex"] as String?,
|
||||||
|
sequence: map["sequence"] as int?,
|
||||||
for (final json in txData["vin"] as List) {
|
outpoint: outpoint,
|
||||||
bool isCoinBase = json['coinbase'] != null;
|
valueStringSats: valueStringSats,
|
||||||
final input = isar_models.Input(
|
addresses: addresses,
|
||||||
txid: json['txid'] as String,
|
witness: map["witness"] as String?,
|
||||||
vout: json['vout'] as int? ?? -1,
|
coinbase: coinbase,
|
||||||
scriptSig: json['scriptSig']?['hex'] as String?,
|
innerRedeemScriptAsm: map["innerRedeemscriptAsm"] as String?,
|
||||||
scriptSigAsm: json['scriptSig']?['asm'] as String?,
|
// don't know yet if wallet owns. Need addresses first
|
||||||
isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?,
|
walletOwns: false,
|
||||||
sequence: json['sequence'] as int?,
|
|
||||||
innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
|
||||||
|
wasSentFromThisWallet = true;
|
||||||
|
input = input.copyWith(walletOwns: true);
|
||||||
|
}
|
||||||
|
|
||||||
inputs.add(input);
|
inputs.add(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final json in txData["vout"] as List) {
|
// parse outputs
|
||||||
final output = isar_models.Output(
|
final List<OutputV2> outputs = [];
|
||||||
scriptPubKey: json['scriptPubKey']?['hex'] as String?,
|
for (final outputJson in txData["vout"] as List) {
|
||||||
scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?,
|
OutputV2 output = OutputV2.fromElectrumXJson(
|
||||||
scriptPubKeyType: json['scriptPubKey']?['type'] as String?,
|
Map<String, dynamic>.from(outputJson as Map),
|
||||||
scriptPubKeyAddress:
|
decimalPlaces: coin.decimals,
|
||||||
json["scriptPubKey"]?["addresses"]?[0] as String? ??
|
// don't know yet if wallet owns. Need addresses first
|
||||||
json['scriptPubKey']['type'] as String,
|
walletOwns: false,
|
||||||
value: Amount.fromDecimal(
|
|
||||||
Decimal.parse(json["value"].toString()),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
).raw.toInt(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// if output was to my wallet, add value to amount received
|
||||||
|
if (receivingAddresses
|
||||||
|
.intersection(output.addresses.toSet())
|
||||||
|
.isNotEmpty) {
|
||||||
|
wasReceivedInThisWallet = true;
|
||||||
|
amountReceivedInThisWallet += output.value;
|
||||||
|
output = output.copyWith(walletOwns: true);
|
||||||
|
} else if (changeAddresses
|
||||||
|
.intersection(output.addresses.toSet())
|
||||||
|
.isNotEmpty) {
|
||||||
|
wasReceivedInThisWallet = true;
|
||||||
|
changeAmountReceivedInThisWallet += output.value;
|
||||||
|
output = output.copyWith(walletOwns: true);
|
||||||
|
}
|
||||||
|
|
||||||
outputs.add(output);
|
outputs.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
final tx = isar_models.Transaction(
|
final totalOut = outputs
|
||||||
|
.map((e) => e.value)
|
||||||
|
.fold(BigInt.zero, (value, element) => value + element);
|
||||||
|
|
||||||
|
isar_models.TransactionType type;
|
||||||
|
isar_models.TransactionSubType subType =
|
||||||
|
isar_models.TransactionSubType.none;
|
||||||
|
|
||||||
|
// at least one input was owned by this wallet
|
||||||
|
if (wasSentFromThisWallet) {
|
||||||
|
type = isar_models.TransactionType.outgoing;
|
||||||
|
|
||||||
|
if (wasReceivedInThisWallet) {
|
||||||
|
if (changeAmountReceivedInThisWallet + amountReceivedInThisWallet ==
|
||||||
|
totalOut) {
|
||||||
|
// definitely sent all to self
|
||||||
|
type = isar_models.TransactionType.sentToSelf;
|
||||||
|
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
||||||
|
// most likely just a typical send
|
||||||
|
// do nothing here yet
|
||||||
|
}
|
||||||
|
|
||||||
|
// check vout 0 for special scripts
|
||||||
|
if (outputs.isNotEmpty) {
|
||||||
|
final output = outputs.first;
|
||||||
|
|
||||||
|
// check for fusion
|
||||||
|
if (BchUtils.isFUZE(output.scriptPubKeyHex.toUint8ListFromHex)) {
|
||||||
|
subType = isar_models.TransactionSubType.cashFusion;
|
||||||
|
} else {
|
||||||
|
// check other cases here such as SLP or cash tokens etc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (wasReceivedInThisWallet) {
|
||||||
|
// only found outputs owned by this wallet
|
||||||
|
type = isar_models.TransactionType.incoming;
|
||||||
|
} else {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Unexpected tx found (ignoring it): $txData",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tx = TransactionV2(
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
|
blockHash: txData["blockhash"] as String?,
|
||||||
|
hash: txData["hash"] as String,
|
||||||
txid: txData["txid"] as String,
|
txid: txData["txid"] as String,
|
||||||
timestamp: txData["blocktime"] as int? ??
|
|
||||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
||||||
type: type,
|
|
||||||
subType: isar_models.TransactionSubType.none,
|
|
||||||
amount: amount.raw.toInt(),
|
|
||||||
amountString: amount.toJsonString(),
|
|
||||||
fee: fee.raw.toInt(),
|
|
||||||
height: txData["height"] as int?,
|
height: txData["height"] as int?,
|
||||||
isCancelled: false,
|
version: txData["version"] as int,
|
||||||
isLelantus: false,
|
timestamp: txData["blocktime"] as int? ??
|
||||||
slateId: null,
|
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||||
otherData: null,
|
inputs: List.unmodifiable(inputs),
|
||||||
nonce: null,
|
outputs: List.unmodifiable(outputs),
|
||||||
inputs: inputs,
|
type: type,
|
||||||
outputs: outputs,
|
subType: subType,
|
||||||
numberOfMessages: null,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
txns.add(Tuple2(tx, transactionAddress));
|
txns.add(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.addNewTransactionData(txns, walletId);
|
await db.updateOrPutTransactionV2s(txns);
|
||||||
|
|
||||||
// quick hack to notify manager to call notifyListeners if
|
// quick hack to notify manager to call notifyListeners if
|
||||||
// transactions changed
|
// transactions changed
|
||||||
|
|
|
@ -22,6 +22,33 @@ import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||||
|
|
||||||
const String kReservedFusionAddress = "reserved_fusion_address";
|
const String kReservedFusionAddress = "reserved_fusion_address";
|
||||||
|
|
||||||
|
final kFusionServerInfoDefaults = Map<Coin, FusionInfo>.unmodifiable(const {
|
||||||
|
Coin.bitcoincash: FusionInfo(
|
||||||
|
host: "fusion.servo.cash",
|
||||||
|
port: 8789,
|
||||||
|
ssl: true,
|
||||||
|
// host: "cashfusion.stackwallet.com",
|
||||||
|
// port: 8787,
|
||||||
|
// ssl: false,
|
||||||
|
rounds: 0, // 0 is continuous
|
||||||
|
),
|
||||||
|
Coin.bitcoincashTestnet: FusionInfo(
|
||||||
|
host: "fusion.servo.cash",
|
||||||
|
port: 8789,
|
||||||
|
ssl: true,
|
||||||
|
// host: "cashfusion.stackwallet.com",
|
||||||
|
// port: 8787,
|
||||||
|
// ssl: false,
|
||||||
|
rounds: 0, // 0 is continuous
|
||||||
|
),
|
||||||
|
Coin.eCash: FusionInfo(
|
||||||
|
host: "fusion.tokamak.cash",
|
||||||
|
port: 8788,
|
||||||
|
ssl: true,
|
||||||
|
rounds: 0, // 0 is continuous
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
class FusionInfo {
|
class FusionInfo {
|
||||||
final String host;
|
final String host;
|
||||||
final int port;
|
final int port;
|
||||||
|
@ -37,16 +64,6 @@ class FusionInfo {
|
||||||
required this.rounds,
|
required this.rounds,
|
||||||
}) : assert(rounds >= 0);
|
}) : assert(rounds >= 0);
|
||||||
|
|
||||||
static const DEFAULTS = FusionInfo(
|
|
||||||
host: "fusion.servo.cash",
|
|
||||||
port: 8789,
|
|
||||||
ssl: true,
|
|
||||||
// host: "cashfusion.stackwallet.com",
|
|
||||||
// port: 8787,
|
|
||||||
// ssl: false,
|
|
||||||
rounds: 0, // 0 is continuous
|
|
||||||
);
|
|
||||||
|
|
||||||
factory FusionInfo.fromJsonString(String jsonString) {
|
factory FusionInfo.fromJsonString(String jsonString) {
|
||||||
final json = jsonDecode(jsonString);
|
final json = jsonDecode(jsonString);
|
||||||
return FusionInfo(
|
return FusionInfo(
|
||||||
|
@ -95,7 +112,7 @@ class FusionInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mixin for the BitcoinCashWallet class that adds CashFusion functionality.
|
/// A mixin that adds CashFusion functionality.
|
||||||
mixin FusionWalletInterface {
|
mixin FusionWalletInterface {
|
||||||
// Passed in wallet data.
|
// Passed in wallet data.
|
||||||
late final String _walletId;
|
late final String _walletId;
|
||||||
|
@ -630,14 +647,25 @@ mixin FusionWalletInterface {
|
||||||
// Loop through UTXOs, checking and adding valid ones.
|
// Loop through UTXOs, checking and adding valid ones.
|
||||||
for (final utxo in walletUtxos) {
|
for (final utxo in walletUtxos) {
|
||||||
final String addressString = utxo.address!;
|
final String addressString = utxo.address!;
|
||||||
final List<String> possibleAddresses = [addressString];
|
final Set<String> possibleAddresses = {};
|
||||||
|
|
||||||
if (bitbox.Address.detectFormat(addressString) ==
|
if (bitbox.Address.detectFormat(addressString) ==
|
||||||
bitbox.Address.formatCashAddr) {
|
bitbox.Address.formatCashAddr) {
|
||||||
possibleAddresses
|
possibleAddresses.add(addressString);
|
||||||
.add(bitbox.Address.toLegacyAddress(addressString));
|
possibleAddresses.add(
|
||||||
|
bitbox.Address.toLegacyAddress(addressString),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
possibleAddresses.add(bitbox.Address.toCashAddress(addressString));
|
possibleAddresses.add(addressString);
|
||||||
|
if (_coin == Coin.eCash) {
|
||||||
|
possibleAddresses.add(
|
||||||
|
bitbox.Address.toECashAddress(addressString),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
possibleAddresses.add(
|
||||||
|
bitbox.Address.toCashAddress(addressString),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch address to get pubkey
|
// Fetch address to get pubkey
|
||||||
|
@ -645,13 +673,13 @@ mixin FusionWalletInterface {
|
||||||
.getAddresses(_walletId)
|
.getAddresses(_walletId)
|
||||||
.filter()
|
.filter()
|
||||||
.anyOf<String,
|
.anyOf<String,
|
||||||
QueryBuilder<Address, Address, QAfterFilterCondition>>(
|
QueryBuilder<Address, Address, QAfterFilterCondition>>(
|
||||||
possibleAddresses, (q, e) => q.valueEqualTo(e))
|
possibleAddresses, (q, e) => q.valueEqualTo(e))
|
||||||
.and()
|
.and()
|
||||||
.group((q) => q
|
.group((q) => q
|
||||||
.subTypeEqualTo(AddressSubType.change)
|
.subTypeEqualTo(AddressSubType.change)
|
||||||
.or()
|
.or()
|
||||||
.subTypeEqualTo(AddressSubType.receiving))
|
.subTypeEqualTo(AddressSubType.receiving))
|
||||||
.and()
|
.and()
|
||||||
.typeEqualTo(AddressType.p2pkh)
|
.typeEqualTo(AddressType.p2pkh)
|
||||||
.findFirst();
|
.findFirst();
|
||||||
|
@ -681,6 +709,10 @@ mixin FusionWalletInterface {
|
||||||
|
|
||||||
// Fuse UTXOs.
|
// Fuse UTXOs.
|
||||||
try {
|
try {
|
||||||
|
if (coinList.isEmpty) {
|
||||||
|
throw Exception("Started with no coins");
|
||||||
|
}
|
||||||
|
|
||||||
await _mainFusionObject!.fuse(
|
await _mainFusionObject!.fuse(
|
||||||
inputsFromWallet: coinList,
|
inputsFromWallet: coinList,
|
||||||
network: _coin.isTestNet
|
network: _coin.isTestNet
|
||||||
|
@ -710,6 +742,16 @@ mixin FusionWalletInterface {
|
||||||
// Do the same for the UI state.
|
// Do the same for the UI state.
|
||||||
_uiState?.incrementFusionRoundsFailed();
|
_uiState?.incrementFusionRoundsFailed();
|
||||||
|
|
||||||
|
// If we have no coins, stop trying.
|
||||||
|
if (coinList.isEmpty ||
|
||||||
|
e.toString().contains("Started with no coins")) {
|
||||||
|
_updateStatus(
|
||||||
|
status: fusion.FusionStatus.failed,
|
||||||
|
info: "Started with no coins, stopping.");
|
||||||
|
_stopRequested = true;
|
||||||
|
_uiState?.setFailed(true, shouldNotify: true);
|
||||||
|
}
|
||||||
|
|
||||||
// If we fail too many times in a row, stop trying.
|
// If we fail too many times in a row, stop trying.
|
||||||
if (_failedFuseCount >= maxFailedFuseCount) {
|
if (_failedFuseCount >= maxFailedFuseCount) {
|
||||||
_updateStatus(
|
_updateStatus(
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:stackwallet/db/hive/db.dart';
|
import 'package:stackwallet/db/hive/db.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/tor_status_changed_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/tor_status_changed_event.dart';
|
||||||
|
@ -936,32 +938,74 @@ class Prefs extends ChangeNotifier {
|
||||||
|
|
||||||
// fusion server info
|
// fusion server info
|
||||||
|
|
||||||
FusionInfo _fusionServerInfo = FusionInfo.DEFAULTS;
|
Map<Coin, FusionInfo> _fusionServerInfo = {};
|
||||||
|
|
||||||
FusionInfo get fusionServerInfo => _fusionServerInfo;
|
FusionInfo getFusionServerInfo(Coin coin) {
|
||||||
|
return _fusionServerInfo[coin] ?? kFusionServerInfoDefaults[coin]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFusionServerInfo(Coin coin, FusionInfo fusionServerInfo) {
|
||||||
|
if (_fusionServerInfo[coin] != fusionServerInfo) {
|
||||||
|
_fusionServerInfo[coin] = fusionServerInfo;
|
||||||
|
|
||||||
set fusionServerInfo(FusionInfo fusionServerInfo) {
|
|
||||||
if (this.fusionServerInfo != fusionServerInfo) {
|
|
||||||
DB.instance.put<dynamic>(
|
DB.instance.put<dynamic>(
|
||||||
boxName: DB.boxNamePrefs,
|
boxName: DB.boxNamePrefs,
|
||||||
key: "fusionServerInfo",
|
key: "fusionServerInfoMap",
|
||||||
value: fusionServerInfo.toJsonString(),
|
value: _fusionServerInfo.map(
|
||||||
|
(key, value) => MapEntry(
|
||||||
|
key.name,
|
||||||
|
value.toJsonString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
_fusionServerInfo = fusionServerInfo;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<FusionInfo> _getFusionServerInfo() async {
|
Future<Map<Coin, FusionInfo>> _getFusionServerInfo() async {
|
||||||
final saved = await DB.instance.get<dynamic>(
|
final map = await DB.instance.get<dynamic>(
|
||||||
boxName: DB.boxNamePrefs,
|
boxName: DB.boxNamePrefs,
|
||||||
key: "fusionServerInfo",
|
key: "fusionServerInfoMap",
|
||||||
) as String?;
|
) as Map?;
|
||||||
|
|
||||||
try {
|
if (map == null) {
|
||||||
return FusionInfo.fromJsonString(saved!);
|
return _fusionServerInfo;
|
||||||
} catch (_) {
|
|
||||||
return FusionInfo.DEFAULTS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final actualMap = Map<String, String>.from(map).map(
|
||||||
|
(key, value) => MapEntry(
|
||||||
|
coinFromPrettyName(key),
|
||||||
|
FusionInfo.fromJsonString(value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// legacy bch check
|
||||||
|
if (actualMap[Coin.bitcoincash] == null ||
|
||||||
|
actualMap[Coin.bitcoincashTestnet] == null) {
|
||||||
|
final saved = await DB.instance.get<dynamic>(
|
||||||
|
boxName: DB.boxNamePrefs,
|
||||||
|
key: "fusionServerInfo",
|
||||||
|
) as String?;
|
||||||
|
|
||||||
|
if (saved != null) {
|
||||||
|
final bchInfo = FusionInfo.fromJsonString(saved);
|
||||||
|
actualMap[Coin.bitcoincash] = bchInfo;
|
||||||
|
actualMap[Coin.bitcoincashTestnet] = bchInfo;
|
||||||
|
unawaited(
|
||||||
|
DB.instance.put<dynamic>(
|
||||||
|
boxName: DB.boxNamePrefs,
|
||||||
|
key: "fusionServerInfoMap",
|
||||||
|
value: actualMap.map(
|
||||||
|
(key, value) => MapEntry(
|
||||||
|
key.name,
|
||||||
|
value.toJsonString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actualMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue