mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
Merge remote-tracking branch 'origin/staging' into arti
This commit is contained in:
commit
0745ee6617
82 changed files with 2264 additions and 479 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -16,6 +16,7 @@ abstract class AppConfig {
|
||||||
static const suffix = _suffix;
|
static const suffix = _suffix;
|
||||||
|
|
||||||
static String get appDefaultDataDirName => _appDataDirName;
|
static String get appDefaultDataDirName => _appDataDirName;
|
||||||
|
static String get shortDescriptionText => _shortDescriptionText;
|
||||||
static String get commitHash => _commitHash;
|
static String get commitHash => _commitHash;
|
||||||
|
|
||||||
static bool hasFeature(AppFeature feature) => _features.contains(feature);
|
static bool hasFeature(AppFeature feature) => _features.contains(feature);
|
||||||
|
|
|
@ -171,7 +171,7 @@ Future<void> migrateWalletsToIsar({
|
||||||
walletId: old.walletId,
|
walletId: old.walletId,
|
||||||
name: old.name,
|
name: old.name,
|
||||||
mainAddressType: AppConfig.getCryptoCurrencyFor(old.coinIdentifier)!
|
mainAddressType: AppConfig.getCryptoCurrencyFor(old.coinIdentifier)!
|
||||||
.primaryAddressType,
|
.defaultAddressType,
|
||||||
favouriteOrderIndex: favourites.indexOf(old.walletId),
|
favouriteOrderIndex: favourites.indexOf(old.walletId),
|
||||||
cachedChainHeight: walletBox.get(
|
cachedChainHeight: walletBox.get(
|
||||||
DBKeys.storedChainHeight,
|
DBKeys.storedChainHeight,
|
||||||
|
|
|
@ -2,12 +2,13 @@ import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import '../transaction.dart';
|
|
||||||
import 'input_v2.dart';
|
|
||||||
import 'output_v2.dart';
|
|
||||||
import '../../../../../utilities/amount/amount.dart';
|
import '../../../../../utilities/amount/amount.dart';
|
||||||
import '../../../../../utilities/extensions/extensions.dart';
|
import '../../../../../utilities/extensions/extensions.dart';
|
||||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
import '../../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||||
|
import '../transaction.dart';
|
||||||
|
import 'input_v2.dart';
|
||||||
|
import 'output_v2.dart';
|
||||||
|
|
||||||
part 'transaction_v2.g.dart';
|
part 'transaction_v2.g.dart';
|
||||||
|
|
||||||
|
@ -56,17 +57,52 @@ class TransactionV2 {
|
||||||
required this.otherData,
|
required this.otherData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TransactionV2 copyWith({
|
||||||
|
String? walletId,
|
||||||
|
String? txid,
|
||||||
|
String? hash,
|
||||||
|
int? timestamp,
|
||||||
|
int? height,
|
||||||
|
String? blockHash,
|
||||||
|
int? version,
|
||||||
|
List<InputV2>? inputs,
|
||||||
|
List<OutputV2>? outputs,
|
||||||
|
TransactionType? type,
|
||||||
|
TransactionSubType? subType,
|
||||||
|
String? otherData,
|
||||||
|
}) {
|
||||||
|
return TransactionV2(
|
||||||
|
walletId: walletId ?? this.walletId,
|
||||||
|
txid: txid ?? this.txid,
|
||||||
|
hash: hash ?? this.hash,
|
||||||
|
timestamp: timestamp ?? this.timestamp,
|
||||||
|
height: height ?? this.height,
|
||||||
|
blockHash: blockHash ?? this.blockHash,
|
||||||
|
version: version ?? this.version,
|
||||||
|
inputs: inputs ?? this.inputs,
|
||||||
|
outputs: outputs ?? this.outputs,
|
||||||
|
type: type ?? this.type,
|
||||||
|
subType: subType ?? this.subType,
|
||||||
|
otherData: otherData ?? this.otherData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int? get size => _getFromOtherData(key: TxV2OdKeys.size) as int?;
|
||||||
|
int? get vSize => _getFromOtherData(key: TxV2OdKeys.vSize) as int?;
|
||||||
|
|
||||||
bool get isEpiccashTransaction =>
|
bool get isEpiccashTransaction =>
|
||||||
_getFromOtherData(key: "isEpiccashTransaction") == true;
|
_getFromOtherData(key: TxV2OdKeys.isEpiccashTransaction) == true;
|
||||||
int? get numberOfMessages =>
|
int? get numberOfMessages =>
|
||||||
_getFromOtherData(key: "numberOfMessages") as int?;
|
_getFromOtherData(key: TxV2OdKeys.numberOfMessages) as int?;
|
||||||
String? get slateId => _getFromOtherData(key: "slateId") as String?;
|
String? get slateId => _getFromOtherData(key: TxV2OdKeys.slateId) as String?;
|
||||||
String? get onChainNote => _getFromOtherData(key: "onChainNote") as String?;
|
String? get onChainNote =>
|
||||||
bool get isCancelled => _getFromOtherData(key: "isCancelled") == true;
|
_getFromOtherData(key: TxV2OdKeys.onChainNote) as String?;
|
||||||
|
bool get isCancelled =>
|
||||||
|
_getFromOtherData(key: TxV2OdKeys.isCancelled) == true;
|
||||||
|
|
||||||
String? get contractAddress =>
|
String? get contractAddress =>
|
||||||
_getFromOtherData(key: "contractAddress") as String?;
|
_getFromOtherData(key: TxV2OdKeys.contractAddress) as String?;
|
||||||
int? get nonce => _getFromOtherData(key: "nonce") as int?;
|
int? get nonce => _getFromOtherData(key: TxV2OdKeys.nonce) as int?;
|
||||||
|
|
||||||
int getConfirmations(int currentChainHeight) {
|
int getConfirmations(int currentChainHeight) {
|
||||||
if (height == null || height! <= 0) return 0;
|
if (height == null || height! <= 0) return 0;
|
||||||
|
@ -145,7 +181,7 @@ class TransactionV2 {
|
||||||
Amount? _getOverrideFee() {
|
Amount? _getOverrideFee() {
|
||||||
try {
|
try {
|
||||||
return Amount.fromSerializedJsonString(
|
return Amount.fromSerializedJsonString(
|
||||||
_getFromOtherData(key: "overrideFee") as String,
|
_getFromOtherData(key: TxV2OdKeys.overrideFee) as String,
|
||||||
);
|
);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -249,3 +285,16 @@ class TransactionV2 {
|
||||||
')';
|
')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract final class TxV2OdKeys {
|
||||||
|
static const size = "size";
|
||||||
|
static const vSize = "vSize";
|
||||||
|
static const isEpiccashTransaction = "isEpiccashTransaction";
|
||||||
|
static const numberOfMessages = "numberOfMessages";
|
||||||
|
static const slateId = "slateId";
|
||||||
|
static const onChainNote = "onChainNote";
|
||||||
|
static const isCancelled = "isCancelled";
|
||||||
|
static const contractAddress = "contractAddress";
|
||||||
|
static const nonce = "nonce";
|
||||||
|
static const overrideFee = "overrideFee";
|
||||||
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
||||||
String _searchTerm = "";
|
String _searchTerm = "";
|
||||||
|
|
||||||
final _coinsTestnet = [
|
final _coinsTestnet = [
|
||||||
...AppConfig.coins.where((e) => e.network == CryptoCurrencyNetwork.test),
|
...AppConfig.coins.where((e) => e.network.isTestNet),
|
||||||
];
|
];
|
||||||
final _coins = [
|
final _coins = [
|
||||||
...AppConfig.coins.where((e) => e.network == CryptoCurrencyNetwork.main),
|
...AppConfig.coins.where((e) => e.network == CryptoCurrencyNetwork.main),
|
||||||
|
|
|
@ -11,11 +11,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
import '../../app_config.dart';
|
import '../../app_config.dart';
|
||||||
import '../../models/isar/models/blockchain_data/address.dart';
|
import '../../models/isar/models/blockchain_data/address.dart';
|
||||||
import '../../models/isar/models/contact_entry.dart';
|
import '../../models/isar/models/contact_entry.dart';
|
||||||
import 'subviews/add_address_book_entry_view.dart';
|
|
||||||
import 'subviews/address_book_filter_view.dart';
|
|
||||||
import '../../providers/db/main_db_provider.dart';
|
import '../../providers/db/main_db_provider.dart';
|
||||||
import '../../providers/global/address_book_service_provider.dart';
|
import '../../providers/global/address_book_service_provider.dart';
|
||||||
import '../../providers/providers.dart';
|
import '../../providers/providers.dart';
|
||||||
|
@ -35,6 +34,8 @@ import '../../widgets/icon_widgets/x_icon.dart';
|
||||||
import '../../widgets/rounded_white_container.dart';
|
import '../../widgets/rounded_white_container.dart';
|
||||||
import '../../widgets/stack_text_field.dart';
|
import '../../widgets/stack_text_field.dart';
|
||||||
import '../../widgets/textfield_icon_button.dart';
|
import '../../widgets/textfield_icon_button.dart';
|
||||||
|
import 'subviews/add_address_book_entry_view.dart';
|
||||||
|
import 'subviews/address_book_filter_view.dart';
|
||||||
|
|
||||||
class AddressBookView extends ConsumerStatefulWidget {
|
class AddressBookView extends ConsumerStatefulWidget {
|
||||||
const AddressBookView({
|
const AddressBookView({
|
||||||
|
@ -67,7 +68,7 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
|
||||||
if (widget.coin == null) {
|
if (widget.coin == null) {
|
||||||
final coins = [...AppConfig.coins];
|
final coins = [...AppConfig.coins];
|
||||||
coins.removeWhere(
|
coins.removeWhere(
|
||||||
(e) => e is Firo && e.network == CryptoCurrencyNetwork.test,
|
(e) => e is Firo && e.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
final bool showTestNet =
|
final bool showTestNet =
|
||||||
|
|
|
@ -43,7 +43,7 @@ class _AddressBookFilterViewState extends ConsumerState<AddressBookFilterView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
final coins = [...AppConfig.coins];
|
final coins = [...AppConfig.coins];
|
||||||
coins.removeWhere(
|
coins.removeWhere(
|
||||||
(e) => e is Firo && e.network == CryptoCurrencyNetwork.test,
|
(e) => e is Firo && e.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
final showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins;
|
final showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins;
|
||||||
|
|
|
@ -13,8 +13,9 @@ import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import '../../../providers/global/prefs_provider.dart';
|
|
||||||
import '../../../app_config.dart';
|
import '../../../app_config.dart';
|
||||||
|
import '../../../providers/global/prefs_provider.dart';
|
||||||
import '../../../themes/coin_image_provider.dart';
|
import '../../../themes/coin_image_provider.dart';
|
||||||
import '../../../themes/stack_colors.dart';
|
import '../../../themes/stack_colors.dart';
|
||||||
import '../../../utilities/constants.dart';
|
import '../../../utilities/constants.dart';
|
||||||
|
@ -29,7 +30,7 @@ class CoinSelectSheet extends StatelessWidget {
|
||||||
final maxHeight = MediaQuery.of(context).size.height * 0.60;
|
final maxHeight = MediaQuery.of(context).size.height * 0.60;
|
||||||
final coins_ = [...AppConfig.coins];
|
final coins_ = [...AppConfig.coins];
|
||||||
coins_.removeWhere(
|
coins_.removeWhere(
|
||||||
(e) => e is Firo && e.network == CryptoCurrencyNetwork.test,
|
(e) => e is Firo && e.network.isTestNet,
|
||||||
);
|
);
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
|
@ -15,11 +15,11 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'coin_select_sheet.dart';
|
|
||||||
|
import '../../../app_config.dart';
|
||||||
import '../../../providers/providers.dart';
|
import '../../../providers/providers.dart';
|
||||||
// import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart';
|
// import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart';
|
||||||
import '../../../providers/ui/address_book_providers/address_entry_data_provider.dart';
|
import '../../../providers/ui/address_book_providers/address_entry_data_provider.dart';
|
||||||
import '../../../app_config.dart';
|
|
||||||
import '../../../themes/coin_icon_provider.dart';
|
import '../../../themes/coin_icon_provider.dart';
|
||||||
import '../../../themes/stack_colors.dart';
|
import '../../../themes/stack_colors.dart';
|
||||||
import '../../../utilities/address_utils.dart';
|
import '../../../utilities/address_utils.dart';
|
||||||
|
@ -36,6 +36,7 @@ import '../../../widgets/icon_widgets/qrcode_icon.dart';
|
||||||
import '../../../widgets/icon_widgets/x_icon.dart';
|
import '../../../widgets/icon_widgets/x_icon.dart';
|
||||||
import '../../../widgets/stack_text_field.dart';
|
import '../../../widgets/stack_text_field.dart';
|
||||||
import '../../../widgets/textfield_icon_button.dart';
|
import '../../../widgets/textfield_icon_button.dart';
|
||||||
|
import 'coin_select_sheet.dart';
|
||||||
|
|
||||||
class NewContactAddressEntryForm extends ConsumerStatefulWidget {
|
class NewContactAddressEntryForm extends ConsumerStatefulWidget {
|
||||||
const NewContactAddressEntryForm({
|
const NewContactAddressEntryForm({
|
||||||
|
@ -92,7 +93,7 @@ class _NewContactAddressEntryFormState
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
coins = [...AppConfig.coins];
|
coins = [...AppConfig.coins];
|
||||||
coins.removeWhere(
|
coins.removeWhere(
|
||||||
(e) => e is Firo && e.network == CryptoCurrencyNetwork.test,
|
(e) => e is Firo && e.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
final showTestNet =
|
final showTestNet =
|
||||||
|
|
|
@ -694,7 +694,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
||||||
selectedSumInt.toAmountAsRaw(
|
selectedSumInt.toAmountAsRaw(
|
||||||
fractionDigits: coin.fractionDigits,
|
fractionDigits: coin.fractionDigits,
|
||||||
);
|
);
|
||||||
return Text(
|
return SelectableText(
|
||||||
ref
|
ref
|
||||||
.watch(pAmountFormatter(coin))
|
.watch(pAmountFormatter(coin))
|
||||||
.format(selectedSum),
|
.format(selectedSum),
|
||||||
|
@ -739,7 +739,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
||||||
"Amount to send",
|
"Amount to send",
|
||||||
style: STextStyles.w600_14(context),
|
style: STextStyles.w600_14(context),
|
||||||
),
|
),
|
||||||
Text(
|
SelectableText(
|
||||||
ref
|
ref
|
||||||
.watch(pAmountFormatter(coin))
|
.watch(pAmountFormatter(coin))
|
||||||
.format(widget.requestedTotal!),
|
.format(widget.requestedTotal!),
|
||||||
|
|
|
@ -213,7 +213,7 @@ class IntroAboutText extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
"An open-source, multicoin wallet for everyone",
|
AppConfig.shortDescriptionText,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: !isDesktop
|
style: !isDesktop
|
||||||
? STextStyles.subtitle(context)
|
? STextStyles.subtitle(context)
|
||||||
|
|
|
@ -188,7 +188,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
||||||
wallet is Bip39HDWallet &&
|
wallet is Bip39HDWallet &&
|
||||||
wallet.supportedAddressTypes.length > 1);
|
wallet.supportedAddressTypes.length > 1);
|
||||||
|
|
||||||
_walletAddressTypes.add(coin.primaryAddressType);
|
_walletAddressTypes.add(wallet.info.mainAddressType);
|
||||||
|
|
||||||
if (_showMultiType) {
|
if (_showMultiType) {
|
||||||
if (_supportsSpark) {
|
if (_supportsSpark) {
|
||||||
|
@ -197,7 +197,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
||||||
_walletAddressTypes.addAll(
|
_walletAddressTypes.addAll(
|
||||||
(wallet as Bip39HDWallet)
|
(wallet as Bip39HDWallet)
|
||||||
.supportedAddressTypes
|
.supportedAddressTypes
|
||||||
.where((e) => e != coin.primaryAddressType),
|
.where((e) => e != wallet.info.mainAddressType),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -392,8 +392,7 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
ref.read(pValidSparkSendToAddress.notifier).state =
|
ref.read(pValidSparkSendToAddress.notifier).state =
|
||||||
SparkInterface.validateSparkAddress(
|
SparkInterface.validateSparkAddress(
|
||||||
address: address ?? "",
|
address: address ?? "",
|
||||||
isTestNet:
|
isTestNet: wallet.cryptoCurrency.network.isTestNet,
|
||||||
wallet.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
|
|
||||||
import '../../../themes/coin_image_provider.dart';
|
import '../../../themes/coin_image_provider.dart';
|
||||||
import '../../../themes/stack_colors.dart';
|
import '../../../themes/stack_colors.dart';
|
||||||
import '../../../utilities/assets.dart';
|
import '../../../utilities/assets.dart';
|
||||||
|
@ -64,6 +65,7 @@ class _RestoringDialogState extends ConsumerState<SendingTransactionDialog> {
|
||||||
|
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
return DesktopDialog(
|
return DesktopDialog(
|
||||||
|
maxHeight: assetPath.endsWith(".gif") ? double.infinity : null,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(40),
|
padding: const EdgeInsets.all(40),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -77,8 +79,10 @@ class _RestoringDialogState extends ConsumerState<SendingTransactionDialog> {
|
||||||
height: 40,
|
height: 40,
|
||||||
),
|
),
|
||||||
assetPath.endsWith(".gif")
|
assetPath.endsWith(".gif")
|
||||||
? Image.file(
|
? Flexible(
|
||||||
File(assetPath),
|
child: Image.file(
|
||||||
|
File(assetPath),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: ProgressAndSuccess(
|
: ProgressAndSuccess(
|
||||||
controller: _progressAndSuccessController!,
|
controller: _progressAndSuccessController!,
|
||||||
|
|
|
@ -48,18 +48,17 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
|
||||||
final _searchFocusNode = FocusNode();
|
final _searchFocusNode = FocusNode();
|
||||||
|
|
||||||
void onTap(int index) {
|
void onTap(int index) {
|
||||||
|
if (currenciesWithoutSelected[index] == current || current.isEmpty) {
|
||||||
|
// ignore if already selected currency
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current = currenciesWithoutSelected[index];
|
||||||
|
currenciesWithoutSelected.remove(current);
|
||||||
|
currenciesWithoutSelected.insert(0, current);
|
||||||
|
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
setState(() {
|
setState(() {});
|
||||||
current = currenciesWithoutSelected[index];
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (currenciesWithoutSelected[index] == current || current.isEmpty) {
|
|
||||||
// ignore if already selected currency
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
current = currenciesWithoutSelected[index];
|
|
||||||
currenciesWithoutSelected.remove(current);
|
|
||||||
currenciesWithoutSelected.insert(0, current);
|
|
||||||
ref.read(prefsChangeNotifierProvider).currency = current;
|
ref.read(prefsChangeNotifierProvider).currency = current;
|
||||||
|
|
||||||
if (ref.read(prefsChangeNotifierProvider).externalCalls) {
|
if (ref.read(prefsChangeNotifierProvider).externalCalls) {
|
||||||
|
@ -104,13 +103,7 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
_searchController = TextEditingController();
|
_searchController = TextEditingController();
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
currenciesWithoutSelected =
|
|
||||||
ref.read(baseCurrenciesProvider).map.keys.toList();
|
|
||||||
current = ref.read(prefsChangeNotifierProvider).currency;
|
current = ref.read(prefsChangeNotifierProvider).currency;
|
||||||
if (current.isNotEmpty) {
|
|
||||||
currenciesWithoutSelected.remove(current);
|
|
||||||
currenciesWithoutSelected.insert(0, current);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -129,16 +122,16 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
|
||||||
if (!isDesktop) {
|
if (!isDesktop) {
|
||||||
current = ref
|
current = ref
|
||||||
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
|
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
|
||||||
|
}
|
||||||
|
|
||||||
currenciesWithoutSelected = ref
|
currenciesWithoutSelected = ref
|
||||||
.watch(baseCurrenciesProvider.select((value) => value.map))
|
.watch(baseCurrenciesProvider.select((value) => value.map))
|
||||||
.keys
|
.keys
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (current.isNotEmpty) {
|
if (current.isNotEmpty) {
|
||||||
currenciesWithoutSelected.remove(current);
|
currenciesWithoutSelected.remove(current);
|
||||||
currenciesWithoutSelected.insert(0, current);
|
currenciesWithoutSelected.insert(0, current);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currenciesWithoutSelected = _filtered();
|
currenciesWithoutSelected = _filtered();
|
||||||
|
|
|
@ -13,8 +13,8 @@ import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'add_edit_node_view.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import '../../sub_widgets/nodes_list.dart';
|
|
||||||
import '../../../../themes/coin_icon_provider.dart';
|
import '../../../../themes/coin_icon_provider.dart';
|
||||||
import '../../../../themes/stack_colors.dart';
|
import '../../../../themes/stack_colors.dart';
|
||||||
import '../../../../utilities/assets.dart';
|
import '../../../../utilities/assets.dart';
|
||||||
|
@ -26,7 +26,8 @@ import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import '../../../../widgets/custom_buttons/blue_text_button.dart';
|
import '../../../../widgets/custom_buttons/blue_text_button.dart';
|
||||||
import '../../../../widgets/desktop/desktop_dialog.dart';
|
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||||
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import '../../sub_widgets/nodes_list.dart';
|
||||||
|
import 'add_edit_node_view.dart';
|
||||||
|
|
||||||
class CoinNodesView extends ConsumerStatefulWidget {
|
class CoinNodesView extends ConsumerStatefulWidget {
|
||||||
const CoinNodesView({
|
const CoinNodesView({
|
||||||
|
@ -59,7 +60,10 @@ class _CoinNodesViewState extends ConsumerState<CoinNodesView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
return DesktopDialog(
|
return DesktopDialog(
|
||||||
|
maxHeight: null,
|
||||||
|
maxWidth: 580,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
@ -129,11 +133,15 @@ class _CoinNodesViewState extends ConsumerState<CoinNodesView> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
),
|
),
|
||||||
Padding(
|
Flexible(
|
||||||
padding: const EdgeInsets.all(20),
|
child: Padding(
|
||||||
child: NodesList(
|
padding: const EdgeInsets.all(20),
|
||||||
coin: widget.coin,
|
child: SingleChildScrollView(
|
||||||
popBackToRoute: CoinNodesView.routeName,
|
child: NodesList(
|
||||||
|
coin: widget.coin,
|
||||||
|
popBackToRoute: CoinNodesView.routeName,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -44,7 +44,7 @@ class _ManageNodesViewState extends ConsumerState<ManageNodesView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
_coins = _coins.toList();
|
_coins = _coins.toList();
|
||||||
_coins.removeWhere(
|
_coins.removeWhere(
|
||||||
(e) => e is Firo && e.network == CryptoCurrencyNetwork.test,
|
(e) => e is Firo && e.network.isTestNet,
|
||||||
);
|
);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
|
@ -814,7 +814,7 @@ abstract class SWB {
|
||||||
coinName: coin.identifier,
|
coinName: coin.identifier,
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
name: walletName,
|
name: walletName,
|
||||||
mainAddressType: coin.primaryAddressType,
|
mainAddressType: coin.defaultAddressType,
|
||||||
restoreHeight: walletbackup['restoreHeight'] as int? ?? 0,
|
restoreHeight: walletbackup['restoreHeight'] as int? ?? 0,
|
||||||
otherDataJsonString: otherData == null ? null : jsonEncode(otherData),
|
otherDataJsonString: otherData == null ? null : jsonEncode(otherData),
|
||||||
cachedChainHeight: walletbackup['storedChainHeight'] as int? ?? 0,
|
cachedChainHeight: walletbackup['storedChainHeight'] as int? ?? 0,
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../../../providers/db/main_db_provider.dart';
|
||||||
|
import '../../../../themes/stack_colors.dart';
|
||||||
|
import '../../../../utilities/text_styles.dart';
|
||||||
|
import '../../../../wallets/isar/models/wallet_info.dart';
|
||||||
|
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||||
|
import '../../../../widgets/background.dart';
|
||||||
|
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
|
import '../../../../widgets/custom_buttons/draggable_switch_button.dart';
|
||||||
|
|
||||||
|
class RbfSettingsView extends ConsumerStatefulWidget {
|
||||||
|
const RbfSettingsView({
|
||||||
|
super.key,
|
||||||
|
required this.walletId,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const String routeName = "/rbfSettings";
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<RbfSettingsView> createState() => _RbfSettingsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RbfSettingsViewState extends ConsumerState<RbfSettingsView> {
|
||||||
|
bool _switchRbfToggledLock = false; // Mutex.
|
||||||
|
Future<void> _switchRbfToggled(bool newValue) async {
|
||||||
|
if (_switchRbfToggledLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_switchRbfToggledLock = true; // Lock mutex.
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Toggle enableOptInRbf in wallet info.
|
||||||
|
await ref.read(pWalletInfo(widget.walletId)).updateOtherData(
|
||||||
|
newEntries: {
|
||||||
|
WalletInfoKeys.enableOptInRbf: newValue,
|
||||||
|
},
|
||||||
|
isar: ref.read(mainDBProvider).isar,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
// ensure _switchRbfToggledLock is set to false no matter what
|
||||||
|
_switchRbfToggledLock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Background(
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: AppBarBackButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
"RBF settings",
|
||||||
|
style: STextStyles.navBarTitle(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 3),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 40,
|
||||||
|
child: DraggableSwitchButton(
|
||||||
|
isOn: ref.watch(
|
||||||
|
pWalletInfo(widget.walletId)
|
||||||
|
.select((value) => value.otherData),
|
||||||
|
)[WalletInfoKeys.enableOptInRbf] as bool? ??
|
||||||
|
false,
|
||||||
|
onValueChanged: _switchRbfToggled,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Enable opt-in RBF",
|
||||||
|
style: STextStyles.w600_20(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,11 +11,15 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../../../providers/providers.dart';
|
||||||
import '../../../../route_generator.dart';
|
import '../../../../route_generator.dart';
|
||||||
import '../../../../themes/stack_colors.dart';
|
import '../../../../themes/stack_colors.dart';
|
||||||
import '../../../../utilities/constants.dart';
|
import '../../../../utilities/constants.dart';
|
||||||
import '../../../../utilities/text_styles.dart';
|
import '../../../../utilities/text_styles.dart';
|
||||||
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||||
|
import '../../../../wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart';
|
||||||
|
import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart';
|
||||||
|
import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||||
import '../../../../widgets/background.dart';
|
import '../../../../widgets/background.dart';
|
||||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import '../../../../widgets/rounded_white_container.dart';
|
import '../../../../widgets/rounded_white_container.dart';
|
||||||
|
@ -23,6 +27,7 @@ import '../../../../widgets/stack_dialog.dart';
|
||||||
import '../../../pinpad_views/lock_screen_view.dart';
|
import '../../../pinpad_views/lock_screen_view.dart';
|
||||||
import 'delete_wallet_warning_view.dart';
|
import 'delete_wallet_warning_view.dart';
|
||||||
import 'lelantus_settings_view.dart';
|
import 'lelantus_settings_view.dart';
|
||||||
|
import 'rbf_settings_view.dart';
|
||||||
import 'rename_wallet_view.dart';
|
import 'rename_wallet_view.dart';
|
||||||
import 'spark_info.dart';
|
import 'spark_info.dart';
|
||||||
|
|
||||||
|
@ -183,73 +188,115 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
if (ref.watch(pWallets).getWallet(walletId)
|
||||||
height: 8,
|
is LelantusInterface)
|
||||||
),
|
const SizedBox(
|
||||||
RoundedWhiteContainer(
|
height: 8,
|
||||||
padding: const EdgeInsets.all(0),
|
),
|
||||||
child: RawMaterialButton(
|
if (ref.watch(pWallets).getWallet(walletId)
|
||||||
shape: RoundedRectangleBorder(
|
is LelantusInterface)
|
||||||
borderRadius: BorderRadius.circular(
|
RoundedWhiteContainer(
|
||||||
Constants.size.circularBorderRadius,
|
padding: const EdgeInsets.all(0),
|
||||||
|
child: RawMaterialButton(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
onPressed: () {
|
||||||
onPressed: () {
|
Navigator.of(context).pushNamed(
|
||||||
Navigator.of(context).pushNamed(
|
LelantusSettingsView.routeName,
|
||||||
LelantusSettingsView.routeName,
|
arguments: walletId,
|
||||||
arguments: walletId,
|
);
|
||||||
);
|
},
|
||||||
},
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 12.0,
|
||||||
horizontal: 12.0,
|
vertical: 20,
|
||||||
vertical: 20,
|
),
|
||||||
),
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text(
|
"Lelantus settings",
|
||||||
"Lelantus settings",
|
style: STextStyles.titleBold12(context),
|
||||||
style: STextStyles.titleBold12(context),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (ref.watch(pWallets).getWallet(walletId) is SparkInterface)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
RoundedWhiteContainer(
|
if (ref.watch(pWallets).getWallet(walletId) is SparkInterface)
|
||||||
padding: const EdgeInsets.all(0),
|
RoundedWhiteContainer(
|
||||||
child: RawMaterialButton(
|
padding: const EdgeInsets.all(0),
|
||||||
shape: RoundedRectangleBorder(
|
child: RawMaterialButton(
|
||||||
borderRadius: BorderRadius.circular(
|
shape: RoundedRectangleBorder(
|
||||||
Constants.size.circularBorderRadius,
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
onPressed: () {
|
||||||
onPressed: () {
|
Navigator.of(context).pushNamed(
|
||||||
Navigator.of(context).pushNamed(
|
SparkInfoView.routeName,
|
||||||
SparkInfoView.routeName,
|
);
|
||||||
);
|
},
|
||||||
},
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 12.0,
|
||||||
horizontal: 12.0,
|
vertical: 20,
|
||||||
vertical: 20,
|
),
|
||||||
),
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text(
|
"Spark info",
|
||||||
"Spark info",
|
style: STextStyles.titleBold12(context),
|
||||||
style: STextStyles.titleBold12(context),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (ref.watch(pWallets).getWallet(walletId) is RbfInterface)
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
if (ref.watch(pWallets).getWallet(walletId) is RbfInterface)
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
child: RawMaterialButton(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
RbfSettingsView.routeName,
|
||||||
|
arguments: walletId,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0,
|
||||||
|
vertical: 20,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"RBF settings",
|
||||||
|
style: STextStyles.titleBold12(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Stack Wallet.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Cypher Stack
|
||||||
|
* All Rights Reserved.
|
||||||
|
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||||
|
* Generated by Cypher Stack on 2023-05-26
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
|
import '../../../../pages_desktop_specific/desktop_home_view.dart';
|
||||||
|
import '../../../../providers/providers.dart';
|
||||||
|
import '../../../../route_generator.dart';
|
||||||
|
import '../../../../themes/stack_colors.dart';
|
||||||
|
import '../../../../utilities/amount/amount.dart';
|
||||||
|
import '../../../../utilities/amount/amount_formatter.dart';
|
||||||
|
import '../../../../utilities/show_loading.dart';
|
||||||
|
import '../../../../utilities/text_styles.dart';
|
||||||
|
import '../../../../utilities/util.dart';
|
||||||
|
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||||
|
import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart';
|
||||||
|
import '../../../../widgets/background.dart';
|
||||||
|
import '../../../../widgets/conditional_parent.dart';
|
||||||
|
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
|
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||||
|
import '../../../../widgets/desktop/primary_button.dart';
|
||||||
|
import '../../../../widgets/detail_item.dart';
|
||||||
|
import '../../../../widgets/fee_slider.dart';
|
||||||
|
import '../../../../widgets/rounded_white_container.dart';
|
||||||
|
import '../../../../widgets/stack_dialog.dart';
|
||||||
|
import '../../../send_view/confirm_transaction_view.dart';
|
||||||
|
|
||||||
|
class BoostTransactionView extends ConsumerStatefulWidget {
|
||||||
|
const BoostTransactionView({
|
||||||
|
super.key,
|
||||||
|
required this.transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const String routeName = "/boostTransaction";
|
||||||
|
|
||||||
|
final TransactionV2 transaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<BoostTransactionView> createState() =>
|
||||||
|
_BoostTransactionViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoostTransactionViewState extends ConsumerState<BoostTransactionView> {
|
||||||
|
late final bool isDesktop;
|
||||||
|
late final String walletId;
|
||||||
|
late final TransactionV2 _transaction;
|
||||||
|
late final Amount fee;
|
||||||
|
late final Amount amount;
|
||||||
|
late final int rate;
|
||||||
|
|
||||||
|
BigInt? customFee;
|
||||||
|
|
||||||
|
int _newRate = 0;
|
||||||
|
|
||||||
|
bool _previewTxnLock = false;
|
||||||
|
Future<void> _previewTxn() async {
|
||||||
|
if (_previewTxnLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_previewTxnLock = true;
|
||||||
|
try {
|
||||||
|
if (_newRate <= rate) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const StackOkDialog(
|
||||||
|
title: "Error",
|
||||||
|
message: "New fee rate must be greater than the current rate.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final wallet = (ref.read(pWallets).getWallet(walletId) as RbfInterface);
|
||||||
|
|
||||||
|
Exception? ex;
|
||||||
|
// build new tx and show loading/tx generation
|
||||||
|
final txData = await showLoading(
|
||||||
|
whileFuture: wallet.prepareRbfSend(
|
||||||
|
oldTransaction: _transaction,
|
||||||
|
newRate: _newRate,
|
||||||
|
),
|
||||||
|
context: context,
|
||||||
|
message: "Preparing RBF Transaction...",
|
||||||
|
onException: (e) => ex = e,
|
||||||
|
);
|
||||||
|
|
||||||
|
// on failure show error message
|
||||||
|
if (txData == null && mounted) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: "RBF send error",
|
||||||
|
message: ex?.toString() ?? "Unknown error found",
|
||||||
|
maxWidth: 600,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// on success show confirm tx screen
|
||||||
|
if (isDesktop && mounted) {
|
||||||
|
unawaited(
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DesktopDialog(
|
||||||
|
maxHeight: MediaQuery.of(context).size.height - 64,
|
||||||
|
maxWidth: 580,
|
||||||
|
child: ConfirmTransactionView(
|
||||||
|
txData: txData!,
|
||||||
|
walletId: walletId,
|
||||||
|
onSuccess: () {},
|
||||||
|
// isPaynymTransaction: isPaynymSend, TODO ?
|
||||||
|
routeOnSuccessName: DesktopHomeView.routeName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (mounted) {
|
||||||
|
unawaited(
|
||||||
|
Navigator.of(context).push(
|
||||||
|
RouteGenerator.getRoute(
|
||||||
|
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
|
||||||
|
builder: (_) => ConfirmTransactionView(
|
||||||
|
txData: txData!,
|
||||||
|
walletId: walletId,
|
||||||
|
// isPaynymTransaction: isPaynymSend, TODO ?
|
||||||
|
onSuccess: () {},
|
||||||
|
),
|
||||||
|
settings: const RouteSettings(
|
||||||
|
name: ConfirmTransactionView.routeName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_previewTxnLock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
isDesktop = Util.isDesktop;
|
||||||
|
_transaction = widget.transaction;
|
||||||
|
walletId = _transaction.walletId;
|
||||||
|
fee = _transaction.getFee(
|
||||||
|
fractionDigits: ref.read(pWalletCoin(walletId)).fractionDigits,
|
||||||
|
);
|
||||||
|
amount = _transaction.getAmountSentFromThisWallet(
|
||||||
|
fractionDigits: ref.read(pWalletCoin(walletId)).fractionDigits,
|
||||||
|
);
|
||||||
|
rate = (fee.raw ~/ BigInt.from(_transaction.vSize!)).toInt();
|
||||||
|
_newRate = rate + 1;
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final coin = ref.watch(pWalletCoin(walletId));
|
||||||
|
final String feeString = ref.watch(pAmountFormatter(coin)).format(
|
||||||
|
fee,
|
||||||
|
);
|
||||||
|
final String amountString = ref.watch(pAmountFormatter(coin)).format(
|
||||||
|
amount,
|
||||||
|
);
|
||||||
|
final String feeRateString = "$rate sats/vByte";
|
||||||
|
|
||||||
|
return ConditionalParent(
|
||||||
|
condition: !isDesktop,
|
||||||
|
builder: (child) => Background(
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).extension<StackColors>()!.background,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).extension<StackColors>()!.background,
|
||||||
|
leading: AppBarBackButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
"Boost transaction",
|
||||||
|
style: STextStyles.navBarTitle(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: isDesktop
|
||||||
|
? const EdgeInsets.only(
|
||||||
|
left: 32,
|
||||||
|
right: 32,
|
||||||
|
bottom: 32,
|
||||||
|
)
|
||||||
|
: const EdgeInsets.all(12),
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: isDesktop,
|
||||||
|
builder: (child) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
borderColor: isDesktop
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.backgroundAppBar
|
||||||
|
: null,
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 32,
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
label: "Preview send",
|
||||||
|
onPressed: _previewTxn,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
ConditionalParent(
|
||||||
|
condition: isDesktop,
|
||||||
|
builder: (child) => RoundedWhiteContainer(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
DetailItem(
|
||||||
|
title: "Send amount",
|
||||||
|
detail: amountString,
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
const _Divider(),
|
||||||
|
DetailItem(
|
||||||
|
title: "Current fee",
|
||||||
|
detail: feeString,
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
const _Divider(),
|
||||||
|
DetailItem(
|
||||||
|
title: "Current rate",
|
||||||
|
detail: feeRateString,
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
const _Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: FeeSlider(
|
||||||
|
overrideLabel: "Select a higher rate",
|
||||||
|
onSatVByteChanged: (value) => _newRate = value,
|
||||||
|
coin: coin,
|
||||||
|
min: rate.toDouble() + 1,
|
||||||
|
max: rate * 5.0,
|
||||||
|
pow: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isDesktop) const Spacer(),
|
||||||
|
if (!isDesktop)
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
if (!isDesktop)
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Preview send",
|
||||||
|
onPressed: _previewTxn,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Divider extends StatelessWidget {
|
||||||
|
const _Divider({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
return Container(
|
||||||
|
height: 1,
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,14 +16,13 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../../../models/isar/models/blockchain_data/transaction.dart';
|
import '../../../../models/isar/models/blockchain_data/transaction.dart';
|
||||||
import '../../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
import '../../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
import '../../../../models/isar/models/ethereum/eth_contract.dart';
|
import '../../../../models/isar/models/ethereum/eth_contract.dart';
|
||||||
import '../../../../notifications/show_flush_bar.dart';
|
import '../../../../notifications/show_flush_bar.dart';
|
||||||
import '../../sub_widgets/tx_icon.dart';
|
|
||||||
import '../dialogs/cancelling_transaction_progress_dialog.dart';
|
|
||||||
import '../edit_note_view.dart';
|
|
||||||
import '../../wallet_view.dart';
|
|
||||||
import '../../../../providers/db/main_db_provider.dart';
|
import '../../../../providers/db/main_db_provider.dart';
|
||||||
import '../../../../providers/global/address_book_service_provider.dart';
|
import '../../../../providers/global/address_book_service_provider.dart';
|
||||||
import '../../../../providers/providers.dart';
|
import '../../../../providers/providers.dart';
|
||||||
|
@ -35,6 +34,7 @@ import '../../../../utilities/block_explorers.dart';
|
||||||
import '../../../../utilities/constants.dart';
|
import '../../../../utilities/constants.dart';
|
||||||
import '../../../../utilities/format.dart';
|
import '../../../../utilities/format.dart';
|
||||||
import '../../../../utilities/logger.dart';
|
import '../../../../utilities/logger.dart';
|
||||||
|
import '../../../../utilities/show_loading.dart';
|
||||||
import '../../../../utilities/text_styles.dart';
|
import '../../../../utilities/text_styles.dart';
|
||||||
import '../../../../utilities/util.dart';
|
import '../../../../utilities/util.dart';
|
||||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
@ -42,6 +42,7 @@ import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart';
|
||||||
import '../../../../wallets/isar/models/spark_coin.dart';
|
import '../../../../wallets/isar/models/spark_coin.dart';
|
||||||
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||||
import '../../../../wallets/wallet/impl/epiccash_wallet.dart';
|
import '../../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||||
|
import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart';
|
||||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||||
import '../../../../widgets/background.dart';
|
import '../../../../widgets/background.dart';
|
||||||
import '../../../../widgets/conditional_parent.dart';
|
import '../../../../widgets/conditional_parent.dart';
|
||||||
|
@ -55,8 +56,11 @@ import '../../../../widgets/icon_widgets/copy_icon.dart';
|
||||||
import '../../../../widgets/icon_widgets/pencil_icon.dart';
|
import '../../../../widgets/icon_widgets/pencil_icon.dart';
|
||||||
import '../../../../widgets/rounded_white_container.dart';
|
import '../../../../widgets/rounded_white_container.dart';
|
||||||
import '../../../../widgets/stack_dialog.dart';
|
import '../../../../widgets/stack_dialog.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import '../../sub_widgets/tx_icon.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import '../../wallet_view.dart';
|
||||||
|
import '../dialogs/cancelling_transaction_progress_dialog.dart';
|
||||||
|
import '../edit_note_view.dart';
|
||||||
|
import 'boost_transaction_view.dart';
|
||||||
|
|
||||||
class TransactionV2DetailsView extends ConsumerStatefulWidget {
|
class TransactionV2DetailsView extends ConsumerStatefulWidget {
|
||||||
const TransactionV2DetailsView({
|
const TransactionV2DetailsView({
|
||||||
|
@ -90,6 +94,7 @@ class _TransactionV2DetailsViewState
|
||||||
late final String unit;
|
late final String unit;
|
||||||
late final int minConfirms;
|
late final int minConfirms;
|
||||||
late final EthContract? ethContract;
|
late final EthContract? ethContract;
|
||||||
|
late final bool supportsRbf;
|
||||||
|
|
||||||
bool get isTokenTx => ethContract != null;
|
bool get isTokenTx => ethContract != null;
|
||||||
|
|
||||||
|
@ -99,12 +104,89 @@ class _TransactionV2DetailsViewState
|
||||||
|
|
||||||
String? _sparkMemo;
|
String? _sparkMemo;
|
||||||
|
|
||||||
|
bool _boostButtonLock = false;
|
||||||
|
Future<void> _boostPressed() async {
|
||||||
|
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||||
|
if (_boostButtonLock || wallet is! RbfInterface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_boostButtonLock = true;
|
||||||
|
try {
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
if (_transaction.vSize == null) {
|
||||||
|
final updatedTx = await showLoading(
|
||||||
|
whileFuture: wallet.updateVSize(_transaction),
|
||||||
|
context: context,
|
||||||
|
message: "Fetching transaction vSize...",
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO handle errors if null
|
||||||
|
_transaction = updatedTx!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO pass the size in to the rbf screen
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DesktopDialog(
|
||||||
|
maxHeight: null,
|
||||||
|
maxWidth: 580,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 32),
|
||||||
|
child: Text(
|
||||||
|
"Boost transaction",
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const DesktopDialogCloseButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: BoostTransactionView(
|
||||||
|
transaction: _transaction,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unawaited(
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
BoostTransactionView.routeName,
|
||||||
|
arguments: _transaction,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_boostButtonLock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
isDesktop = Util.isDesktop;
|
isDesktop = Util.isDesktop;
|
||||||
_transaction = widget.transaction;
|
_transaction = widget.transaction;
|
||||||
walletId = widget.walletId;
|
walletId = widget.walletId;
|
||||||
|
|
||||||
|
if (_transaction.type
|
||||||
|
case TransactionType.sentToSelf || TransactionType.outgoing) {
|
||||||
|
supportsRbf = _transaction.subType == TransactionSubType.none &&
|
||||||
|
ref.read(pWallets).getWallet(walletId) is RbfInterface;
|
||||||
|
} else {
|
||||||
|
supportsRbf = false;
|
||||||
|
}
|
||||||
|
|
||||||
coin = widget.coin;
|
coin = widget.coin;
|
||||||
|
|
||||||
if (_transaction.subType == TransactionSubType.ethToken) {
|
if (_transaction.subType == TransactionSubType.ethToken) {
|
||||||
|
@ -482,6 +564,11 @@ class _TransactionV2DetailsViewState
|
||||||
outputLabel = "Sent to";
|
outputLabel = "Sent to";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final confirmedTxn = _transaction.isConfirmed(
|
||||||
|
currentHeight,
|
||||||
|
coin.minConfirms,
|
||||||
|
);
|
||||||
|
|
||||||
return ConditionalParent(
|
return ConditionalParent(
|
||||||
condition: !isDesktop,
|
condition: !isDesktop,
|
||||||
builder: (child) => Background(
|
builder: (child) => Background(
|
||||||
|
@ -1330,6 +1417,15 @@ class _TransactionV2DetailsViewState
|
||||||
context,
|
context,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (supportsRbf && !confirmedTxn)
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
if (supportsRbf && !confirmedTxn)
|
||||||
|
CustomTextButton(
|
||||||
|
text: "Boost transaction",
|
||||||
|
onTap: _boostPressed,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (!isDesktop)
|
if (!isDesktop)
|
||||||
|
@ -1779,6 +1875,16 @@ class _TransactionV2DetailsViewState
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
|
// if (whatIsIt(
|
||||||
|
// _transaction,
|
||||||
|
// currentHeight,
|
||||||
|
// ) !=
|
||||||
|
// "Sending")
|
||||||
|
// isDesktop
|
||||||
|
// ? const _Divider()
|
||||||
|
// : const SizedBox(
|
||||||
|
// height: 12,
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -377,7 +377,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
Future<void> _onExchangePressed(BuildContext context) async {
|
Future<void> _onExchangePressed(BuildContext context) async {
|
||||||
final CryptoCurrency coin = ref.read(pWalletCoin(walletId));
|
final CryptoCurrency coin = ref.read(pWalletCoin(walletId));
|
||||||
|
|
||||||
if (coin.network == CryptoCurrencyNetwork.test) {
|
if (coin.network.isTestNet) {
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => const StackOkDialog(
|
builder: (_) => const StackOkDialog(
|
||||||
|
@ -423,7 +423,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
Future<void> _onBuyPressed(BuildContext context) async {
|
Future<void> _onBuyPressed(BuildContext context) async {
|
||||||
final CryptoCurrency coin = ref.read(pWalletCoin(walletId));
|
final CryptoCurrency coin = ref.read(pWalletCoin(walletId));
|
||||||
|
|
||||||
if (coin.network == CryptoCurrencyNetwork.test) {
|
if (coin.network.isTestNet) {
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => const StackOkDialog(
|
builder: (_) => const StackOkDialog(
|
||||||
|
|
|
@ -11,13 +11,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
import '../../app_config.dart';
|
import '../../app_config.dart';
|
||||||
import '../../models/isar/models/blockchain_data/address.dart';
|
import '../../models/isar/models/blockchain_data/address.dart';
|
||||||
import '../../models/isar/models/contact_entry.dart';
|
import '../../models/isar/models/contact_entry.dart';
|
||||||
import '../../pages/address_book_views/subviews/add_address_book_entry_view.dart';
|
import '../../pages/address_book_views/subviews/add_address_book_entry_view.dart';
|
||||||
import '../../pages/address_book_views/subviews/address_book_filter_view.dart';
|
import '../../pages/address_book_views/subviews/address_book_filter_view.dart';
|
||||||
import 'subwidgets/desktop_address_book_scaffold.dart';
|
|
||||||
import 'subwidgets/desktop_contact_details.dart';
|
|
||||||
import '../../providers/db/main_db_provider.dart';
|
import '../../providers/db/main_db_provider.dart';
|
||||||
import '../../providers/global/address_book_service_provider.dart';
|
import '../../providers/global/address_book_service_provider.dart';
|
||||||
import '../../providers/providers.dart';
|
import '../../providers/providers.dart';
|
||||||
|
@ -40,6 +39,8 @@ import '../../widgets/rounded_container.dart';
|
||||||
import '../../widgets/rounded_white_container.dart';
|
import '../../widgets/rounded_white_container.dart';
|
||||||
import '../../widgets/stack_text_field.dart';
|
import '../../widgets/stack_text_field.dart';
|
||||||
import '../../widgets/textfield_icon_button.dart';
|
import '../../widgets/textfield_icon_button.dart';
|
||||||
|
import 'subwidgets/desktop_address_book_scaffold.dart';
|
||||||
|
import 'subwidgets/desktop_contact_details.dart';
|
||||||
|
|
||||||
class DesktopAddressBook extends ConsumerStatefulWidget {
|
class DesktopAddressBook extends ConsumerStatefulWidget {
|
||||||
const DesktopAddressBook({super.key});
|
const DesktopAddressBook({super.key});
|
||||||
|
@ -99,7 +100,7 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> {
|
||||||
// if (widget.coin == null) {
|
// if (widget.coin == null) {
|
||||||
final coins = AppConfig.coins.toList();
|
final coins = AppConfig.coins.toList();
|
||||||
coins.removeWhere(
|
coins.removeWhere(
|
||||||
(e) => e is Firo && e.network == CryptoCurrencyNetwork.test,
|
(e) => e is Firo && e.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
final bool showTestNet =
|
final bool showTestNet =
|
||||||
|
|
|
@ -488,7 +488,7 @@ class _DesktopCoinControlUseDialogState
|
||||||
.textDark,
|
.textDark,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
SelectableText(
|
||||||
"${widget.amountToSend!.decimal.toStringAsFixed(
|
"${widget.amountToSend!.decimal.toStringAsFixed(
|
||||||
coin.fractionDigits,
|
coin.fractionDigits,
|
||||||
)}"
|
)}"
|
||||||
|
@ -523,7 +523,7 @@ class _DesktopCoinControlUseDialogState
|
||||||
.textDark,
|
.textDark,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
SelectableText(
|
||||||
"${selectedSum.decimal.toStringAsFixed(
|
"${selectedSum.decimal.toStringAsFixed(
|
||||||
coin.fractionDigits,
|
coin.fractionDigits,
|
||||||
)} ${coin.ticker}",
|
)} ${coin.ticker}",
|
||||||
|
|
|
@ -704,8 +704,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
ref.read(pValidSparkSendToAddress.notifier).state =
|
ref.read(pValidSparkSendToAddress.notifier).state =
|
||||||
SparkInterface.validateSparkAddress(
|
SparkInterface.validateSparkAddress(
|
||||||
address: address ?? "",
|
address: address ?? "",
|
||||||
isTestNet:
|
isTestNet: wallet.cryptoCurrency.network.isTestNet,
|
||||||
wallet.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -883,7 +882,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
_cryptoFocus.addListener(() {
|
_cryptoFocus.addListener(() {
|
||||||
if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
|
if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
|
||||||
if (ref.read(pSendAmount) == null) {
|
if (ref.read(pSendAmount) == null) {
|
||||||
ref.refresh(sendAmountProvider);
|
if (ref.read(sendAmountProvider) != Amount.zero && mounted) {
|
||||||
|
ref.read(sendAmountProvider.state).state = Amount.zero;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ref.read(sendAmountProvider.state).state = ref.read(pSendAmount)!;
|
ref.read(sendAmountProvider.state).state = ref.read(pSendAmount)!;
|
||||||
}
|
}
|
||||||
|
@ -1468,7 +1469,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
if (_data != null && _data!.contactLabel == _address) {
|
if (_data != null && _data!.contactLabel == _address) {
|
||||||
error = SparkInterface.validateSparkAddress(
|
error = SparkInterface.validateSparkAddress(
|
||||||
address: _data!.address,
|
address: _data!.address,
|
||||||
isTestNet: coin.network == CryptoCurrencyNetwork.test,
|
isTestNet: coin.network.isTestNet,
|
||||||
)
|
)
|
||||||
? "Lelantus to Spark not supported"
|
? "Lelantus to Spark not supported"
|
||||||
: null;
|
: null;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import '../../../../../wallets/wallet/wallet_mixin_interfaces/coin_control_inter
|
||||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart';
|
import '../../../../../wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart';
|
||||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
|
import '../../../../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
|
||||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
import '../../../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||||
|
import '../../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart';
|
||||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
import '../../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||||
import '../../../../../widgets/custom_buttons/draggable_switch_button.dart';
|
import '../../../../../widgets/custom_buttons/draggable_switch_button.dart';
|
||||||
import '../../../../../widgets/desktop/desktop_dialog.dart';
|
import '../../../../../widgets/desktop/desktop_dialog.dart';
|
||||||
|
@ -81,6 +82,27 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _switchRbfToggledLock = false; // Mutex.
|
||||||
|
Future<void> _switchRbfToggled(bool newValue) async {
|
||||||
|
if (_switchRbfToggledLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_switchRbfToggledLock = true; // Lock mutex.
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Toggle enableOptInRbf in wallet info.
|
||||||
|
await ref.read(pWalletInfo(widget.walletId)).updateOtherData(
|
||||||
|
newEntries: {
|
||||||
|
WalletInfoKeys.enableOptInRbf: newValue,
|
||||||
|
},
|
||||||
|
isar: ref.read(mainDBProvider).isar,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
// ensure _switchRbfToggledLock is set to false no matter what
|
||||||
|
_switchRbfToggledLock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final wallet = ref.watch(
|
final wallet = ref.watch(
|
||||||
|
@ -198,6 +220,38 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (wallet is RbfInterface)
|
||||||
|
_MoreFeaturesItemBase(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 3),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 40,
|
||||||
|
child: DraggableSwitchButton(
|
||||||
|
isOn: ref.watch(
|
||||||
|
pWalletInfo(widget.walletId)
|
||||||
|
.select((value) => value.otherData),
|
||||||
|
)[WalletInfoKeys.enableOptInRbf] as bool? ??
|
||||||
|
false,
|
||||||
|
onValueChanged: _switchRbfToggled,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Flag outgoing transactions with opt-in RBF",
|
||||||
|
style: STextStyles.w600_20(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 28,
|
height: 28,
|
||||||
),
|
),
|
||||||
|
|
|
@ -208,7 +208,7 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 350,
|
width: 350,
|
||||||
child: Text(
|
child: Text(
|
||||||
"Open source multicoin wallet for everyone",
|
AppConfig.shortDescriptionText,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: STextStyles.desktopSubtitleH1(context),
|
style: STextStyles.desktopSubtitleH1(context),
|
||||||
),
|
),
|
||||||
|
|
|
@ -69,7 +69,7 @@ class _NodesSettings extends ConsumerState<NodesSettings> {
|
||||||
void initState() {
|
void initState() {
|
||||||
_coins = _coins.toList();
|
_coins = _coins.toList();
|
||||||
_coins.removeWhere(
|
_coins.removeWhere(
|
||||||
(e) => e is Firo && e.network == CryptoCurrencyNetwork.test,
|
(e) => e is Firo && e.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
searchNodeController = TextEditingController();
|
searchNodeController = TextEditingController();
|
||||||
|
|
|
@ -130,6 +130,7 @@ import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_setting
|
||||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart';
|
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart';
|
||||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart';
|
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart';
|
||||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart';
|
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart';
|
||||||
|
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rbf_settings_view.dart';
|
||||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart';
|
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart';
|
||||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart';
|
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart';
|
||||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
|
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
|
||||||
|
@ -144,6 +145,7 @@ import 'pages/wallet_view/transaction_views/edit_note_view.dart';
|
||||||
import 'pages/wallet_view/transaction_views/transaction_details_view.dart';
|
import 'pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||||
import 'pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
|
import 'pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
|
||||||
import 'pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
|
import 'pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
|
||||||
|
import 'pages/wallet_view/transaction_views/tx_v2/boost_transaction_view.dart';
|
||||||
import 'pages/wallet_view/transaction_views/tx_v2/fusion_group_details_view.dart';
|
import 'pages/wallet_view/transaction_views/tx_v2/fusion_group_details_view.dart';
|
||||||
import 'pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
import 'pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||||
import 'pages/wallet_view/wallet_view.dart';
|
import 'pages/wallet_view/wallet_view.dart';
|
||||||
|
@ -1967,6 +1969,18 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
|
case RbfSettingsView.routeName:
|
||||||
|
if (args is String) {
|
||||||
|
return getRoute(
|
||||||
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
builder: (_) => RbfSettingsView(walletId: args),
|
||||||
|
settings: RouteSettings(
|
||||||
|
name: settings.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case SparkInfoView.routeName:
|
case SparkInfoView.routeName:
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
@ -2173,6 +2187,20 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
|
case BoostTransactionView.routeName:
|
||||||
|
if (args is TransactionV2) {
|
||||||
|
return getRoute(
|
||||||
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
builder: (_) => BoostTransactionView(
|
||||||
|
transaction: args,
|
||||||
|
),
|
||||||
|
settings: RouteSettings(
|
||||||
|
name: settings.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case BackupRestoreSettings.routeName:
|
case BackupRestoreSettings.routeName:
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:frostdart/frostdart.dart';
|
||||||
import 'package:frostdart/frostdart_bindings_generated.dart';
|
import 'package:frostdart/frostdart_bindings_generated.dart';
|
||||||
import 'package:frostdart/output.dart';
|
import 'package:frostdart/output.dart';
|
||||||
import 'package:frostdart/util.dart';
|
import 'package:frostdart/util.dart';
|
||||||
|
|
||||||
import '../models/isar/models/blockchain_data/utxo.dart';
|
import '../models/isar/models/blockchain_data/utxo.dart';
|
||||||
import '../utilities/amount/amount.dart';
|
import '../utilities/amount/amount.dart';
|
||||||
import '../utilities/extensions/extensions.dart';
|
import '../utilities/extensions/extensions.dart';
|
||||||
|
@ -83,9 +84,8 @@ abstract class Frost {
|
||||||
required CryptoCurrency coin,
|
required CryptoCurrency coin,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
final network = coin.network == CryptoCurrencyNetwork.test
|
final network =
|
||||||
? Network.Testnet
|
coin.network.isTestNet ? Network.Testnet : Network.Mainnet;
|
||||||
: Network.Mainnet;
|
|
||||||
final signConfigPointer = decodedSignConfig(
|
final signConfigPointer = decodedSignConfig(
|
||||||
encodedConfig: signConfig,
|
encodedConfig: signConfig,
|
||||||
network: network,
|
network: network,
|
||||||
|
|
|
@ -30,6 +30,7 @@ class PriceAPI {
|
||||||
BitcoinFrost: "bitcoin",
|
BitcoinFrost: "bitcoin",
|
||||||
Litecoin: "litecoin",
|
Litecoin: "litecoin",
|
||||||
Bitcoincash: "bitcoin-cash",
|
Bitcoincash: "bitcoin-cash",
|
||||||
|
Dash: "dash",
|
||||||
Dogecoin: "dogecoin",
|
Dogecoin: "dogecoin",
|
||||||
Epiccash: "epic-cash",
|
Epiccash: "epic-cash",
|
||||||
Ecash: "ecash",
|
Ecash: "ecash",
|
||||||
|
|
|
@ -11,7 +11,6 @@ import '../../utilities/extensions/impl/string.dart';
|
||||||
import '../../utilities/extensions/impl/uint8_list.dart';
|
import '../../utilities/extensions/impl/uint8_list.dart';
|
||||||
import '../../utilities/format.dart';
|
import '../../utilities/format.dart';
|
||||||
import '../../utilities/logger.dart';
|
import '../../utilities/logger.dart';
|
||||||
import '../crypto_currency/crypto_currency.dart';
|
|
||||||
import '../crypto_currency/intermediate/bip39_hd_currency.dart';
|
import '../crypto_currency/intermediate/bip39_hd_currency.dart';
|
||||||
import '../models/tx_data.dart';
|
import '../models/tx_data.dart';
|
||||||
|
|
||||||
|
@ -92,7 +91,7 @@ abstract final class LelantusFfiWrapper {
|
||||||
mintKeyPair.privateKey!.toHex,
|
mintKeyPair.privateKey!.toHex,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
mintKeyPair.identifier.toHex,
|
mintKeyPair.identifier.toHex,
|
||||||
isTestnet: args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestnet: args.cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (int setId = 1; setId <= args.latestSetId; setId++) {
|
for (int setId = 1; setId <= args.latestSetId; setId++) {
|
||||||
|
@ -117,8 +116,7 @@ abstract final class LelantusFfiWrapper {
|
||||||
amount,
|
amount,
|
||||||
mintKeyPair.privateKey!.toHex,
|
mintKeyPair.privateKey!.toHex,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
isTestnet:
|
isTestnet: args.cryptoCurrency.network.isTestNet,
|
||||||
args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
|
||||||
);
|
);
|
||||||
final bool isUsed = args.usedSerialNumbers.contains(serialNumber);
|
final bool isUsed = args.usedSerialNumbers.contains(serialNumber);
|
||||||
|
|
||||||
|
@ -162,8 +160,7 @@ abstract final class LelantusFfiWrapper {
|
||||||
amount,
|
amount,
|
||||||
aesPrivateKey,
|
aesPrivateKey,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
isTestnet:
|
isTestnet: args.cryptoCurrency.network.isTestNet,
|
||||||
args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
|
||||||
);
|
);
|
||||||
final bool isUsed = args.usedSerialNumbers.contains(serialNumber);
|
final bool isUsed = args.usedSerialNumbers.contains(serialNumber);
|
||||||
|
|
||||||
|
@ -314,7 +311,7 @@ abstract final class LelantusFfiWrapper {
|
||||||
spendAmount: spendAmount,
|
spendAmount: spendAmount,
|
||||||
subtractFeeFromAmount: arg.subtractFeeFromAmount,
|
subtractFeeFromAmount: arg.subtractFeeFromAmount,
|
||||||
lelantusEntries: arg.lelantusEntries,
|
lelantusEntries: arg.lelantusEntries,
|
||||||
isTestNet: arg.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestNet: arg.cryptoCurrency.network.isTestNet,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final changeToMint = estimateJoinSplitFee.changeToMint;
|
final changeToMint = estimateJoinSplitFee.changeToMint;
|
||||||
|
@ -364,7 +361,7 @@ abstract final class LelantusFfiWrapper {
|
||||||
changeToMint,
|
changeToMint,
|
||||||
jmintprivatekey,
|
jmintprivatekey,
|
||||||
arg.index,
|
arg.index,
|
||||||
isTestnet: arg.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestnet: arg.cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
final _derivePath = "${arg.partialDerivationPath}$JMINT_INDEX/$keyPath";
|
final _derivePath = "${arg.partialDerivationPath}$JMINT_INDEX/$keyPath";
|
||||||
|
@ -378,7 +375,7 @@ abstract final class LelantusFfiWrapper {
|
||||||
arg.index,
|
arg.index,
|
||||||
Format.uint8listToString(jmintKeyPair.identifier),
|
Format.uint8listToString(jmintKeyPair.identifier),
|
||||||
aesPrivateKey,
|
aesPrivateKey,
|
||||||
isTestnet: arg.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestnet: arg.cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
tx.addOutput(
|
tx.addOutput(
|
||||||
|
@ -434,7 +431,7 @@ abstract final class LelantusFfiWrapper {
|
||||||
anonymitySets,
|
anonymitySets,
|
||||||
anonymitySetHashes,
|
anonymitySetHashes,
|
||||||
groupBlockHashes,
|
groupBlockHashes,
|
||||||
isTestnet: arg.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestnet: arg.cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
final finalTx = bitcoindart.TransactionBuilder(network: _network);
|
final finalTx = bitcoindart.TransactionBuilder(network: _network);
|
||||||
|
|
|
@ -53,7 +53,7 @@ class Banano extends NanoCurrency {
|
||||||
int get minConfirms => 1;
|
int get minConfirms => 1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.banano;
|
AddressType get defaultAddressType => AddressType.banano;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get defaultRepresentative =>
|
String get defaultRepresentative =>
|
||||||
|
@ -97,7 +97,7 @@ class Banano extends NanoCurrency {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => throw UnsupportedError(
|
DerivePathType get defaultDerivePathType => throw UnsupportedError(
|
||||||
"$runtimeType does not use bitcoin style derivation paths",
|
"$runtimeType does not use bitcoin style derivation paths",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,10 @@ class Bitcoin extends Bip39HDCurrency
|
||||||
_id = "bitcoinTestNet";
|
_id = "bitcoinTestNet";
|
||||||
_name = "tBitcoin";
|
_name = "tBitcoin";
|
||||||
_ticker = "tBTC";
|
_ticker = "tBTC";
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
|
_id = "bitcoinTestNet4";
|
||||||
|
_name = "t4Bitcoin";
|
||||||
|
_ticker = "t4BTC";
|
||||||
default:
|
default:
|
||||||
throw Exception("Unsupported network: $network");
|
throw Exception("Unsupported network: $network");
|
||||||
}
|
}
|
||||||
|
@ -71,6 +75,8 @@ class Bitcoin extends Bip39HDCurrency
|
||||||
return "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
|
return "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
|
||||||
case CryptoCurrencyNetwork.test:
|
case CryptoCurrencyNetwork.test:
|
||||||
return "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
|
return "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
|
return "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043";
|
||||||
default:
|
default:
|
||||||
throw Exception("Unsupported network: $network");
|
throw Exception("Unsupported network: $network");
|
||||||
}
|
}
|
||||||
|
@ -99,6 +105,7 @@ class Bitcoin extends Bip39HDCurrency
|
||||||
feePerKb: BigInt.from(1), // Not used in stack wallet currently
|
feePerKb: BigInt.from(1), // Not used in stack wallet currently
|
||||||
);
|
);
|
||||||
case CryptoCurrencyNetwork.test:
|
case CryptoCurrencyNetwork.test:
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
return coinlib.Network(
|
return coinlib.Network(
|
||||||
wifPrefix: 0xef,
|
wifPrefix: 0xef,
|
||||||
p2pkhPrefix: 0x6f,
|
p2pkhPrefix: 0x6f,
|
||||||
|
@ -247,6 +254,19 @@ class Bitcoin extends Bip39HDCurrency
|
||||||
isDown: false,
|
isDown: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
|
return NodeModel(
|
||||||
|
host: "bitcoin-testnet4.stackwallet.com",
|
||||||
|
port: 50002,
|
||||||
|
name: DefaultNodes.defaultName,
|
||||||
|
id: DefaultNodes.buildId(this),
|
||||||
|
useSSL: true,
|
||||||
|
enabled: true,
|
||||||
|
coinName: identifier,
|
||||||
|
isFailover: true,
|
||||||
|
isDown: false,
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
@ -268,7 +288,7 @@ class Bitcoin extends Bip39HDCurrency
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2wpkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -277,7 +297,7 @@ class Bitcoin extends Bip39HDCurrency
|
||||||
int get targetBlockTimeSeconds => 600;
|
int get targetBlockTimeSeconds => 600;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.bip84;
|
DerivePathType get defaultDerivePathType => DerivePathType.bip86;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
@ -286,6 +306,8 @@ class Bitcoin extends Bip39HDCurrency
|
||||||
return Uri.parse("https://mempool.space/tx/$txid");
|
return Uri.parse("https://mempool.space/tx/$txid");
|
||||||
case CryptoCurrencyNetwork.test:
|
case CryptoCurrencyNetwork.test:
|
||||||
return Uri.parse("https://mempool.space/testnet/tx/$txid");
|
return Uri.parse("https://mempool.space/testnet/tx/$txid");
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
|
return Uri.parse("https://mempool.space/testnet4/tx/$txid");
|
||||||
default:
|
default:
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"Unsupported network for defaultBlockExplorer(): $network",
|
"Unsupported network for defaultBlockExplorer(): $network",
|
||||||
|
|
|
@ -24,6 +24,10 @@ class BitcoinFrost extends FrostCurrency {
|
||||||
_id = "bitcoinFrostTestNet";
|
_id = "bitcoinFrostTestNet";
|
||||||
_name = "tBitcoin Frost";
|
_name = "tBitcoin Frost";
|
||||||
_ticker = "tBTC";
|
_ticker = "tBTC";
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
|
_id = "bitcoinFrostTestNet4";
|
||||||
|
_name = "t4Bitcoin Frost";
|
||||||
|
_ticker = "t4BTC";
|
||||||
default:
|
default:
|
||||||
throw Exception("Unsupported network: $network");
|
throw Exception("Unsupported network: $network");
|
||||||
}
|
}
|
||||||
|
@ -84,6 +88,19 @@ class BitcoinFrost extends FrostCurrency {
|
||||||
isDown: false,
|
isDown: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
|
return NodeModel(
|
||||||
|
host: "bitcoin-testnet4.stackwallet.com",
|
||||||
|
port: 50002,
|
||||||
|
name: DefaultNodes.defaultName,
|
||||||
|
id: DefaultNodes.buildId(this),
|
||||||
|
useSSL: true,
|
||||||
|
enabled: true,
|
||||||
|
coinName: identifier,
|
||||||
|
isFailover: true,
|
||||||
|
isDown: false,
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
@ -96,6 +113,8 @@ class BitcoinFrost extends FrostCurrency {
|
||||||
return "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
|
return "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
|
||||||
case CryptoCurrencyNetwork.test:
|
case CryptoCurrencyNetwork.test:
|
||||||
return "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
|
return "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
|
return "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043";
|
||||||
default:
|
default:
|
||||||
throw Exception("Unsupported network: $network");
|
throw Exception("Unsupported network: $network");
|
||||||
}
|
}
|
||||||
|
@ -132,6 +151,7 @@ class BitcoinFrost extends FrostCurrency {
|
||||||
feePerKb: BigInt.from(1), // Not used in stack wallet currently
|
feePerKb: BigInt.from(1), // Not used in stack wallet currently
|
||||||
);
|
);
|
||||||
case CryptoCurrencyNetwork.test:
|
case CryptoCurrencyNetwork.test:
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
return coinlib.Network(
|
return coinlib.Network(
|
||||||
wifPrefix: 0xef,
|
wifPrefix: 0xef,
|
||||||
p2pkhPrefix: 0x6f,
|
p2pkhPrefix: 0x6f,
|
||||||
|
@ -175,7 +195,7 @@ class BitcoinFrost extends FrostCurrency {
|
||||||
List<int> get possibleMnemonicLengths => [];
|
List<int> get possibleMnemonicLengths => [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.frostMS;
|
AddressType get defaultAddressType => AddressType.frostMS;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -184,7 +204,7 @@ class BitcoinFrost extends FrostCurrency {
|
||||||
int get targetBlockTimeSeconds => 600;
|
int get targetBlockTimeSeconds => 600;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => throw UnsupportedError(
|
DerivePathType get defaultDerivePathType => throw UnsupportedError(
|
||||||
"$runtimeType does not use bitcoin style derivation paths",
|
"$runtimeType does not use bitcoin style derivation paths",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -195,6 +215,8 @@ class BitcoinFrost extends FrostCurrency {
|
||||||
return Uri.parse("https://mempool.space/tx/$txid");
|
return Uri.parse("https://mempool.space/tx/$txid");
|
||||||
case CryptoCurrencyNetwork.test:
|
case CryptoCurrencyNetwork.test:
|
||||||
return Uri.parse("https://mempool.space/testnet/tx/$txid");
|
return Uri.parse("https://mempool.space/testnet/tx/$txid");
|
||||||
|
case CryptoCurrencyNetwork.test4:
|
||||||
|
return Uri.parse("https://mempool.space/testnet4/tx/$txid");
|
||||||
default:
|
default:
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"Unsupported network for defaultBlockExplorer(): $network",
|
"Unsupported network for defaultBlockExplorer(): $network",
|
||||||
|
|
|
@ -211,7 +211,7 @@ class Bitcoincash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
// 0 for bitcoincash: address scheme, 1 for legacy address
|
// 0 for bitcoincash: address scheme, 1 for legacy address
|
||||||
final format = bitbox.Address.detectFormat(address);
|
final format = bitbox.Address.detectFormat(address);
|
||||||
|
|
||||||
if (network == CryptoCurrencyNetwork.test) {
|
if (network.isTestNet) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +336,7 @@ class Bitcoincash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2pkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -345,7 +345,7 @@ class Bitcoincash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
int get targetBlockTimeSeconds => 600;
|
int get targetBlockTimeSeconds => 600;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.bip44;
|
DerivePathType get defaultDerivePathType => DerivePathType.bip44;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
247
lib/wallets/crypto_currency/coins/dash.dart
Normal file
247
lib/wallets/crypto_currency/coins/dash.dart
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
|
||||||
|
|
||||||
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
|
import '../../../models/node_model.dart';
|
||||||
|
import '../../../utilities/amount/amount.dart';
|
||||||
|
import '../../../utilities/default_nodes.dart';
|
||||||
|
import '../../../utilities/enums/derive_path_type_enum.dart';
|
||||||
|
import '../crypto_currency.dart';
|
||||||
|
import '../interfaces/electrumx_currency_interface.dart';
|
||||||
|
import '../intermediate/bip39_hd_currency.dart';
|
||||||
|
|
||||||
|
class Dash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
|
Dash(super.network) {
|
||||||
|
_idMain = "dash";
|
||||||
|
_uriScheme = "dash";
|
||||||
|
switch (network) {
|
||||||
|
case CryptoCurrencyNetwork.main:
|
||||||
|
_id = _idMain;
|
||||||
|
_name = "Dash";
|
||||||
|
_ticker = "DASH";
|
||||||
|
// case CryptoCurrencyNetwork.test:
|
||||||
|
// _id = "dashTestNet";
|
||||||
|
// _name = "tDash";
|
||||||
|
// _ticker = "tDASH";
|
||||||
|
default:
|
||||||
|
throw Exception("Unsupported network: $network");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
late final String _id;
|
||||||
|
@override
|
||||||
|
String get identifier => _id;
|
||||||
|
|
||||||
|
late final String _idMain;
|
||||||
|
@override
|
||||||
|
String get mainNetId => _idMain;
|
||||||
|
|
||||||
|
late final String _name;
|
||||||
|
@override
|
||||||
|
String get prettyName => _name;
|
||||||
|
|
||||||
|
late final String _uriScheme;
|
||||||
|
@override
|
||||||
|
String get uriScheme => _uriScheme;
|
||||||
|
|
||||||
|
late final String _ticker;
|
||||||
|
@override
|
||||||
|
String get ticker => _ticker;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get torSupport => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<DerivePathType> get supportedDerivationPathTypes => [
|
||||||
|
DerivePathType.bip44,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String constructDerivePath({
|
||||||
|
required DerivePathType derivePathType,
|
||||||
|
int account = 0,
|
||||||
|
required int chain,
|
||||||
|
required int index,
|
||||||
|
}) {
|
||||||
|
String coinType;
|
||||||
|
|
||||||
|
switch (networkParams.wifPrefix) {
|
||||||
|
case 204: // dash mainnet wif
|
||||||
|
coinType = "5"; // dash mainnet
|
||||||
|
break;
|
||||||
|
// case 239: // dash testnet wif
|
||||||
|
// coinType = "1"; // dash testnet
|
||||||
|
// break;
|
||||||
|
default:
|
||||||
|
throw Exception("Invalid Dash network wif used!");
|
||||||
|
}
|
||||||
|
|
||||||
|
int purpose;
|
||||||
|
switch (derivePathType) {
|
||||||
|
case DerivePathType.bip44:
|
||||||
|
purpose = 44;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Exception("DerivePathType $derivePathType not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "m/$purpose'/$coinType'/$account'/$chain/$index";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Amount get dustLimit => Amount(
|
||||||
|
rawValue: BigInt.from(1000000),
|
||||||
|
fractionDigits: fractionDigits,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get genesisHash {
|
||||||
|
switch (network) {
|
||||||
|
case CryptoCurrencyNetwork.main:
|
||||||
|
return "00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6";
|
||||||
|
// case CryptoCurrencyNetwork.test:
|
||||||
|
// return "00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c";
|
||||||
|
default:
|
||||||
|
throw Exception("Unsupported network: $network");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
({
|
||||||
|
coinlib.Address address,
|
||||||
|
AddressType addressType,
|
||||||
|
}) getAddressForPublicKey({
|
||||||
|
required coinlib.ECPublicKey publicKey,
|
||||||
|
required DerivePathType derivePathType,
|
||||||
|
}) {
|
||||||
|
switch (derivePathType) {
|
||||||
|
case DerivePathType.bip44:
|
||||||
|
final addr = coinlib.P2PKHAddress.fromPublicKey(
|
||||||
|
publicKey,
|
||||||
|
version: networkParams.p2pkhPrefix,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (address: addr, addressType: AddressType.p2pkh);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Exception("DerivePathType $derivePathType not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get minConfirms => 6;
|
||||||
|
|
||||||
|
@override
|
||||||
|
coinlib.Network get networkParams {
|
||||||
|
switch (network) {
|
||||||
|
case CryptoCurrencyNetwork.main:
|
||||||
|
return coinlib.Network(
|
||||||
|
p2pkhPrefix: 76,
|
||||||
|
p2shPrefix: 16,
|
||||||
|
wifPrefix: 204,
|
||||||
|
pubHDPrefix: 0x0488B21E,
|
||||||
|
privHDPrefix: 0x0488ADE4,
|
||||||
|
bech32Hrp: "dash", // TODO ?????
|
||||||
|
messagePrefix: '\x18Dash Signed Message:\n', // TODO ?????
|
||||||
|
minFee: BigInt.from(1), // Not used in stack wallet currently
|
||||||
|
minOutput: dustLimit.raw, // Not used in stack wallet currently
|
||||||
|
feePerKb: BigInt.from(1), // Not used in stack wallet currently
|
||||||
|
);
|
||||||
|
// case CryptoCurrencyNetwork.test:
|
||||||
|
// return coinlib.Network(
|
||||||
|
// p2pkhPrefix: 140,
|
||||||
|
// p2shPrefix: 19,
|
||||||
|
// wifPrefix: 239,
|
||||||
|
// pubHDPrefix: 0x043587CF,
|
||||||
|
// privHDPrefix: 0x04358394,
|
||||||
|
// bech32Hrp: "tdash", // TODO ?????
|
||||||
|
// messagePrefix: '\x18Dash Signed Message:\n', // TODO ?????
|
||||||
|
// minFee: BigInt.from(1), // Not used in stack wallet currently
|
||||||
|
// minOutput: dustLimit.raw, // Not used in stack wallet currently
|
||||||
|
// feePerKb: BigInt.from(1), // Not used in stack wallet currently
|
||||||
|
// );
|
||||||
|
default:
|
||||||
|
throw Exception("Unsupported network: $network");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool validateAddress(String address) {
|
||||||
|
try {
|
||||||
|
coinlib.Address.fromString(address, networkParams);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
NodeModel get defaultNode {
|
||||||
|
switch (network) {
|
||||||
|
case CryptoCurrencyNetwork.main:
|
||||||
|
return NodeModel(
|
||||||
|
host: "dash.stackwallet.com",
|
||||||
|
port: 60002,
|
||||||
|
name: DefaultNodes.defaultName,
|
||||||
|
id: DefaultNodes.buildId(this),
|
||||||
|
useSSL: true,
|
||||||
|
enabled: true,
|
||||||
|
coinName: identifier,
|
||||||
|
isFailover: true,
|
||||||
|
isDown: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get defaultSeedPhraseLength => 12;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get fractionDigits => 8;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasBuySupport => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasMnemonicPassphraseSupport => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
|
@override
|
||||||
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
|
@override
|
||||||
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get targetBlockTimeSeconds => 150;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DerivePathType get defaultDerivePathType => DerivePathType.bip44;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
switch (network) {
|
||||||
|
case CryptoCurrencyNetwork.main:
|
||||||
|
return Uri.parse("https://insight.dash.org/insight/tx/$txid");
|
||||||
|
// case CryptoCurrencyNetwork.test:
|
||||||
|
// return Uri.parse(
|
||||||
|
// "https://insight.testnet.networks.dash.org:3002/insight/tx/$txid",
|
||||||
|
// );
|
||||||
|
default:
|
||||||
|
throw Exception(
|
||||||
|
"Unsupported network for defaultBlockExplorer(): $network",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get transactionVersion => 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BigInt get defaultFeeRate => BigInt.from(1000); // TODO check for dash?
|
||||||
|
}
|
|
@ -225,7 +225,7 @@ class Dogecoin extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2pkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -234,7 +234,7 @@ class Dogecoin extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
int get targetBlockTimeSeconds => 60;
|
int get targetBlockTimeSeconds => 60;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.bip44;
|
DerivePathType get defaultDerivePathType => DerivePathType.bip44;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -314,7 +314,7 @@ class Ecash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2pkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100);
|
BigInt get satsPerCoin => BigInt.from(100);
|
||||||
|
@ -323,7 +323,7 @@ class Ecash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
int get targetBlockTimeSeconds => 600;
|
int get targetBlockTimeSeconds => 600;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.eCash44;
|
DerivePathType get defaultDerivePathType => DerivePathType.eCash44;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter_libepiccash/lib.dart' as epic;
|
import 'package:flutter_libepiccash/lib.dart' as epic;
|
||||||
|
|
||||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
import '../../../models/node_model.dart';
|
import '../../../models/node_model.dart';
|
||||||
import '../../../utilities/default_nodes.dart';
|
import '../../../utilities/default_nodes.dart';
|
||||||
|
@ -102,7 +103,7 @@ class Epiccash extends Bip39Currency {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 12];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 12];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.mimbleWimble;
|
AddressType get defaultAddressType => AddressType.mimbleWimble;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -111,7 +112,7 @@ class Epiccash extends Bip39Currency {
|
||||||
int get targetBlockTimeSeconds => 60;
|
int get targetBlockTimeSeconds => 60;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => throw UnsupportedError(
|
DerivePathType get defaultDerivePathType => throw UnsupportedError(
|
||||||
"$runtimeType does not use bitcoin style derivation paths",
|
"$runtimeType does not use bitcoin style derivation paths",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
import 'package:ethereum_addresses/ethereum_addresses.dart';
|
||||||
|
|
||||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
import '../../../models/node_model.dart';
|
import '../../../models/node_model.dart';
|
||||||
import '../../../utilities/default_nodes.dart';
|
import '../../../utilities/default_nodes.dart';
|
||||||
|
@ -86,7 +87,7 @@ class Ethereum extends Bip39Currency {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.ethereum;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(1000000000000000000);
|
BigInt get satsPerCoin => BigInt.from(1000000000000000000);
|
||||||
|
@ -95,7 +96,7 @@ class Ethereum extends Bip39Currency {
|
||||||
int get targetBlockTimeSeconds => 15;
|
int get targetBlockTimeSeconds => 15;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.eth;
|
DerivePathType get defaultDerivePathType => DerivePathType.eth;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -176,7 +176,7 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
bool validateSparkAddress(String address) {
|
bool validateSparkAddress(String address) {
|
||||||
return SparkInterface.validateSparkAddress(
|
return SparkInterface.validateSparkAddress(
|
||||||
address: address,
|
address: address,
|
||||||
isTestNet: network == CryptoCurrencyNetwork.test,
|
isTestNet: network.isTestNet,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2pkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -252,7 +252,7 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
int get targetBlockTimeSeconds => 150;
|
int get targetBlockTimeSeconds => 150;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.bip44;
|
DerivePathType get defaultDerivePathType => DerivePathType.bip44;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -256,7 +256,7 @@ class Litecoin extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2wpkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -265,7 +265,7 @@ class Litecoin extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
int get targetBlockTimeSeconds => 150;
|
int get targetBlockTimeSeconds => 150;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.bip84;
|
DerivePathType get defaultDerivePathType => DerivePathType.bip84;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -100,7 +100,7 @@ class Monero extends CryptonoteCurrency {
|
||||||
int get targetBlockTimeSeconds => 120;
|
int get targetBlockTimeSeconds => 120;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => throw UnsupportedError(
|
DerivePathType get defaultDerivePathType => throw UnsupportedError(
|
||||||
"$runtimeType does not use bitcoin style derivation paths",
|
"$runtimeType does not use bitcoin style derivation paths",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -230,7 +230,7 @@ class Namecoin extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 12];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 12];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2wpkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -239,7 +239,7 @@ class Namecoin extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
int get targetBlockTimeSeconds => 600;
|
int get targetBlockTimeSeconds => 600;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.bip84;
|
DerivePathType get defaultDerivePathType => DerivePathType.bip84;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -53,7 +53,7 @@ class Nano extends NanoCurrency {
|
||||||
int get minConfirms => 1;
|
int get minConfirms => 1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.nano;
|
AddressType get defaultAddressType => AddressType.nano;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get defaultRepresentative =>
|
String get defaultRepresentative =>
|
||||||
|
@ -85,7 +85,7 @@ class Nano extends NanoCurrency {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => throw UnsupportedError(
|
DerivePathType get defaultDerivePathType => throw UnsupportedError(
|
||||||
"$runtimeType does not use bitcoin style derivation paths",
|
"$runtimeType does not use bitcoin style derivation paths",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -208,7 +208,7 @@ class Particl extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2wpkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(100000000);
|
BigInt get satsPerCoin => BigInt.from(100000000);
|
||||||
|
@ -217,7 +217,7 @@ class Particl extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
int get targetBlockTimeSeconds => 600;
|
int get targetBlockTimeSeconds => 600;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.bip84;
|
DerivePathType get defaultDerivePathType => DerivePathType.bip84;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -228,7 +228,7 @@ class Peercoin extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.p2wpkh;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(1000000); // 1*10^6.
|
BigInt get satsPerCoin => BigInt.from(1000000); // 1*10^6.
|
||||||
|
@ -237,7 +237,7 @@ class Peercoin extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
||||||
int get targetBlockTimeSeconds => 600;
|
int get targetBlockTimeSeconds => 600;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.bip84;
|
DerivePathType get defaultDerivePathType => DerivePathType.bip84;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -94,7 +94,7 @@ class Solana extends Bip39Currency {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 24];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.solana;
|
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(1000000000);
|
BigInt get satsPerCoin => BigInt.from(1000000000);
|
||||||
|
@ -103,7 +103,7 @@ class Solana extends Bip39Currency {
|
||||||
int get targetBlockTimeSeconds => 1;
|
int get targetBlockTimeSeconds => 1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => DerivePathType.solana;
|
DerivePathType get defaultDerivePathType => DerivePathType.solana;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri defaultBlockExplorer(String txid) {
|
Uri defaultBlockExplorer(String txid) {
|
||||||
|
|
|
@ -108,7 +108,7 @@ class Stellar extends Bip39Currency {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 12];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 12];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.stellar;
|
AddressType get defaultAddressType => AddressType.stellar;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(
|
BigInt get satsPerCoin => BigInt.from(
|
||||||
|
@ -119,7 +119,7 @@ class Stellar extends Bip39Currency {
|
||||||
int get targetBlockTimeSeconds => 5;
|
int get targetBlockTimeSeconds => 5;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => throw UnsupportedError(
|
DerivePathType get defaultDerivePathType => throw UnsupportedError(
|
||||||
"$runtimeType does not use bitcoin style derivation paths",
|
"$runtimeType does not use bitcoin style derivation paths",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,7 @@ class Tezos extends Bip39Currency {
|
||||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 12];
|
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength, 12];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.tezos;
|
AddressType get defaultAddressType => AddressType.tezos;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BigInt get satsPerCoin => BigInt.from(1000000);
|
BigInt get satsPerCoin => BigInt.from(1000000);
|
||||||
|
@ -204,7 +204,7 @@ class Tezos extends Bip39Currency {
|
||||||
int get targetBlockTimeSeconds => 60;
|
int get targetBlockTimeSeconds => 60;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType =>
|
DerivePathType get defaultDerivePathType =>
|
||||||
throw UnsupportedError("Is this even used?");
|
throw UnsupportedError("Is this even used?");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -95,7 +95,7 @@ class Wownero extends CryptonoteCurrency {
|
||||||
int get targetBlockTimeSeconds => 120;
|
int get targetBlockTimeSeconds => 120;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DerivePathType get primaryDerivePathType => throw UnsupportedError(
|
DerivePathType get defaultDerivePathType => throw UnsupportedError(
|
||||||
"$runtimeType does not use bitcoin style derivation paths",
|
"$runtimeType does not use bitcoin style derivation paths",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ export 'coins/banano.dart';
|
||||||
export 'coins/bitcoin.dart';
|
export 'coins/bitcoin.dart';
|
||||||
export 'coins/bitcoin_frost.dart';
|
export 'coins/bitcoin_frost.dart';
|
||||||
export 'coins/bitcoincash.dart';
|
export 'coins/bitcoincash.dart';
|
||||||
|
export 'coins/dash.dart';
|
||||||
export 'coins/dogecoin.dart';
|
export 'coins/dogecoin.dart';
|
||||||
export 'coins/ecash.dart';
|
export 'coins/ecash.dart';
|
||||||
export 'coins/epiccash.dart';
|
export 'coins/epiccash.dart';
|
||||||
|
@ -25,7 +26,11 @@ export 'coins/wownero.dart';
|
||||||
enum CryptoCurrencyNetwork {
|
enum CryptoCurrencyNetwork {
|
||||||
main,
|
main,
|
||||||
test,
|
test,
|
||||||
stage;
|
stage,
|
||||||
|
test4;
|
||||||
|
|
||||||
|
bool get isTestNet =>
|
||||||
|
this == CryptoCurrencyNetwork.test || this == CryptoCurrencyNetwork.test4;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class CryptoCurrency {
|
abstract class CryptoCurrency {
|
||||||
|
@ -67,10 +72,10 @@ abstract class CryptoCurrency {
|
||||||
bool get hasBuySupport;
|
bool get hasBuySupport;
|
||||||
bool get hasMnemonicPassphraseSupport;
|
bool get hasMnemonicPassphraseSupport;
|
||||||
List<int> get possibleMnemonicLengths;
|
List<int> get possibleMnemonicLengths;
|
||||||
AddressType get primaryAddressType;
|
AddressType get defaultAddressType;
|
||||||
BigInt get satsPerCoin;
|
BigInt get satsPerCoin;
|
||||||
int get targetBlockTimeSeconds;
|
int get targetBlockTimeSeconds;
|
||||||
DerivePathType get primaryDerivePathType;
|
DerivePathType get defaultDerivePathType;
|
||||||
|
|
||||||
Uri defaultBlockExplorer(String txid);
|
Uri defaultBlockExplorer(String txid);
|
||||||
|
|
||||||
|
|
|
@ -10,5 +10,5 @@ abstract class CryptonoteCurrency extends CryptoCurrency {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AddressType get primaryAddressType => AddressType.cryptonote;
|
AddressType get defaultAddressType => AddressType.cryptonote;
|
||||||
}
|
}
|
||||||
|
|
|
@ -458,7 +458,7 @@ class WalletInfo implements IsarId {
|
||||||
coinName: coin.identifier,
|
coinName: coin.identifier,
|
||||||
walletId: walletIdOverride ?? const Uuid().v1(),
|
walletId: walletIdOverride ?? const Uuid().v1(),
|
||||||
name: name,
|
name: name,
|
||||||
mainAddressType: coin.primaryAddressType,
|
mainAddressType: coin.defaultAddressType,
|
||||||
restoreHeight: restoreHeight,
|
restoreHeight: restoreHeight,
|
||||||
otherDataJsonString: otherDataJsonString,
|
otherDataJsonString: otherDataJsonString,
|
||||||
);
|
);
|
||||||
|
@ -510,4 +510,5 @@ abstract class WalletInfoKeys {
|
||||||
static const String enableLelantusScanning = "enableLelantusScanningKey";
|
static const String enableLelantusScanning = "enableLelantusScanningKey";
|
||||||
static const String firoSparkCacheSetTimestampCache =
|
static const String firoSparkCacheSetTimestampCache =
|
||||||
"firoSparkCacheSetTimestampCacheKey";
|
"firoSparkCacheSetTimestampCacheKey";
|
||||||
|
static const String enableOptInRbf = "enableOptInRbfKey";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import 'package:cw_monero/pending_monero_transaction.dart';
|
import 'package:cw_monero/pending_monero_transaction.dart';
|
||||||
import 'package:cw_wownero/pending_wownero_transaction.dart';
|
import 'package:cw_wownero/pending_wownero_transaction.dart';
|
||||||
|
import 'package:tezart/tezart.dart' as tezart;
|
||||||
|
import 'package:web3dart/web3dart.dart' as web3dart;
|
||||||
|
|
||||||
import '../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
import '../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
import '../../models/isar/models/isar_models.dart';
|
import '../../models/isar/models/isar_models.dart';
|
||||||
import '../../models/paynym/paynym_account_lite.dart';
|
import '../../models/paynym/paynym_account_lite.dart';
|
||||||
import '../../utilities/amount/amount.dart';
|
import '../../utilities/amount/amount.dart';
|
||||||
import '../../utilities/enums/fee_rate_type_enum.dart';
|
import '../../utilities/enums/fee_rate_type_enum.dart';
|
||||||
import '../isar/models/spark_coin.dart';
|
import '../isar/models/spark_coin.dart';
|
||||||
import 'package:tezart/tezart.dart' as tezart;
|
|
||||||
import 'package:web3dart/web3dart.dart' as web3dart;
|
typedef TxRecipient = ({String address, Amount amount, bool isChange});
|
||||||
|
|
||||||
class TxData {
|
class TxData {
|
||||||
final FeeRateType? feeRateType;
|
final FeeRateType? feeRateType;
|
||||||
|
@ -27,7 +30,7 @@ class TxData {
|
||||||
|
|
||||||
final String? memo;
|
final String? memo;
|
||||||
|
|
||||||
final List<({String address, Amount amount, bool isChange})>? recipients;
|
final List<TxRecipient>? recipients;
|
||||||
final Set<UTXO>? utxos;
|
final Set<UTXO>? utxos;
|
||||||
final List<UTXO>? usedUTXOs;
|
final List<UTXO>? usedUTXOs;
|
||||||
|
|
||||||
|
@ -76,6 +79,8 @@ class TxData {
|
||||||
|
|
||||||
final TransactionV2? tempTx;
|
final TransactionV2? tempTx;
|
||||||
|
|
||||||
|
final bool ignoreCachedBalanceChecks;
|
||||||
|
|
||||||
TxData({
|
TxData({
|
||||||
this.feeRateType,
|
this.feeRateType,
|
||||||
this.feeRateAmount,
|
this.feeRateAmount,
|
||||||
|
@ -112,6 +117,7 @@ class TxData {
|
||||||
this.sparkMints,
|
this.sparkMints,
|
||||||
this.usedSparkCoins,
|
this.usedSparkCoins,
|
||||||
this.tempTx,
|
this.tempTx,
|
||||||
|
this.ignoreCachedBalanceChecks = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
Amount? get amount => recipients != null && recipients!.isNotEmpty
|
Amount? get amount => recipients != null && recipients!.isNotEmpty
|
||||||
|
@ -161,13 +167,7 @@ class TxData {
|
||||||
String? memo,
|
String? memo,
|
||||||
Set<UTXO>? utxos,
|
Set<UTXO>? utxos,
|
||||||
List<UTXO>? usedUTXOs,
|
List<UTXO>? usedUTXOs,
|
||||||
List<
|
List<TxRecipient>? recipients,
|
||||||
({
|
|
||||||
String address,
|
|
||||||
Amount amount,
|
|
||||||
bool isChange,
|
|
||||||
})>?
|
|
||||||
recipients,
|
|
||||||
String? frostMSConfig,
|
String? frostMSConfig,
|
||||||
List<String>? frostSigners,
|
List<String>? frostSigners,
|
||||||
String? changeAddress,
|
String? changeAddress,
|
||||||
|
@ -196,6 +196,7 @@ class TxData {
|
||||||
List<TxData>? sparkMints,
|
List<TxData>? sparkMints,
|
||||||
List<SparkCoin>? usedSparkCoins,
|
List<SparkCoin>? usedSparkCoins,
|
||||||
TransactionV2? tempTx,
|
TransactionV2? tempTx,
|
||||||
|
bool? ignoreCachedBalanceChecks,
|
||||||
}) {
|
}) {
|
||||||
return TxData(
|
return TxData(
|
||||||
feeRateType: feeRateType ?? this.feeRateType,
|
feeRateType: feeRateType ?? this.feeRateType,
|
||||||
|
@ -235,6 +236,8 @@ class TxData {
|
||||||
sparkMints: sparkMints ?? this.sparkMints,
|
sparkMints: sparkMints ?? this.sparkMints,
|
||||||
usedSparkCoins: usedSparkCoins ?? this.usedSparkCoins,
|
usedSparkCoins: usedSparkCoins ?? this.usedSparkCoins,
|
||||||
tempTx: tempTx ?? this.tempTx,
|
tempTx: tempTx ?? this.tempTx,
|
||||||
|
ignoreCachedBalanceChecks:
|
||||||
|
ignoreCachedBalanceChecks ?? this.ignoreCachedBalanceChecks,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,5 +277,6 @@ class TxData {
|
||||||
'sparkMints: $sparkMints, '
|
'sparkMints: $sparkMints, '
|
||||||
'usedSparkCoins: $usedSparkCoins, '
|
'usedSparkCoins: $usedSparkCoins, '
|
||||||
'tempTx: $tempTx, '
|
'tempTx: $tempTx, '
|
||||||
|
'ignoreCachedBalanceChecks: $ignoreCachedBalanceChecks, '
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,18 @@ import '../../crypto_currency/crypto_currency.dart';
|
||||||
import '../../crypto_currency/interfaces/paynym_currency_interface.dart';
|
import '../../crypto_currency/interfaces/paynym_currency_interface.dart';
|
||||||
import '../intermediate/bip39_hd_wallet.dart';
|
import '../intermediate/bip39_hd_wallet.dart';
|
||||||
import '../wallet_mixin_interfaces/coin_control_interface.dart';
|
import '../wallet_mixin_interfaces/coin_control_interface.dart';
|
||||||
|
import '../wallet_mixin_interfaces/cpfp_interface.dart';
|
||||||
import '../wallet_mixin_interfaces/electrumx_interface.dart';
|
import '../wallet_mixin_interfaces/electrumx_interface.dart';
|
||||||
import '../wallet_mixin_interfaces/paynym_interface.dart';
|
import '../wallet_mixin_interfaces/paynym_interface.dart';
|
||||||
|
import '../wallet_mixin_interfaces/rbf_interface.dart';
|
||||||
|
|
||||||
class BitcoinWallet<T extends PaynymCurrencyInterface> extends Bip39HDWallet<T>
|
class BitcoinWallet<T extends PaynymCurrencyInterface> extends Bip39HDWallet<T>
|
||||||
with ElectrumXInterface<T>, CoinControlInterface, PaynymInterface<T> {
|
with
|
||||||
|
ElectrumXInterface<T>,
|
||||||
|
CoinControlInterface,
|
||||||
|
PaynymInterface<T>,
|
||||||
|
RbfInterface<T>,
|
||||||
|
CpfpInterface<T> {
|
||||||
@override
|
@override
|
||||||
int get isarTransactionVersion => 2;
|
int get isarTransactionVersion => 2;
|
||||||
|
|
||||||
|
|
314
lib/wallets/wallet/impl/dash_wallet.dart
Normal file
314
lib/wallets/wallet/impl/dash_wallet.dart
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
|
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
import '../../../models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||||
|
import '../../../models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||||
|
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
|
import '../../../utilities/amount/amount.dart';
|
||||||
|
import '../../../utilities/logger.dart';
|
||||||
|
import '../../crypto_currency/crypto_currency.dart';
|
||||||
|
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||||
|
import '../intermediate/bip39_hd_wallet.dart';
|
||||||
|
import '../wallet_mixin_interfaces/coin_control_interface.dart';
|
||||||
|
import '../wallet_mixin_interfaces/electrumx_interface.dart';
|
||||||
|
|
||||||
|
class DashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
||||||
|
with ElectrumXInterface<T>, CoinControlInterface {
|
||||||
|
DashWallet(CryptoCurrencyNetwork network) : super(Dash(network) as T);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get maximumFeerate => 2500;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get isarTransactionVersion => 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get changeAddressFilterOperation =>
|
||||||
|
FilterGroup.and(standardChangeAddressFilters);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get receivingAddressFilterOperation =>
|
||||||
|
FilterGroup.and(standardReceivingAddressFilters);
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Address>> fetchAddressesForElectrumXScan() async {
|
||||||
|
final allAddresses = await mainDB
|
||||||
|
.getAddresses(walletId)
|
||||||
|
.filter()
|
||||||
|
.not()
|
||||||
|
.group(
|
||||||
|
(q) => q
|
||||||
|
.typeEqualTo(AddressType.nonWallet)
|
||||||
|
.or()
|
||||||
|
.subTypeEqualTo(AddressSubType.nonWallet),
|
||||||
|
)
|
||||||
|
.findAll();
|
||||||
|
return allAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateTransactions() async {
|
||||||
|
// Get all addresses.
|
||||||
|
final List<Address> allAddressesOld =
|
||||||
|
await fetchAddressesForElectrumXScan();
|
||||||
|
|
||||||
|
// Separate receiving and change addresses.
|
||||||
|
final Set<String> receivingAddresses = allAddressesOld
|
||||||
|
.where((e) => e.subType == AddressSubType.receiving)
|
||||||
|
.map((e) => e.value)
|
||||||
|
.toSet();
|
||||||
|
final Set<String> changeAddresses = allAddressesOld
|
||||||
|
.where((e) => e.subType == AddressSubType.change)
|
||||||
|
.map((e) => e.value)
|
||||||
|
.toSet();
|
||||||
|
|
||||||
|
// Remove duplicates.
|
||||||
|
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
|
||||||
|
|
||||||
|
// Fetch history from ElectrumX.
|
||||||
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
|
await fetchHistory(allAddressesSet);
|
||||||
|
|
||||||
|
// Only parse new txs (not in db yet).
|
||||||
|
final List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
for (final txHash in allTxHashes) {
|
||||||
|
// Check for duplicates by searching for tx by tx_hash in db.
|
||||||
|
final storedTx = await mainDB.isar.transactionV2s
|
||||||
|
.where()
|
||||||
|
.txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (storedTx == null ||
|
||||||
|
storedTx.height == null ||
|
||||||
|
(storedTx.height != null && storedTx.height! <= 0)) {
|
||||||
|
// Tx not in db yet.
|
||||||
|
final tx = await electrumXCachedClient.getTransaction(
|
||||||
|
txHash: txHash["tx_hash"] as String,
|
||||||
|
verbose: true,
|
||||||
|
cryptoCurrency: cryptoCurrency,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only tx to list once.
|
||||||
|
if (allTransactions
|
||||||
|
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
||||||
|
-1) {
|
||||||
|
tx["height"] = txHash["height"];
|
||||||
|
allTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse all new txs.
|
||||||
|
final List<TransactionV2> txns = [];
|
||||||
|
for (final txData in allTransactions) {
|
||||||
|
bool wasSentFromThisWallet = false;
|
||||||
|
// Set to true if any inputs were detected as owned by this wallet.
|
||||||
|
|
||||||
|
bool wasReceivedInThisWallet = false;
|
||||||
|
// Set to true if any outputs were detected as owned by this wallet.
|
||||||
|
|
||||||
|
// Parse inputs.
|
||||||
|
BigInt amountReceivedInThisWallet = BigInt.zero;
|
||||||
|
BigInt changeAmountReceivedInThisWallet = BigInt.zero;
|
||||||
|
final List<InputV2> inputs = [];
|
||||||
|
for (final jsonInput in txData["vin"] as List) {
|
||||||
|
final map = Map<String, dynamic>.from(jsonInput as Map);
|
||||||
|
|
||||||
|
final List<String> addresses = [];
|
||||||
|
String valueStringSats = "0";
|
||||||
|
OutpointV2? outpoint;
|
||||||
|
|
||||||
|
final coinbase = map["coinbase"] as String?;
|
||||||
|
|
||||||
|
if (coinbase == null) {
|
||||||
|
// Not a coinbase (ie a typical input).
|
||||||
|
final txid = map["txid"] as String;
|
||||||
|
final vout = map["vout"] as int;
|
||||||
|
|
||||||
|
final inputTx = await electrumXCachedClient.getTransaction(
|
||||||
|
txHash: txid,
|
||||||
|
cryptoCurrency: cryptoCurrency,
|
||||||
|
);
|
||||||
|
|
||||||
|
final prevOutJson = Map<String, dynamic>.from(
|
||||||
|
(inputTx["vout"] as List).firstWhere((e) => e["n"] == vout) as Map,
|
||||||
|
);
|
||||||
|
|
||||||
|
final prevOut = OutputV2.fromElectrumXJson(
|
||||||
|
prevOutJson,
|
||||||
|
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||||
|
isFullAmountNotSats: true,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
|
scriptSigHex: map["scriptSig"]?["hex"] as String?,
|
||||||
|
scriptSigAsm: map["scriptSig"]?["asm"] as String?,
|
||||||
|
sequence: map["sequence"] as int?,
|
||||||
|
outpoint: outpoint,
|
||||||
|
valueStringSats: valueStringSats,
|
||||||
|
addresses: addresses,
|
||||||
|
witness: map["witness"] as String?,
|
||||||
|
coinbase: coinbase,
|
||||||
|
innerRedeemScriptAsm: map["innerRedeemscriptAsm"] as String?,
|
||||||
|
// Need addresses before we can know if the wallet owns this input.
|
||||||
|
walletOwns: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if input was from this wallet.
|
||||||
|
if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
|
||||||
|
wasSentFromThisWallet = true;
|
||||||
|
input = input.copyWith(walletOwns: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.add(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse outputs.
|
||||||
|
final List<OutputV2> outputs = [];
|
||||||
|
for (final outputJson in txData["vout"] as List) {
|
||||||
|
OutputV2 output = OutputV2.fromElectrumXJson(
|
||||||
|
Map<String, dynamic>.from(outputJson as Map),
|
||||||
|
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||||
|
isFullAmountNotSats: true,
|
||||||
|
// Need addresses before we can know if the wallet owns this input.
|
||||||
|
walletOwns: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalOut = outputs
|
||||||
|
.map((e) => e.value)
|
||||||
|
.fold(BigInt.zero, (value, element) => value + element);
|
||||||
|
|
||||||
|
TransactionType type;
|
||||||
|
final TransactionSubType subType = TransactionSubType.none;
|
||||||
|
|
||||||
|
// At least one input was owned by this wallet.
|
||||||
|
if (wasSentFromThisWallet) {
|
||||||
|
type = TransactionType.outgoing;
|
||||||
|
|
||||||
|
if (wasReceivedInThisWallet) {
|
||||||
|
if (changeAmountReceivedInThisWallet + amountReceivedInThisWallet ==
|
||||||
|
totalOut) {
|
||||||
|
// Definitely sent all to self.
|
||||||
|
type = TransactionType.sentToSelf;
|
||||||
|
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
||||||
|
// Most likely just a typical send, do nothing here yet.
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is where we would check for them.
|
||||||
|
// TODO: [prio=high] Check for special Dash outputs.
|
||||||
|
}
|
||||||
|
} else if (wasReceivedInThisWallet) {
|
||||||
|
// Only found outputs owned by this wallet.
|
||||||
|
type = TransactionType.incoming;
|
||||||
|
} else {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Unexpected tx found (ignoring it): $txData",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tx = TransactionV2(
|
||||||
|
walletId: walletId,
|
||||||
|
blockHash: txData["blockhash"] as String?,
|
||||||
|
hash: txData["txid"] as String,
|
||||||
|
txid: txData["txid"] as String,
|
||||||
|
height: txData["height"] as int?,
|
||||||
|
version: txData["version"] as int,
|
||||||
|
timestamp: txData["blocktime"] as int? ??
|
||||||
|
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
inputs: List.unmodifiable(inputs),
|
||||||
|
outputs: List.unmodifiable(outputs),
|
||||||
|
type: type,
|
||||||
|
subType: subType,
|
||||||
|
otherData: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
txns.add(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mainDB.updateOrPutTransactionV2s(txns);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<({String? blockedReason, bool blocked, String? utxoLabel})>
|
||||||
|
checkBlockUTXO(
|
||||||
|
Map<String, dynamic> jsonUTXO,
|
||||||
|
String? scriptPubKeyHex,
|
||||||
|
Map<String, dynamic> jsonTX,
|
||||||
|
String? utxoOwnerAddress,
|
||||||
|
) async {
|
||||||
|
bool blocked = false;
|
||||||
|
String? blockedReason;
|
||||||
|
|
||||||
|
// // check for bip47 notification
|
||||||
|
// final outputs = jsonTX["vout"] as List;
|
||||||
|
// for (final output in outputs) {
|
||||||
|
// final List<String>? scriptChunks =
|
||||||
|
// (output['scriptPubKey']?['asm'] as String?)?.split(" ");
|
||||||
|
// if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||||
|
// final blindedPaymentCode = scriptChunks![1];
|
||||||
|
// final bytes = blindedPaymentCode.toUint8ListFromHex;
|
||||||
|
//
|
||||||
|
// // https://en.bitcoin.it/wiki/BIP_0047#Sending
|
||||||
|
// if (bytes.length == 80 && bytes.first == 1) {
|
||||||
|
// blocked = true;
|
||||||
|
// blockedReason = "Paynym notification output. Incautious "
|
||||||
|
// "handling of outputs from notification transactions "
|
||||||
|
// "may cause unintended loss of privacy.";
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (blockedReason: blockedReason, blocked: blocked, utxoLabel: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) {
|
||||||
|
return Amount(
|
||||||
|
rawValue: BigInt.from(
|
||||||
|
((181 * inputCount) + (34 * outputCount) + 10) *
|
||||||
|
(feeRatePerKB / 1000).ceil(),
|
||||||
|
),
|
||||||
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int estimateTxFee({required int vSize, required int feeRatePerKB}) {
|
||||||
|
return vSize * (feeRatePerKB / 1000).ceil();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
|
@ -14,10 +16,15 @@ import '../intermediate/bip39_hd_wallet.dart';
|
||||||
import '../wallet_mixin_interfaces/coin_control_interface.dart';
|
import '../wallet_mixin_interfaces/coin_control_interface.dart';
|
||||||
import '../wallet_mixin_interfaces/electrumx_interface.dart';
|
import '../wallet_mixin_interfaces/electrumx_interface.dart';
|
||||||
import '../wallet_mixin_interfaces/ordinals_interface.dart';
|
import '../wallet_mixin_interfaces/ordinals_interface.dart';
|
||||||
|
import '../wallet_mixin_interfaces/rbf_interface.dart';
|
||||||
|
|
||||||
class LitecoinWallet<T extends ElectrumXCurrencyInterface>
|
class LitecoinWallet<T extends ElectrumXCurrencyInterface>
|
||||||
extends Bip39HDWallet<T>
|
extends Bip39HDWallet<T>
|
||||||
with ElectrumXInterface<T>, CoinControlInterface<T>, OrdinalsInterface<T> {
|
with
|
||||||
|
ElectrumXInterface<T>,
|
||||||
|
CoinControlInterface<T>,
|
||||||
|
RbfInterface<T>,
|
||||||
|
OrdinalsInterface<T> {
|
||||||
@override
|
@override
|
||||||
int get isarTransactionVersion => 2;
|
int get isarTransactionVersion => 2;
|
||||||
|
|
||||||
|
@ -285,6 +292,14 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? otherData;
|
||||||
|
if (txData["size"] is int || txData["vsize"] is int) {
|
||||||
|
otherData = jsonEncode({
|
||||||
|
TxV2OdKeys.size: txData["size"] as int?,
|
||||||
|
TxV2OdKeys.vSize: txData["vsize"] as int?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
final tx = TransactionV2(
|
final tx = TransactionV2(
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
blockHash: txData["blockhash"] as String?,
|
blockHash: txData["blockhash"] as String?,
|
||||||
|
@ -298,7 +313,7 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
|
||||||
outputs: List.unmodifiable(outputs),
|
outputs: List.unmodifiable(outputs),
|
||||||
type: type,
|
type: type,
|
||||||
subType: subType,
|
subType: subType,
|
||||||
otherData: null,
|
otherData: otherData,
|
||||||
);
|
);
|
||||||
|
|
||||||
txns.add(tx);
|
txns.add(tx);
|
||||||
|
|
|
@ -46,7 +46,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
publicKey: List<int>.empty(),
|
publicKey: List<int>.empty(),
|
||||||
derivationIndex: 0,
|
derivationIndex: 0,
|
||||||
derivationPath: DerivationPath()..value = _addressDerivationPath,
|
derivationPath: DerivationPath()..value = _addressDerivationPath,
|
||||||
type: cryptoCurrency.primaryAddressType,
|
type: info.mainAddressType,
|
||||||
subType: AddressSubType.receiving,
|
subType: AddressSubType.receiving,
|
||||||
);
|
);
|
||||||
return addressStruct;
|
return addressStruct;
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:tezart/tezart.dart' as tezart;
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../../../models/balance.dart';
|
import '../../../models/balance.dart';
|
||||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
@ -18,8 +21,6 @@ import '../../crypto_currency/crypto_currency.dart';
|
||||||
import '../../isar/models/wallet_info.dart';
|
import '../../isar/models/wallet_info.dart';
|
||||||
import '../../models/tx_data.dart';
|
import '../../models/tx_data.dart';
|
||||||
import '../intermediate/bip39_wallet.dart';
|
import '../intermediate/bip39_wallet.dart';
|
||||||
import 'package:tezart/tezart.dart' as tezart;
|
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
// const kDefaultTransactionStorageLimit = 496;
|
// const kDefaultTransactionStorageLimit = 496;
|
||||||
// const kDefaultTransactionGasLimit = 10600;
|
// const kDefaultTransactionGasLimit = 10600;
|
||||||
|
@ -83,7 +84,7 @@ class TezosWallet extends Bip39Wallet<Tezos> {
|
||||||
publicKey: keyStore.publicKey.toUint8ListFromBase58CheckEncoded,
|
publicKey: keyStore.publicKey.toUint8ListFromBase58CheckEncoded,
|
||||||
derivationIndex: 0,
|
derivationIndex: 0,
|
||||||
derivationPath: DerivationPath()..value = derivationPath,
|
derivationPath: DerivationPath()..value = derivationPath,
|
||||||
type: info.coin.primaryAddressType,
|
type: info.mainAddressType,
|
||||||
subType: AddressSubType.receiving,
|
subType: AddressSubType.receiving,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
|
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
import '../../../models/balance.dart';
|
import '../../../models/balance.dart';
|
||||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
import '../../../utilities/amount/amount.dart';
|
import '../../../utilities/amount/amount.dart';
|
||||||
import '../../../utilities/enums/derive_path_type_enum.dart';
|
import '../../../utilities/enums/derive_path_type_enum.dart';
|
||||||
import '../../crypto_currency/intermediate/bip39_hd_currency.dart';
|
import '../../crypto_currency/intermediate/bip39_hd_currency.dart';
|
||||||
import 'bip39_wallet.dart';
|
|
||||||
import '../wallet_mixin_interfaces/multi_address_interface.dart';
|
import '../wallet_mixin_interfaces/multi_address_interface.dart';
|
||||||
|
import 'bip39_wallet.dart';
|
||||||
|
|
||||||
abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
||||||
with MultiAddressInterface<T> {
|
with MultiAddressInterface<T> {
|
||||||
|
@ -66,7 +67,7 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
||||||
final address = await _generateAddress(
|
final address = await _generateAddress(
|
||||||
chain: chain,
|
chain: chain,
|
||||||
index: index,
|
index: index,
|
||||||
derivePathType: info.coin.primaryDerivePathType,
|
derivePathType: _fromAddressType(info.mainAddressType),
|
||||||
);
|
);
|
||||||
|
|
||||||
await mainDB.updateOrPutAddresses([address]);
|
await mainDB.updateOrPutAddresses([address]);
|
||||||
|
@ -88,7 +89,7 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
||||||
final address = await _generateAddress(
|
final address = await _generateAddress(
|
||||||
chain: chain,
|
chain: chain,
|
||||||
index: index,
|
index: index,
|
||||||
derivePathType: info.coin.primaryDerivePathType,
|
derivePathType: _fromAddressType(info.mainAddressType),
|
||||||
);
|
);
|
||||||
|
|
||||||
await mainDB.updateOrPutAddresses([address]);
|
await mainDB.updateOrPutAddresses([address]);
|
||||||
|
@ -101,7 +102,7 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
||||||
final address = await _generateAddress(
|
final address = await _generateAddress(
|
||||||
chain: 0, // receiving
|
chain: 0, // receiving
|
||||||
index: 0, // initial index
|
index: 0, // initial index
|
||||||
derivePathType: info.coin.primaryDerivePathType,
|
derivePathType: _fromAddressType(info.mainAddressType),
|
||||||
);
|
);
|
||||||
|
|
||||||
await mainDB.updateOrPutAddresses([address]);
|
await mainDB.updateOrPutAddresses([address]);
|
||||||
|
@ -118,6 +119,37 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
||||||
|
|
||||||
// ========== Private ========================================================
|
// ========== Private ========================================================
|
||||||
|
|
||||||
|
DerivePathType _fromAddressType(AddressType addressType) {
|
||||||
|
switch (addressType) {
|
||||||
|
case AddressType.p2pkh:
|
||||||
|
// DerivePathType.bip44:
|
||||||
|
// DerivePathType.bch44:
|
||||||
|
// DerivePathType.eCash44:
|
||||||
|
// Should be one of the above due to silly case due to bch and ecash
|
||||||
|
return info.coin.defaultDerivePathType;
|
||||||
|
|
||||||
|
case AddressType.p2sh:
|
||||||
|
return DerivePathType.bip49;
|
||||||
|
|
||||||
|
case AddressType.p2wpkh:
|
||||||
|
return DerivePathType.bip84;
|
||||||
|
|
||||||
|
case AddressType.p2tr:
|
||||||
|
return DerivePathType.bip86;
|
||||||
|
|
||||||
|
case AddressType.solana:
|
||||||
|
return DerivePathType.solana;
|
||||||
|
|
||||||
|
case AddressType.ethereum:
|
||||||
|
return DerivePathType.eth;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw ArgumentError(
|
||||||
|
"Incompatible AddressType \"$addressType\" passed to DerivePathType.fromAddressType()",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Address> _generateAddress({
|
Future<Address> _generateAddress({
|
||||||
required int chain,
|
required int chain,
|
||||||
required int index,
|
required int index,
|
||||||
|
|
|
@ -28,6 +28,7 @@ import 'impl/banano_wallet.dart';
|
||||||
import 'impl/bitcoin_frost_wallet.dart';
|
import 'impl/bitcoin_frost_wallet.dart';
|
||||||
import 'impl/bitcoin_wallet.dart';
|
import 'impl/bitcoin_wallet.dart';
|
||||||
import 'impl/bitcoincash_wallet.dart';
|
import 'impl/bitcoincash_wallet.dart';
|
||||||
|
import 'impl/dash_wallet.dart';
|
||||||
import 'impl/dogecoin_wallet.dart';
|
import 'impl/dogecoin_wallet.dart';
|
||||||
import 'impl/ecash_wallet.dart';
|
import 'impl/ecash_wallet.dart';
|
||||||
import 'impl/epiccash_wallet.dart';
|
import 'impl/epiccash_wallet.dart';
|
||||||
|
@ -323,6 +324,9 @@ abstract class Wallet<T extends CryptoCurrency> {
|
||||||
case const (Bitcoincash):
|
case const (Bitcoincash):
|
||||||
return BitcoincashWallet(net);
|
return BitcoincashWallet(net);
|
||||||
|
|
||||||
|
case const (Dash):
|
||||||
|
return DashWallet(net);
|
||||||
|
|
||||||
case const (Dogecoin):
|
case const (Dogecoin):
|
||||||
return DogecoinWallet(net);
|
return DogecoinWallet(net);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
import '../../../models/isar/models/isar_models.dart';
|
import '../../../models/isar/models/isar_models.dart';
|
||||||
import '../../../models/signing_data.dart';
|
import '../../../models/signing_data.dart';
|
||||||
import '../../../utilities/logger.dart';
|
import '../../../utilities/logger.dart';
|
||||||
import '../../crypto_currency/crypto_currency.dart';
|
|
||||||
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||||
import '../../models/tx_data.dart';
|
import '../../models/tx_data.dart';
|
||||||
import '../intermediate/bip39_hd_wallet.dart';
|
import '../intermediate/bip39_hd_wallet.dart';
|
||||||
|
@ -27,7 +26,7 @@ mixin BCashInterface<T extends ElectrumXCurrencyInterface>
|
||||||
// TODO: use coinlib
|
// TODO: use coinlib
|
||||||
|
|
||||||
final builder = bitbox.Bitbox.transactionBuilder(
|
final builder = bitbox.Bitbox.transactionBuilder(
|
||||||
testnet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
testnet: cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
builder.setVersion(cryptoCurrency.transactionVersion);
|
builder.setVersion(cryptoCurrency.transactionVersion);
|
||||||
|
@ -100,7 +99,7 @@ mixin BCashInterface<T extends ElectrumXCurrencyInterface>
|
||||||
network: bitbox_utils.Network(
|
network: bitbox_utils.Network(
|
||||||
cryptoCurrency.networkParams.privHDPrefix,
|
cryptoCurrency.networkParams.privHDPrefix,
|
||||||
cryptoCurrency.networkParams.pubHDPrefix,
|
cryptoCurrency.networkParams.pubHDPrefix,
|
||||||
cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
cryptoCurrency.network.isTestNet,
|
||||||
cryptoCurrency.networkParams.p2pkhPrefix,
|
cryptoCurrency.networkParams.p2pkhPrefix,
|
||||||
cryptoCurrency.networkParams.wifPrefix,
|
cryptoCurrency.networkParams.wifPrefix,
|
||||||
cryptoCurrency.networkParams.p2pkhPrefix,
|
cryptoCurrency.networkParams.p2pkhPrefix,
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||||
|
import 'electrumx_interface.dart';
|
||||||
|
|
||||||
|
mixin CpfpInterface<T extends ElectrumXCurrencyInterface>
|
||||||
|
on ElectrumXInterface<T> {
|
||||||
|
//
|
||||||
|
}
|
|
@ -26,7 +26,9 @@ import '../../models/tx_data.dart';
|
||||||
import '../impl/bitcoin_wallet.dart';
|
import '../impl/bitcoin_wallet.dart';
|
||||||
import '../impl/peercoin_wallet.dart';
|
import '../impl/peercoin_wallet.dart';
|
||||||
import '../intermediate/bip39_hd_wallet.dart';
|
import '../intermediate/bip39_hd_wallet.dart';
|
||||||
|
import 'cpfp_interface.dart';
|
||||||
import 'paynym_interface.dart';
|
import 'paynym_interface.dart';
|
||||||
|
import 'rbf_interface.dart';
|
||||||
|
|
||||||
mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
on Bip39HDWallet<T> {
|
on Bip39HDWallet<T> {
|
||||||
|
@ -122,12 +124,16 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
utxos ?? await mainDB.getUTXOs(walletId).findAll();
|
utxos ?? await mainDB.getUTXOs(walletId).findAll();
|
||||||
final currentChainHeight = await chainHeight;
|
final currentChainHeight = await chainHeight;
|
||||||
|
|
||||||
|
final canCPFP = this is CpfpInterface && coinControl;
|
||||||
|
|
||||||
final spendableOutputs = availableOutputs
|
final spendableOutputs = availableOutputs
|
||||||
.where(
|
.where(
|
||||||
(e) =>
|
(e) =>
|
||||||
!e.isBlocked &&
|
!e.isBlocked &&
|
||||||
(e.used != true) &&
|
(e.used != true) &&
|
||||||
e.isConfirmed(currentChainHeight, cryptoCurrency.minConfirms),
|
(canCPFP ||
|
||||||
|
e.isConfirmed(
|
||||||
|
currentChainHeight, cryptoCurrency.minConfirms)),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
final spendableSatoshiValue =
|
final spendableSatoshiValue =
|
||||||
|
@ -628,6 +634,11 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
outputs: [],
|
outputs: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: [prio=high]: check this opt in rbf
|
||||||
|
final sequence = this is RbfInterface && (this as RbfInterface).flagOptInRBF
|
||||||
|
? 0xffffffff - 10
|
||||||
|
: 0xffffffff - 1;
|
||||||
|
|
||||||
// Add transaction inputs
|
// Add transaction inputs
|
||||||
for (var i = 0; i < utxoSigningData.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxoSigningData[i].utxo.txid;
|
final txid = utxoSigningData[i].utxo.txid;
|
||||||
|
@ -659,7 +670,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
input = coinlib.P2PKHInput(
|
input = coinlib.P2PKHInput(
|
||||||
prevOut: prevOutpoint,
|
prevOut: prevOutpoint,
|
||||||
publicKey: utxoSigningData[i].keyPair!.publicKey,
|
publicKey: utxoSigningData[i].keyPair!.publicKey,
|
||||||
sequence: 0xffffffff - 1,
|
sequence: sequence,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: fix this as it is (probably) wrong!
|
// TODO: fix this as it is (probably) wrong!
|
||||||
|
@ -670,14 +681,14 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
// program: coinlib.MultisigProgram.decompile(
|
// program: coinlib.MultisigProgram.decompile(
|
||||||
// utxoSigningData[i].redeemScript!,
|
// utxoSigningData[i].redeemScript!,
|
||||||
// ),
|
// ),
|
||||||
// sequence: 0xffffffff - 1,
|
// sequence: sequence,
|
||||||
// );
|
// );
|
||||||
|
|
||||||
case DerivePathType.bip84:
|
case DerivePathType.bip84:
|
||||||
input = coinlib.P2WPKHInput(
|
input = coinlib.P2WPKHInput(
|
||||||
prevOut: prevOutpoint,
|
prevOut: prevOutpoint,
|
||||||
publicKey: utxoSigningData[i].keyPair!.publicKey,
|
publicKey: utxoSigningData[i].keyPair!.publicKey,
|
||||||
sequence: 0xffffffff - 1,
|
sequence: sequence,
|
||||||
);
|
);
|
||||||
|
|
||||||
case DerivePathType.bip86:
|
case DerivePathType.bip86:
|
||||||
|
@ -695,7 +706,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
InputV2.isarCantDoRequiredInDefaultConstructor(
|
InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
scriptSigHex: input.scriptSig.toHex,
|
scriptSigHex: input.scriptSig.toHex,
|
||||||
scriptSigAsm: null,
|
scriptSigAsm: null,
|
||||||
sequence: 0xffffffff - 1,
|
sequence: sequence,
|
||||||
outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor(
|
outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
txid: utxoSigningData[i].utxo.txid,
|
txid: utxoSigningData[i].utxo.txid,
|
||||||
vout: utxoSigningData[i].utxo.vout,
|
vout: utxoSigningData[i].utxo.vout,
|
||||||
|
@ -1648,12 +1659,21 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
if (customSatsPerVByte != null) {
|
if (customSatsPerVByte != null) {
|
||||||
// check for send all
|
// check for send all
|
||||||
bool isSendAll = false;
|
bool isSendAll = false;
|
||||||
if (txData.amount == info.cachedBalance.spendable) {
|
if (txData.ignoreCachedBalanceChecks ||
|
||||||
|
txData.amount == info.cachedBalance.spendable) {
|
||||||
isSendAll = true;
|
isSendAll = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final bool coinControl = utxos != null;
|
final bool coinControl = utxos != null;
|
||||||
|
|
||||||
|
if (coinControl &&
|
||||||
|
this is CpfpInterface &&
|
||||||
|
txData.amount ==
|
||||||
|
(info.cachedBalance.spendable +
|
||||||
|
info.cachedBalance.pendingSpendable)) {
|
||||||
|
isSendAll = true;
|
||||||
|
}
|
||||||
|
|
||||||
final result = await coinSelection(
|
final result = await coinSelection(
|
||||||
txData: txData.copyWith(feeRateAmount: -1),
|
txData: txData.copyWith(feeRateAmount: -1),
|
||||||
isSendAll: isSendAll,
|
isSendAll: isSendAll,
|
||||||
|
|
|
@ -6,6 +6,8 @@ import 'package:bitcoindart/bitcoindart.dart' as bitcoindart;
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:lelantus/lelantus.dart' as lelantus;
|
import 'package:lelantus/lelantus.dart' as lelantus;
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../../../models/balance.dart';
|
import '../../../models/balance.dart';
|
||||||
import '../../../models/isar/models/isar_models.dart';
|
import '../../../models/isar/models/isar_models.dart';
|
||||||
import '../../../models/lelantus_fee_data.dart';
|
import '../../../models/lelantus_fee_data.dart';
|
||||||
|
@ -15,12 +17,10 @@ import '../../../utilities/extensions/impl/uint8_list.dart';
|
||||||
import '../../../utilities/format.dart';
|
import '../../../utilities/format.dart';
|
||||||
import '../../../utilities/logger.dart';
|
import '../../../utilities/logger.dart';
|
||||||
import '../../api/lelantus_ffi_wrapper.dart';
|
import '../../api/lelantus_ffi_wrapper.dart';
|
||||||
import '../../crypto_currency/crypto_currency.dart';
|
|
||||||
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||||
import '../../models/tx_data.dart';
|
import '../../models/tx_data.dart';
|
||||||
import '../intermediate/bip39_hd_wallet.dart';
|
import '../intermediate/bip39_hd_wallet.dart';
|
||||||
import 'electrumx_interface.dart';
|
import 'electrumx_interface.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
mixin LelantusInterface<T extends ElectrumXCurrencyInterface>
|
mixin LelantusInterface<T extends ElectrumXCurrencyInterface>
|
||||||
on Bip39HDWallet<T>, ElectrumXInterface<T> {
|
on Bip39HDWallet<T>, ElectrumXInterface<T> {
|
||||||
|
@ -38,7 +38,7 @@ mixin LelantusInterface<T extends ElectrumXCurrencyInterface>
|
||||||
spendAmount: amount,
|
spendAmount: amount,
|
||||||
subtractFeeFromAmount: true,
|
subtractFeeFromAmount: true,
|
||||||
lelantusEntries: lelantusEntries,
|
lelantusEntries: lelantusEntries,
|
||||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestNet: cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Amount(
|
return Amount(
|
||||||
|
@ -526,7 +526,7 @@ mixin LelantusInterface<T extends ElectrumXCurrencyInterface>
|
||||||
int.parse(coin.value),
|
int.parse(coin.value),
|
||||||
mintKeyPair.privateKey.data.toHex,
|
mintKeyPair.privateKey.data.toHex,
|
||||||
coin.mintIndex,
|
coin.mintIndex,
|
||||||
isTestnet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestnet: cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
final bool isUsed = usedSerialNumbersSet.contains(serialNumber);
|
final bool isUsed = usedSerialNumbersSet.contains(serialNumber);
|
||||||
|
|
||||||
|
@ -1033,7 +1033,7 @@ mixin LelantusInterface<T extends ElectrumXCurrencyInterface>
|
||||||
await mainDB.getHighestUsedMintIndex(walletId: walletId);
|
await mainDB.getHighestUsedMintIndex(walletId: walletId);
|
||||||
final nextFreeMintIndex = (lastUsedIndex ?? 0) + 1;
|
final nextFreeMintIndex = (lastUsedIndex ?? 0) + 1;
|
||||||
|
|
||||||
final isTestnet = cryptoCurrency.network == CryptoCurrencyNetwork.test;
|
final isTestnet = cryptoCurrency.network.isTestNet;
|
||||||
|
|
||||||
final root = await getRootHDNode();
|
final root = await getRootHDNode();
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
||||||
publicKey: publicKey.toUint8ListFromHex,
|
publicKey: publicKey.toUint8ListFromHex,
|
||||||
derivationIndex: 0,
|
derivationIndex: 0,
|
||||||
derivationPath: null,
|
derivationPath: null,
|
||||||
type: cryptoCurrency.primaryAddressType,
|
type: info.mainAddressType,
|
||||||
subType: AddressSubType.receiving,
|
subType: AddressSubType.receiving,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -599,7 +599,7 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
||||||
value: tx["account"].toString(),
|
value: tx["account"].toString(),
|
||||||
derivationIndex: 0,
|
derivationIndex: 0,
|
||||||
derivationPath: null,
|
derivationPath: null,
|
||||||
type: info.coin.primaryAddressType,
|
type: info.mainAddressType,
|
||||||
subType: AddressSubType.nonWallet,
|
subType: AddressSubType.nonWallet,
|
||||||
);
|
);
|
||||||
final Tuple2<Transaction, Address> tuple = Tuple2(transaction, address);
|
final Tuple2<Transaction, Address> tuple = Tuple2(transaction, address);
|
||||||
|
|
|
@ -68,7 +68,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
final root = await _getRootNode();
|
final root = await _getRootNode();
|
||||||
final node = root.derivePath(
|
final node = root.derivePath(
|
||||||
_basePaynymDerivePath(
|
_basePaynymDerivePath(
|
||||||
testnet: info.coin.network == CryptoCurrencyNetwork.test,
|
testnet: info.coin.network.isTestNet,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return node;
|
return node;
|
||||||
|
@ -159,7 +159,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
final root = await _getRootNode();
|
final root = await _getRootNode();
|
||||||
final node = root.derivePath(
|
final node = root.derivePath(
|
||||||
_basePaynymDerivePath(
|
_basePaynymDerivePath(
|
||||||
testnet: info.coin.network == CryptoCurrencyNetwork.test,
|
testnet: info.coin.network.isTestNet,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
derivationPath: DerivationPath()
|
derivationPath: DerivationPath()
|
||||||
..value = _receivingPaynymAddressDerivationPath(
|
..value = _receivingPaynymAddressDerivationPath(
|
||||||
index,
|
index,
|
||||||
testnet: info.coin.network == CryptoCurrencyNetwork.test,
|
testnet: info.coin.network.isTestNet,
|
||||||
),
|
),
|
||||||
type: generateSegwitAddress ? AddressType.p2wpkh : AddressType.p2pkh,
|
type: generateSegwitAddress ? AddressType.p2wpkh : AddressType.p2pkh,
|
||||||
subType: AddressSubType.paynymReceive,
|
subType: AddressSubType.paynymReceive,
|
||||||
|
@ -219,7 +219,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
derivationPath: DerivationPath()
|
derivationPath: DerivationPath()
|
||||||
..value = _sendPaynymAddressDerivationPath(
|
..value = _sendPaynymAddressDerivationPath(
|
||||||
index,
|
index,
|
||||||
testnet: info.coin.network == CryptoCurrencyNetwork.test,
|
testnet: info.coin.network.isTestNet,
|
||||||
),
|
),
|
||||||
type: AddressType.nonWallet,
|
type: AddressType.nonWallet,
|
||||||
subType: AddressSubType.paynymSend,
|
subType: AddressSubType.paynymSend,
|
||||||
|
@ -314,7 +314,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
final node = root
|
final node = root
|
||||||
.derivePath(
|
.derivePath(
|
||||||
_basePaynymDerivePath(
|
_basePaynymDerivePath(
|
||||||
testnet: info.coin.network == CryptoCurrencyNetwork.test,
|
testnet: info.coin.network.isTestNet,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.derive(0);
|
.derive(0);
|
||||||
|
@ -330,7 +330,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
final paymentCode = PaymentCode.fromBip32Node(
|
final paymentCode = PaymentCode.fromBip32Node(
|
||||||
node.derivePath(
|
node.derivePath(
|
||||||
_basePaynymDerivePath(
|
_basePaynymDerivePath(
|
||||||
testnet: info.coin.network == CryptoCurrencyNetwork.test,
|
testnet: info.coin.network.isTestNet,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
networkType: networkType,
|
networkType: networkType,
|
||||||
|
@ -1469,7 +1469,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
final root = await _getRootNode();
|
final root = await _getRootNode();
|
||||||
final node = root.derivePath(
|
final node = root.derivePath(
|
||||||
_basePaynymDerivePath(
|
_basePaynymDerivePath(
|
||||||
testnet: info.coin.network == CryptoCurrencyNetwork.test,
|
testnet: info.coin.network.isTestNet,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final paymentCode = PaymentCode.fromBip32Node(
|
final paymentCode = PaymentCode.fromBip32Node(
|
||||||
|
@ -1497,7 +1497,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
derivationIndex: 0,
|
derivationIndex: 0,
|
||||||
derivationPath: DerivationPath()
|
derivationPath: DerivationPath()
|
||||||
..value = _notificationDerivationPath(
|
..value = _notificationDerivationPath(
|
||||||
testnet: info.coin.network == CryptoCurrencyNetwork.test,
|
testnet: info.coin.network.isTestNet,
|
||||||
),
|
),
|
||||||
type: AddressType.p2pkh,
|
type: AddressType.p2pkh,
|
||||||
subType: AddressSubType.paynymNotification,
|
subType: AddressSubType.paynymNotification,
|
||||||
|
@ -1617,6 +1617,24 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
final List<Map<String, dynamic>> allTxHashes =
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
await fetchHistory(allAddressesSet);
|
await fetchHistory(allAddressesSet);
|
||||||
|
|
||||||
|
final unconfirmedTxs = await mainDB.isar.transactionV2s
|
||||||
|
.where()
|
||||||
|
.walletIdEqualTo(walletId)
|
||||||
|
.filter()
|
||||||
|
.heightIsNull()
|
||||||
|
.or()
|
||||||
|
.heightEqualTo(0)
|
||||||
|
.txidProperty()
|
||||||
|
.findAll();
|
||||||
|
|
||||||
|
allTxHashes.addAll(
|
||||||
|
unconfirmedTxs.map(
|
||||||
|
(e) => {
|
||||||
|
"tx_hash": e,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Only parse new txs (not in db yet).
|
// Only parse new txs (not in db yet).
|
||||||
final List<Map<String, dynamic>> allTransactions = [];
|
final List<Map<String, dynamic>> allTransactions = [];
|
||||||
for (final txHash in allTxHashes) {
|
for (final txHash in allTxHashes) {
|
||||||
|
@ -1630,16 +1648,36 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
// storedTx.height == null ||
|
// storedTx.height == null ||
|
||||||
// (storedTx.height != null && storedTx.height! <= 0)) {
|
// (storedTx.height != null && storedTx.height! <= 0)) {
|
||||||
// Tx not in db yet.
|
// Tx not in db yet.
|
||||||
final tx = await electrumXCachedClient.getTransaction(
|
final txid = txHash["tx_hash"] as String;
|
||||||
txHash: txHash["tx_hash"] as String,
|
final Map<String, dynamic> tx;
|
||||||
verbose: true,
|
try {
|
||||||
cryptoCurrency: cryptoCurrency,
|
tx = await electrumXCachedClient.getTransaction(
|
||||||
);
|
txHash: txid,
|
||||||
|
verbose: true,
|
||||||
|
cryptoCurrency: cryptoCurrency,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// tx no longer exists then delete from local db
|
||||||
|
if (e.toString().contains(
|
||||||
|
"JSON-RPC error 2: daemon error: DaemonError({'code': -5, "
|
||||||
|
"'message': 'No such mempool or blockchain transaction",
|
||||||
|
)) {
|
||||||
|
await mainDB.isar.writeTxn(
|
||||||
|
() async => await mainDB.isar.transactionV2s
|
||||||
|
.where()
|
||||||
|
.walletIdEqualTo(walletId)
|
||||||
|
.filter()
|
||||||
|
.txidEqualTo(txid)
|
||||||
|
.deleteFirst(),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only tx to list once.
|
// Only tx to list once.
|
||||||
if (allTransactions
|
if (allTransactions.indexWhere((e) => e["txid"] == txid) == -1) {
|
||||||
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
|
||||||
-1) {
|
|
||||||
tx["height"] = txHash["height"];
|
tx["height"] = txHash["height"];
|
||||||
allTransactions.add(tx);
|
allTransactions.add(tx);
|
||||||
}
|
}
|
||||||
|
@ -1794,6 +1832,14 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? otherData;
|
||||||
|
if (txData["size"] is int || txData["vsize"] is int) {
|
||||||
|
otherData = jsonEncode({
|
||||||
|
TxV2OdKeys.size: txData["size"] as int?,
|
||||||
|
TxV2OdKeys.vSize: txData["vsize"] as int?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
final tx = TransactionV2(
|
final tx = TransactionV2(
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
blockHash: txData["blockhash"] as String?,
|
blockHash: txData["blockhash"] as String?,
|
||||||
|
@ -1807,7 +1853,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||||
outputs: List.unmodifiable(outputs),
|
outputs: List.unmodifiable(outputs),
|
||||||
type: type,
|
type: type,
|
||||||
subType: subType,
|
subType: subType,
|
||||||
otherData: null,
|
otherData: otherData,
|
||||||
);
|
);
|
||||||
|
|
||||||
txns.add(tx);
|
txns.add(tx);
|
||||||
|
|
283
lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart
Normal file
283
lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
|
import '../../../models/isar/models/isar_models.dart';
|
||||||
|
import '../../../utilities/amount/amount.dart';
|
||||||
|
import '../../../utilities/enums/fee_rate_type_enum.dart';
|
||||||
|
import '../../../utilities/logger.dart';
|
||||||
|
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||||
|
import '../../isar/models/wallet_info.dart';
|
||||||
|
import '../../models/tx_data.dart';
|
||||||
|
import 'electrumx_interface.dart';
|
||||||
|
|
||||||
|
typedef TxSize = ({int real, int virtual});
|
||||||
|
|
||||||
|
mixin RbfInterface<T extends ElectrumXCurrencyInterface>
|
||||||
|
on ElectrumXInterface<T> {
|
||||||
|
bool get flagOptInRBF =>
|
||||||
|
info.otherData[WalletInfoKeys.enableOptInRbf] as bool? ?? false;
|
||||||
|
|
||||||
|
Future<TxSize?> getVSize(String txid) async {
|
||||||
|
final tx = await electrumXCachedClient.getTransaction(
|
||||||
|
txHash: txid,
|
||||||
|
cryptoCurrency: cryptoCurrency,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (real: tx["size"] as int, virtual: tx["vsize"] as int);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TransactionV2> updateVSize(TransactionV2 transactionV2) async {
|
||||||
|
final size = await getVSize(transactionV2.txid);
|
||||||
|
final otherData = jsonDecode(transactionV2.otherData ?? "{}");
|
||||||
|
otherData[TxV2OdKeys.vSize] = size!.virtual;
|
||||||
|
otherData[TxV2OdKeys.size] = size.real;
|
||||||
|
final updatedTx = transactionV2.copyWith(otherData: jsonEncode(otherData));
|
||||||
|
await mainDB.updateOrPutTransactionV2s([updatedTx]);
|
||||||
|
return updatedTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TxData> prepareRbfSend({
|
||||||
|
required TransactionV2 oldTransaction,
|
||||||
|
required int newRate,
|
||||||
|
}) async {
|
||||||
|
final note = await mainDB.isar.transactionNotes
|
||||||
|
.where()
|
||||||
|
.walletIdEqualTo(walletId)
|
||||||
|
.filter()
|
||||||
|
.txidEqualTo(oldTransaction.txid)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
final Set<UTXO> utxos = {};
|
||||||
|
for (final input in oldTransaction.inputs) {
|
||||||
|
final utxo = UTXO(
|
||||||
|
walletId: walletId,
|
||||||
|
txid: input.outpoint!.txid,
|
||||||
|
vout: input.outpoint!.vout,
|
||||||
|
value: input.value.toInt(),
|
||||||
|
name: "rbf",
|
||||||
|
isBlocked: false,
|
||||||
|
blockedReason: null,
|
||||||
|
isCoinbase: false,
|
||||||
|
blockHash: "rbf",
|
||||||
|
blockHeight: 1,
|
||||||
|
blockTime: 1,
|
||||||
|
used: false,
|
||||||
|
address: input.addresses.first,
|
||||||
|
);
|
||||||
|
|
||||||
|
utxos.add(utxo);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<TxRecipient> recipients = [];
|
||||||
|
for (final output in oldTransaction.outputs) {
|
||||||
|
if (output.addresses.length != 1) {
|
||||||
|
throw UnsupportedError(
|
||||||
|
"Unexpected output.addresses.length: ${output.addresses.length}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final address = output.addresses.first;
|
||||||
|
final addressModel = await mainDB.getAddress(walletId, address);
|
||||||
|
final isChange = addressModel?.subType == AddressSubType.change;
|
||||||
|
|
||||||
|
recipients.add(
|
||||||
|
(
|
||||||
|
address: address,
|
||||||
|
amount: Amount(
|
||||||
|
rawValue: output.value,
|
||||||
|
fractionDigits: cryptoCurrency.fractionDigits),
|
||||||
|
isChange: isChange,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final oldFee = oldTransaction
|
||||||
|
.getFee(fractionDigits: cryptoCurrency.fractionDigits)
|
||||||
|
.raw;
|
||||||
|
final inSum = utxos
|
||||||
|
.map((e) => BigInt.from(e.value))
|
||||||
|
.fold(BigInt.zero, (p, e) => p + e);
|
||||||
|
|
||||||
|
final noChange =
|
||||||
|
recipients.map((e) => e.isChange).fold(false, (p, e) => p || e) ==
|
||||||
|
false;
|
||||||
|
final otherAvailableUtxos = await mainDB
|
||||||
|
.getUTXOs(walletId)
|
||||||
|
.filter()
|
||||||
|
.isBlockedEqualTo(false)
|
||||||
|
.and()
|
||||||
|
.group(
|
||||||
|
(q) => q.usedIsNull().or().usedEqualTo(false),
|
||||||
|
)
|
||||||
|
.findAll();
|
||||||
|
|
||||||
|
final height = await chainHeight;
|
||||||
|
otherAvailableUtxos.removeWhere(
|
||||||
|
(e) => !e.isConfirmed(
|
||||||
|
height,
|
||||||
|
cryptoCurrency.minConfirms,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
TxData txData = TxData(
|
||||||
|
recipients: recipients,
|
||||||
|
feeRateType: FeeRateType.custom,
|
||||||
|
satsPerVByte: newRate,
|
||||||
|
utxos: utxos,
|
||||||
|
ignoreCachedBalanceChecks: true,
|
||||||
|
note: note?.value ?? "",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (otherAvailableUtxos.isEmpty && noChange && recipients.length == 1) {
|
||||||
|
// safe to assume send all?
|
||||||
|
txData = txData.copyWith(
|
||||||
|
recipients: [
|
||||||
|
(
|
||||||
|
address: recipients.first.address,
|
||||||
|
amount: Amount(
|
||||||
|
rawValue: inSum,
|
||||||
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
),
|
||||||
|
isChange: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
Logging.instance.log(
|
||||||
|
"RBF on assumed send all",
|
||||||
|
level: LogLevel.Debug,
|
||||||
|
);
|
||||||
|
return await prepareSend(txData: txData);
|
||||||
|
} else if (txData.recipients!.where((e) => e.isChange).length == 1) {
|
||||||
|
final newFee = BigInt.from(oldTransaction.vSize! * newRate);
|
||||||
|
final feeDifferenceRequired = newFee - oldFee;
|
||||||
|
if (feeDifferenceRequired < BigInt.zero) {
|
||||||
|
throw Exception("Negative new fee in RBF found");
|
||||||
|
} else if (feeDifferenceRequired == BigInt.zero) {
|
||||||
|
throw Exception("New fee in RBF has not changed at all");
|
||||||
|
}
|
||||||
|
|
||||||
|
final indexOfChangeOutput =
|
||||||
|
txData.recipients!.indexWhere((e) => e.isChange);
|
||||||
|
|
||||||
|
final removed = txData.recipients!.removeAt(indexOfChangeOutput);
|
||||||
|
|
||||||
|
BigInt newChangeAmount = removed.amount.raw - feeDifferenceRequired;
|
||||||
|
|
||||||
|
if (newChangeAmount >= BigInt.zero) {
|
||||||
|
if (newChangeAmount >= cryptoCurrency.dustLimit.raw) {
|
||||||
|
// yay we have enough
|
||||||
|
// update recipients
|
||||||
|
txData.recipients!.insert(
|
||||||
|
indexOfChangeOutput,
|
||||||
|
(
|
||||||
|
address: removed.address,
|
||||||
|
amount: Amount(
|
||||||
|
rawValue: newChangeAmount,
|
||||||
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
),
|
||||||
|
isChange: removed.isChange,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Logging.instance.log(
|
||||||
|
"RBF with same utxo set with increased fee and reduced change",
|
||||||
|
level: LogLevel.Debug,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// new change amount is less than dust limit.
|
||||||
|
// TODO: check if worth adding another utxo?
|
||||||
|
// depending on several factors, it may be cheaper to just add]
|
||||||
|
// the dust to the fee...
|
||||||
|
// we'll do that for now... aka remove the change output entirely
|
||||||
|
// which now that I think about it, will reduce the size of the tx...
|
||||||
|
// oh well...
|
||||||
|
|
||||||
|
// do nothing here as we already removed the change output above
|
||||||
|
Logging.instance.log(
|
||||||
|
"RBF with same utxo set with increased fee and no change",
|
||||||
|
level: LogLevel.Debug,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return await buildTransaction(
|
||||||
|
txData: txData.copyWith(
|
||||||
|
usedUTXOs: txData.utxos!.toList(),
|
||||||
|
fee: Amount(
|
||||||
|
rawValue: newFee,
|
||||||
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
utxoSigningData: await fetchBuildTxData(txData.utxos!.toList()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// if change amount is negative
|
||||||
|
} else {
|
||||||
|
// we need more utxos
|
||||||
|
if (otherAvailableUtxos.isEmpty) {
|
||||||
|
throw Exception("Insufficient funds to pay for increased fee");
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<UTXO> extraUtxos = [];
|
||||||
|
for (int i = 0; i < otherAvailableUtxos.length; i++) {
|
||||||
|
final utxoToAdd = otherAvailableUtxos[i];
|
||||||
|
newChangeAmount += BigInt.from(utxoToAdd.value);
|
||||||
|
extraUtxos.add(utxoToAdd);
|
||||||
|
|
||||||
|
if (newChangeAmount >= cryptoCurrency.dustLimit.raw) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newChangeAmount < cryptoCurrency.dustLimit.raw) {
|
||||||
|
throw Exception("Insufficient funds to pay for increased fee");
|
||||||
|
}
|
||||||
|
txData.recipients!.insert(
|
||||||
|
indexOfChangeOutput,
|
||||||
|
(
|
||||||
|
address: removed.address,
|
||||||
|
amount: Amount(
|
||||||
|
rawValue: newChangeAmount,
|
||||||
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
),
|
||||||
|
isChange: removed.isChange,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final newUtxoSet = {
|
||||||
|
...txData.utxos!,
|
||||||
|
...extraUtxos,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: remove assert
|
||||||
|
assert(newUtxoSet.length == txData.utxos!.length + extraUtxos.length);
|
||||||
|
|
||||||
|
Logging.instance.log(
|
||||||
|
"RBF with ${extraUtxos.length} extra utxo(s)"
|
||||||
|
" added to pay for the new fee",
|
||||||
|
level: LogLevel.Debug,
|
||||||
|
);
|
||||||
|
|
||||||
|
return await buildTransaction(
|
||||||
|
txData: txData.copyWith(
|
||||||
|
utxos: newUtxoSet,
|
||||||
|
usedUTXOs: newUtxoSet.toList(),
|
||||||
|
fee: Amount(
|
||||||
|
rawValue: newFee,
|
||||||
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
utxoSigningData: await fetchBuildTxData(newUtxoSet.toList()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO handle building a tx here in this case
|
||||||
|
throw Exception(
|
||||||
|
"Unexpected number of change outputs found:"
|
||||||
|
" ${txData.recipients!.where((e) => e.isChange).length}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,6 @@ import '../../../utilities/amount/amount.dart';
|
||||||
import '../../../utilities/enums/derive_path_type_enum.dart';
|
import '../../../utilities/enums/derive_path_type_enum.dart';
|
||||||
import '../../../utilities/extensions/extensions.dart';
|
import '../../../utilities/extensions/extensions.dart';
|
||||||
import '../../../utilities/logger.dart';
|
import '../../../utilities/logger.dart';
|
||||||
import '../../crypto_currency/crypto_currency.dart';
|
|
||||||
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||||
import '../../isar/models/spark_coin.dart';
|
import '../../isar/models/spark_coin.dart';
|
||||||
import '../../isar/models/wallet_info.dart';
|
import '../../isar/models/wallet_info.dart';
|
||||||
|
@ -86,7 +85,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
if (_sparkChangeAddressCached == null) {
|
if (_sparkChangeAddressCached == null) {
|
||||||
final root = await getRootHDNode();
|
final root = await getRootHDNode();
|
||||||
final String derivationPath;
|
final String derivationPath;
|
||||||
if (cryptoCurrency.network == CryptoCurrencyNetwork.test) {
|
if (cryptoCurrency.network.isTestNet) {
|
||||||
derivationPath =
|
derivationPath =
|
||||||
"$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
|
"$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
|
||||||
} else {
|
} else {
|
||||||
|
@ -98,7 +97,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
privateKey: keys.privateKey.data,
|
privateKey: keys.privateKey.data,
|
||||||
index: kDefaultSparkIndex,
|
index: kDefaultSparkIndex,
|
||||||
diversifier: kSparkChange,
|
diversifier: kSparkChange,
|
||||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestNet: cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -158,7 +157,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
|
|
||||||
final root = await getRootHDNode();
|
final root = await getRootHDNode();
|
||||||
final String derivationPath;
|
final String derivationPath;
|
||||||
if (cryptoCurrency.network == CryptoCurrencyNetwork.test) {
|
if (cryptoCurrency.network.isTestNet) {
|
||||||
derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
|
derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
|
||||||
} else {
|
} else {
|
||||||
derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
|
derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
|
||||||
|
@ -169,7 +168,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
privateKey: keys.privateKey.data,
|
privateKey: keys.privateKey.data,
|
||||||
index: kDefaultSparkIndex,
|
index: kDefaultSparkIndex,
|
||||||
diversifier: diversifier,
|
diversifier: diversifier,
|
||||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestNet: cryptoCurrency.network.isTestNet,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Address(
|
return Address(
|
||||||
|
@ -335,7 +334,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
|
|
||||||
final root = await getRootHDNode();
|
final root = await getRootHDNode();
|
||||||
final String derivationPath;
|
final String derivationPath;
|
||||||
if (cryptoCurrency.network == CryptoCurrencyNetwork.test) {
|
if (cryptoCurrency.network.isTestNet) {
|
||||||
derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
|
derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
|
||||||
} else {
|
} else {
|
||||||
derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
|
derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
|
||||||
|
@ -704,7 +703,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
privateKeyHexSet: privateKeyHexSet,
|
privateKeyHexSet: privateKeyHexSet,
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestNet: cryptoCurrency.network.isTestNet,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -836,7 +835,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
privateKeyHexSet: privateKeyHexSet,
|
privateKeyHexSet: privateKeyHexSet,
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
isTestNet: cryptoCurrency.network.isTestNet,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
newlyIdCoins.addAll(myCoins);
|
newlyIdCoins.addAll(myCoins);
|
||||||
|
|
145
lib/widgets/boost_fee_slider.dart
Normal file
145
lib/widgets/boost_fee_slider.dart
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../utilities/amount/amount.dart';
|
||||||
|
import '../utilities/amount/amount_formatter.dart';
|
||||||
|
import '../wallets/crypto_currency/crypto_currency.dart'; // Update with your actual path
|
||||||
|
|
||||||
|
class BoostFeeSlider extends ConsumerStatefulWidget {
|
||||||
|
final CryptoCurrency coin;
|
||||||
|
final void Function(BigInt) onFeeChanged;
|
||||||
|
final BigInt min;
|
||||||
|
final BigInt max;
|
||||||
|
|
||||||
|
const BoostFeeSlider({
|
||||||
|
super.key,
|
||||||
|
required this.coin,
|
||||||
|
required this.onFeeChanged,
|
||||||
|
required this.min,
|
||||||
|
required this.max,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<BoostFeeSlider> createState() => _BoostFeeSliderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoostFeeSliderState extends ConsumerState<BoostFeeSlider> {
|
||||||
|
double _currentSliderValue = 0;
|
||||||
|
late TextEditingController _textEditingController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_currentSliderValue = widget.min.toDouble();
|
||||||
|
_textEditingController = TextEditingController(
|
||||||
|
text: ref.read(pAmountFormatter(widget.coin)).format(
|
||||||
|
Amount(
|
||||||
|
rawValue: BigInt.from(_currentSliderValue),
|
||||||
|
fractionDigits: widget.coin.fractionDigits,
|
||||||
|
),
|
||||||
|
withUnitName: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_textEditingController.addListener(() {
|
||||||
|
// TODO: value.replaceAll(',', '') doesn't work for certain locales
|
||||||
|
final double? value =
|
||||||
|
double.tryParse(_textEditingController.text.replaceAll(',', ''));
|
||||||
|
if (value != null) {
|
||||||
|
final BigInt bigIntValue = BigInt.from(
|
||||||
|
value * BigInt.from(10).pow(widget.coin.fractionDigits).toInt(),
|
||||||
|
);
|
||||||
|
if (bigIntValue >= widget.min && bigIntValue <= widget.max) {
|
||||||
|
setState(() {
|
||||||
|
_currentSliderValue = value;
|
||||||
|
widget.onFeeChanged(bigIntValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textEditingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: _currentSliderValue,
|
||||||
|
min: widget.min.toDouble(),
|
||||||
|
max: widget.max.toDouble(),
|
||||||
|
divisions: (widget.max - widget.min).toInt(),
|
||||||
|
label: ref.read(pAmountFormatter(widget.coin)).format(
|
||||||
|
Amount(
|
||||||
|
rawValue: BigInt.from(_currentSliderValue),
|
||||||
|
fractionDigits: widget.coin.fractionDigits,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_currentSliderValue = value;
|
||||||
|
_textEditingController.text =
|
||||||
|
ref.read(pAmountFormatter(widget.coin)).format(
|
||||||
|
Amount(
|
||||||
|
rawValue: BigInt.from(_currentSliderValue),
|
||||||
|
fractionDigits: widget.coin.fractionDigits,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
widget.onFeeChanged(BigInt.from(_currentSliderValue));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 16 + // Left and right padding.
|
||||||
|
122 / 8 * widget.coin.fractionDigits + // Variable width.
|
||||||
|
8 * widget.coin.ticker.length, // End padding for ticker.
|
||||||
|
child: TextField(
|
||||||
|
controller: _textEditingController,
|
||||||
|
keyboardType:
|
||||||
|
const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*')),
|
||||||
|
],
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
// TODO: value.replaceAll(',', '') doesn't work for certain locales
|
||||||
|
final double? newValue =
|
||||||
|
double.tryParse(value.replaceAll(',', ''));
|
||||||
|
if (newValue != null) {
|
||||||
|
final BigInt bigIntValue = BigInt.from(
|
||||||
|
newValue *
|
||||||
|
BigInt.from(10)
|
||||||
|
.pow(widget.coin.fractionDigits)
|
||||||
|
.toInt(),
|
||||||
|
);
|
||||||
|
if (bigIntValue >= widget.min &&
|
||||||
|
bigIntValue <= widget.max) {
|
||||||
|
setState(() {
|
||||||
|
_currentSliderValue = newValue;
|
||||||
|
widget.onFeeChanged(bigIntValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +1,45 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../utilities/text_styles.dart';
|
import '../utilities/text_styles.dart';
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
|
||||||
|
/// This has limitations. At least one of [pow] or [min] must be set to 1
|
||||||
class FeeSlider extends StatefulWidget {
|
class FeeSlider extends StatefulWidget {
|
||||||
const FeeSlider({
|
const FeeSlider({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onSatVByteChanged,
|
required this.onSatVByteChanged,
|
||||||
required this.coin,
|
required this.coin,
|
||||||
|
this.min = 1,
|
||||||
|
this.max = 5,
|
||||||
|
this.pow = 4,
|
||||||
this.showWU = false,
|
this.showWU = false,
|
||||||
|
this.overrideLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final CryptoCurrency coin;
|
final CryptoCurrency coin;
|
||||||
|
final double min;
|
||||||
|
final double max;
|
||||||
|
final double pow;
|
||||||
final bool showWU;
|
final bool showWU;
|
||||||
final void Function(int) onSatVByteChanged;
|
final void Function(int) onSatVByteChanged;
|
||||||
|
final String? overrideLabel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FeeSlider> createState() => _FeeSliderState();
|
State<FeeSlider> createState() => _FeeSliderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FeeSliderState extends State<FeeSlider> {
|
class _FeeSliderState extends State<FeeSlider> {
|
||||||
static const double min = 1;
|
|
||||||
static const double max = 4;
|
|
||||||
|
|
||||||
double sliderValue = 0;
|
double sliderValue = 0;
|
||||||
|
|
||||||
int rate = min.toInt();
|
late int rate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
rate = widget.min.toInt();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -36,7 +49,7 @@ class _FeeSliderState extends State<FeeSlider> {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.showWU ? "sat/WU" : "sat/vByte",
|
widget.overrideLabel ?? (widget.showWU ? "sat/WU" : "sat/vByte"),
|
||||||
style: STextStyles.smallMed12(context),
|
style: STextStyles.smallMed12(context),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
|
@ -50,7 +63,10 @@ class _FeeSliderState extends State<FeeSlider> {
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
sliderValue = value;
|
sliderValue = value;
|
||||||
final number = pow(sliderValue * (max - min) + min, 4).toDouble();
|
final number = pow(
|
||||||
|
sliderValue * (widget.max - widget.min) + widget.min,
|
||||||
|
widget.pow,
|
||||||
|
).toDouble();
|
||||||
if (widget.coin is Dogecoin) {
|
if (widget.coin is Dogecoin) {
|
||||||
rate = (number * 1000).toInt();
|
rate = (number * 1000).toInt();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -13,26 +13,19 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:solana/solana.dart';
|
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../models/node_model.dart';
|
|
||||||
import '../notifications/show_flush_bar.dart';
|
|
||||||
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||||
import '../pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart';
|
import '../pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart';
|
||||||
import '../providers/global/active_wallet_provider.dart';
|
import '../providers/global/active_wallet_provider.dart';
|
||||||
|
import '../providers/global/secure_store_provider.dart';
|
||||||
import '../providers/providers.dart';
|
import '../providers/providers.dart';
|
||||||
import '../services/tor_service.dart';
|
|
||||||
import '../themes/stack_colors.dart';
|
import '../themes/stack_colors.dart';
|
||||||
import '../utilities/assets.dart';
|
import '../utilities/assets.dart';
|
||||||
import '../utilities/connection_check/electrum_connection_check.dart';
|
|
||||||
import '../utilities/constants.dart';
|
import '../utilities/constants.dart';
|
||||||
import '../utilities/default_nodes.dart';
|
import '../utilities/default_nodes.dart';
|
||||||
import '../utilities/enums/sync_type_enum.dart';
|
import '../utilities/enums/sync_type_enum.dart';
|
||||||
import '../utilities/logger.dart';
|
import '../utilities/test_node_connection.dart';
|
||||||
import '../utilities/test_epic_box_connection.dart';
|
|
||||||
import '../utilities/test_eth_node_connection.dart';
|
|
||||||
import '../utilities/test_monero_node_connection.dart';
|
|
||||||
import '../utilities/text_styles.dart';
|
import '../utilities/text_styles.dart';
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
import 'rounded_white_container.dart';
|
import 'rounded_white_container.dart';
|
||||||
|
@ -82,150 +75,6 @@ class NodeOptionsSheet extends ConsumerWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _testConnection(
|
|
||||||
NodeModel node,
|
|
||||||
BuildContext context,
|
|
||||||
WidgetRef ref,
|
|
||||||
) async {
|
|
||||||
bool testPassed = false;
|
|
||||||
|
|
||||||
switch (coin.runtimeType) {
|
|
||||||
case const (Epiccash):
|
|
||||||
try {
|
|
||||||
testPassed = await testEpicNodeConnection(
|
|
||||||
NodeFormData()
|
|
||||||
..host = node.host
|
|
||||||
..useSSL = node.useSSL
|
|
||||||
..port = node.port,
|
|
||||||
) !=
|
|
||||||
null;
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case const (Monero):
|
|
||||||
case const (Wownero):
|
|
||||||
try {
|
|
||||||
final uri = Uri.parse(node.host);
|
|
||||||
if (uri.scheme.startsWith("http")) {
|
|
||||||
final String path = uri.path.isEmpty ? "/json_rpc" : uri.path;
|
|
||||||
|
|
||||||
final String uriString =
|
|
||||||
"${uri.scheme}://${uri.host}:${node.port}$path";
|
|
||||||
|
|
||||||
final response = await testMoneroNodeConnection(
|
|
||||||
Uri.parse(uriString),
|
|
||||||
false,
|
|
||||||
proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
|
|
||||||
? ref.read(pTorService).getProxyInfo()
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.cert != null && context.mounted) {
|
|
||||||
// if (mounted) {
|
|
||||||
final shouldAllowBadCert = await showBadX509CertificateDialog(
|
|
||||||
response.cert!,
|
|
||||||
response.url!,
|
|
||||||
response.port!,
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldAllowBadCert) {
|
|
||||||
final response = await testMoneroNodeConnection(
|
|
||||||
Uri.parse(uriString),
|
|
||||||
true,
|
|
||||||
proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
|
|
||||||
? ref.read(pTorService).getProxyInfo()
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
testPassed = response.success;
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
} else {
|
|
||||||
testPassed = response.success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case const (Bitcoin):
|
|
||||||
case const (Litecoin):
|
|
||||||
case const (Dogecoin):
|
|
||||||
case const (Firo):
|
|
||||||
case const (Particl):
|
|
||||||
case const (Bitcoincash):
|
|
||||||
case const (Namecoin):
|
|
||||||
case const (Ecash):
|
|
||||||
case const (BitcoinFrost):
|
|
||||||
case const (Peercoin):
|
|
||||||
try {
|
|
||||||
testPassed = await checkElectrumServer(
|
|
||||||
host: node.host,
|
|
||||||
port: node.port,
|
|
||||||
useSSL: node.useSSL,
|
|
||||||
overridePrefs: ref.read(prefsChangeNotifierProvider),
|
|
||||||
overrideTorService: ref.read(pTorService),
|
|
||||||
);
|
|
||||||
} catch (_) {
|
|
||||||
testPassed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case const (Ethereum):
|
|
||||||
try {
|
|
||||||
testPassed = await testEthNodeConnection(node.host);
|
|
||||||
} catch (_) {
|
|
||||||
testPassed = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case const (Nano):
|
|
||||||
case const (Banano):
|
|
||||||
case const (Tezos):
|
|
||||||
case const (Stellar):
|
|
||||||
throw UnimplementedError();
|
|
||||||
//TODO: check network/node
|
|
||||||
|
|
||||||
case const (Solana):
|
|
||||||
try {
|
|
||||||
RpcClient rpcClient;
|
|
||||||
if (node.host.startsWith("http") || node.host.startsWith("https")) {
|
|
||||||
rpcClient = RpcClient("${node.host}:${node.port}");
|
|
||||||
} else {
|
|
||||||
rpcClient = RpcClient("http://${node.host}:${node.port}");
|
|
||||||
}
|
|
||||||
await rpcClient.getEpochInfo().then((value) => testPassed = true);
|
|
||||||
} catch (_) {
|
|
||||||
testPassed = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testPassed) {
|
|
||||||
// showFloatingFlushBar(
|
|
||||||
// type: FlushBarType.success,
|
|
||||||
// message: "Server ping success",
|
|
||||||
// context: context,
|
|
||||||
// );
|
|
||||||
} else {
|
|
||||||
unawaited(
|
|
||||||
showFloatingFlushBar(
|
|
||||||
type: FlushBarType.warning,
|
|
||||||
iconAsset: Assets.svg.circleAlert,
|
|
||||||
message: "Could not connect to node",
|
|
||||||
context: context,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return testPassed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final maxHeight = MediaQuery.of(context).size.height * 0.60;
|
final maxHeight = MediaQuery.of(context).size.height * 0.60;
|
||||||
|
@ -403,21 +252,38 @@ class NodeOptionsSheet extends ConsumerWidget {
|
||||||
onPressed: status == "Connected"
|
onPressed: status == "Connected"
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
final canConnect =
|
final pw = await node.getPassword(
|
||||||
await _testConnection(node, context, ref);
|
ref.read(secureStoreProvider),
|
||||||
if (!canConnect) {
|
);
|
||||||
return;
|
if (context.mounted) {
|
||||||
|
final canConnect = await testNodeConnection(
|
||||||
|
context: context,
|
||||||
|
nodeFormData: NodeFormData()
|
||||||
|
..name = node.name
|
||||||
|
..host = node.host
|
||||||
|
..login = node.loginName
|
||||||
|
..password = pw
|
||||||
|
..port = node.port
|
||||||
|
..useSSL = node.useSSL
|
||||||
|
..isFailover = node.isFailover
|
||||||
|
..trusted = node.trusted,
|
||||||
|
cryptoCurrency: coin,
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
if (!canConnect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(nodeServiceChangeNotifierProvider)
|
||||||
|
.setPrimaryNodeFor(
|
||||||
|
coin: coin,
|
||||||
|
node: node,
|
||||||
|
shouldNotifyListeners: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _notifyWalletsOfUpdatedNode(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ref
|
|
||||||
.read(nodeServiceChangeNotifierProvider)
|
|
||||||
.setPrimaryNodeFor(
|
|
||||||
coin: coin,
|
|
||||||
node: node,
|
|
||||||
shouldNotifyListeners: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
await _notifyWalletsOfUpdatedNode(ref);
|
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
// status == "Connected" ? "Disconnect" : "Connect",
|
// status == "Connected" ? "Disconnect" : "Connect",
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../themes/stack_colors.dart';
|
import '../themes/stack_colors.dart';
|
||||||
import '../utilities/text_styles.dart';
|
import '../utilities/text_styles.dart';
|
||||||
import '../utilities/util.dart';
|
import '../utilities/util.dart';
|
||||||
|
@ -148,6 +149,7 @@ class StackOkDialog extends StatelessWidget {
|
||||||
required this.title,
|
required this.title,
|
||||||
this.message,
|
this.message,
|
||||||
this.desktopPopRootNavigator = false,
|
this.desktopPopRootNavigator = false,
|
||||||
|
this.maxWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool desktopPopRootNavigator;
|
final bool desktopPopRootNavigator;
|
||||||
|
@ -158,6 +160,7 @@ class StackOkDialog extends StatelessWidget {
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String? message;
|
final String? message;
|
||||||
|
final double? maxWidth;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -165,17 +168,20 @@ class StackOkDialog extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
ConstrainedBox(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
children: [
|
child: Row(
|
||||||
Flexible(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
child: Text(
|
children: [
|
||||||
title,
|
Flexible(
|
||||||
style: STextStyles.pageTitleH2(context),
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: STextStyles.pageTitleH2(context),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
icon != null ? icon! : Container(),
|
||||||
icon != null ? icon! : Container(),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
if (message != null)
|
if (message != null)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -194,40 +200,44 @@ class StackOkDialog extends StatelessWidget {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
Row(
|
ConstrainedBox(
|
||||||
children: [
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
leftButton == null
|
child: Row(
|
||||||
? const Spacer()
|
children: [
|
||||||
: Expanded(child: leftButton!),
|
leftButton == null
|
||||||
const SizedBox(
|
? const Spacer()
|
||||||
width: 8,
|
: Expanded(child: leftButton!),
|
||||||
),
|
const SizedBox(
|
||||||
Expanded(
|
width: 8,
|
||||||
child: TextButton(
|
),
|
||||||
onPressed: !Util.isDesktop
|
Expanded(
|
||||||
? () {
|
child: TextButton(
|
||||||
Navigator.of(context).pop();
|
onPressed: !Util.isDesktop
|
||||||
onOkPressed?.call("OK");
|
? () {
|
||||||
}
|
Navigator.of(context).pop();
|
||||||
: () {
|
onOkPressed?.call("OK");
|
||||||
if (desktopPopRootNavigator) {
|
|
||||||
Navigator.of(context, rootNavigator: true).pop();
|
|
||||||
} else {
|
|
||||||
int count = 0;
|
|
||||||
Navigator.of(context).popUntil((_) => count++ >= 2);
|
|
||||||
// onOkPressed?.call("OK");
|
|
||||||
}
|
}
|
||||||
},
|
: () {
|
||||||
style: Theme.of(context)
|
if (desktopPopRootNavigator) {
|
||||||
.extension<StackColors>()!
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
.getPrimaryEnabledButtonStyle(context),
|
} else {
|
||||||
child: Text(
|
int count = 0;
|
||||||
"Ok",
|
Navigator.of(context)
|
||||||
style: STextStyles.button(context),
|
.popUntil((_) => count++ >= 2);
|
||||||
|
// onOkPressed?.call("OK");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.getPrimaryEnabledButtonStyle(context),
|
||||||
|
child: Text(
|
||||||
|
"Ok",
|
||||||
|
style: STextStyles.button(context),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -44,6 +44,7 @@ const _prefix = "Campfire";
|
||||||
const _separator = "";
|
const _separator = "";
|
||||||
const _suffix = "";
|
const _suffix = "";
|
||||||
const _appDataDirName = "campfire";
|
const _appDataDirName = "campfire";
|
||||||
|
const _shortDescriptionText = "Your privacy. Your wallet. Your Firo.";
|
||||||
const _commitHash = "$BUILT_COMMIT_HASH";
|
const _commitHash = "$BUILT_COMMIT_HASH";
|
||||||
|
|
||||||
const Set<AppFeature> _features = {};
|
const Set<AppFeature> _features = {};
|
||||||
|
|
|
@ -38,6 +38,7 @@ const _prefix = "Stack";
|
||||||
const _separator = " ";
|
const _separator = " ";
|
||||||
const _suffix = "Duo";
|
const _suffix = "Duo";
|
||||||
const _appDataDirName = "stackduo";
|
const _appDataDirName = "stackduo";
|
||||||
|
const _shortDescriptionText = "An open-source, multicoin wallet for everyone";
|
||||||
const _commitHash = "$BUILT_COMMIT_HASH";
|
const _commitHash = "$BUILT_COMMIT_HASH";
|
||||||
|
|
||||||
const Set<AppFeature> _features = {
|
const Set<AppFeature> _features = {
|
||||||
|
@ -56,7 +57,9 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
|
||||||
Monero(CryptoCurrencyNetwork.main),
|
Monero(CryptoCurrencyNetwork.main),
|
||||||
BitcoinFrost(CryptoCurrencyNetwork.main),
|
BitcoinFrost(CryptoCurrencyNetwork.main),
|
||||||
Bitcoin(CryptoCurrencyNetwork.test),
|
Bitcoin(CryptoCurrencyNetwork.test),
|
||||||
|
Bitcoin(CryptoCurrencyNetwork.test4),
|
||||||
BitcoinFrost(CryptoCurrencyNetwork.test),
|
BitcoinFrost(CryptoCurrencyNetwork.test),
|
||||||
|
BitcoinFrost(CryptoCurrencyNetwork.test4),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
EOF
|
EOF
|
|
@ -38,6 +38,7 @@ const _prefix = "Stack";
|
||||||
const _separator = " ";
|
const _separator = " ";
|
||||||
const _suffix = "Wallet";
|
const _suffix = "Wallet";
|
||||||
const _appDataDirName = "stackwallet";
|
const _appDataDirName = "stackwallet";
|
||||||
|
const _shortDescriptionText = "An open-source, multicoin wallet for everyone";
|
||||||
const _commitHash = "$BUILT_COMMIT_HASH";
|
const _commitHash = "$BUILT_COMMIT_HASH";
|
||||||
|
|
||||||
const Set<AppFeature> _features = {
|
const Set<AppFeature> _features = {
|
||||||
|
@ -54,6 +55,7 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
|
||||||
Banano(CryptoCurrencyNetwork.main),
|
Banano(CryptoCurrencyNetwork.main),
|
||||||
Bitcoincash(CryptoCurrencyNetwork.main),
|
Bitcoincash(CryptoCurrencyNetwork.main),
|
||||||
BitcoinFrost(CryptoCurrencyNetwork.main),
|
BitcoinFrost(CryptoCurrencyNetwork.main),
|
||||||
|
Dash(CryptoCurrencyNetwork.main),
|
||||||
Dogecoin(CryptoCurrencyNetwork.main),
|
Dogecoin(CryptoCurrencyNetwork.main),
|
||||||
Ecash(CryptoCurrencyNetwork.main),
|
Ecash(CryptoCurrencyNetwork.main),
|
||||||
Epiccash(CryptoCurrencyNetwork.main),
|
Epiccash(CryptoCurrencyNetwork.main),
|
||||||
|
@ -69,13 +71,15 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
|
||||||
Tezos(CryptoCurrencyNetwork.main),
|
Tezos(CryptoCurrencyNetwork.main),
|
||||||
Wownero(CryptoCurrencyNetwork.main),
|
Wownero(CryptoCurrencyNetwork.main),
|
||||||
Bitcoin(CryptoCurrencyNetwork.test),
|
Bitcoin(CryptoCurrencyNetwork.test),
|
||||||
BitcoinFrost(CryptoCurrencyNetwork.test),
|
Bitcoin(CryptoCurrencyNetwork.test4),
|
||||||
Litecoin(CryptoCurrencyNetwork.test),
|
|
||||||
Bitcoincash(CryptoCurrencyNetwork.test),
|
Bitcoincash(CryptoCurrencyNetwork.test),
|
||||||
Firo(CryptoCurrencyNetwork.test),
|
BitcoinFrost(CryptoCurrencyNetwork.test),
|
||||||
|
BitcoinFrost(CryptoCurrencyNetwork.test4),
|
||||||
Dogecoin(CryptoCurrencyNetwork.test),
|
Dogecoin(CryptoCurrencyNetwork.test),
|
||||||
Stellar(CryptoCurrencyNetwork.test),
|
Firo(CryptoCurrencyNetwork.test),
|
||||||
|
Litecoin(CryptoCurrencyNetwork.test),
|
||||||
Peercoin(CryptoCurrencyNetwork.test),
|
Peercoin(CryptoCurrencyNetwork.test),
|
||||||
|
Stellar(CryptoCurrencyNetwork.test),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
EOF
|
EOF
|
Loading…
Reference in a new issue