Merge remote-tracking branch 'origin_sw/staging' into wallets_refactor

# Conflicts:
#	crypto_plugins/flutter_libepiccash
#	lib/db/isar/main_db.dart
#	pubspec.lock
#	pubspec.yaml
#	test/services/coins/firo/firo_wallet_test.mocks.dart
#	test/widget_tests/transaction_card_test.mocks.dart
This commit is contained in:
Julian 2023-10-29 12:27:02 -06:00
commit b88c073e22
115 changed files with 19088 additions and 5721 deletions

View file

@ -8,12 +8,12 @@ jobs:
- name: Prepare repository
uses: actions/checkout@v3
with:
flutter-version: '3.10.3'
flutter-version: '3.10.6'
channel: 'stable'
- name: Install Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.10.3'
flutter-version: '3.10.6'
channel: 'stable'
- name: Setup | Rust
uses: ATiltedTree/setup-rust@v1

2
.gitmodules vendored
View file

@ -6,4 +6,4 @@
url = https://github.com/cypherstack/flutter_libmonero.git
[submodule "crypto_plugins/flutter_liblelantus"]
path = crypto_plugins/flutter_liblelantus
url = https://github.com/cypherstack/flutter_liblelantus.git
url = https://github.com/cypherstack/flutter_liblelantus.git

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

3
assets/svg/fusing.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.768 12.7778C22.0427 9.72145 19.529 7.28212 16.4516 6.67196C14.9906 6.37118 13.4811 6.47323 12.0731 6.95663C12.3953 4.84042 13.7811 3.04089 15.7469 2.19569C16.3218 1.94862 16.1929 1.09999 15.575 1.04069C14.6349 0.944011 13.6895 1.01383 12.7765 1.22868C9.68399 1.95571 7.28204 4.46938 6.63321 7.54852C6.33243 9.00946 6.47423 10.5177 6.95548 11.9227C4.84012 11.6005 3.04059 10.2169 2.19755 8.24891C1.95048 7.67743 1.10176 7.80633 1.04265 8.42079C0.945952 9.3618 1.01579 10.3071 1.23066 11.2224C1.95598 14.2787 4.46965 16.7181 7.54707 17.3282C9.00801 17.629 10.5175 17.5269 11.9256 17.0435C11.6033 19.1598 10.2176 20.9593 8.25176 21.8045C7.67684 22.0515 7.80574 22.9002 8.42363 22.9595C9.36379 23.0562 10.3091 22.9863 11.2222 22.7715C14.2786 22.0462 16.7179 19.5325 17.328 16.4551C17.6288 14.9941 17.5268 13.4847 17.0434 12.0766C19.1596 12.3988 20.9591 13.7846 21.8043 15.7504C22.0514 16.3253 22.9 16.1964 22.9593 15.5785C23.0559 14.6384 22.9828 13.6931 22.768 12.7778ZM11.9613 14.0626C10.8613 14.0626 9.89884 13.1388 9.89884 12.0001C9.89884 10.8614 10.8227 9.93758 11.9613 9.93758C13.1 9.93758 14.0625 10.8614 14.0625 12.0001C14.0625 13.1388 13.1387 14.0626 11.9613 14.0626Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

3
assets/svg/peers.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.7 12.0002C11.1303 12.0002 13.1 10.0302 13.1 7.6002C13.1 5.17023 11.1303 3.2002 8.7 3.2002C6.26969 3.2002 4.3 5.17023 4.3 7.6002C4.3 10.0302 6.26969 12.0002 8.7 12.0002ZM10.4428 13.6502H6.95719C3.66647 13.6502 1 16.3177 1 19.6074C1 20.2674 1.5335 20.8002 2.19144 20.8002H15.2072C15.8672 20.8002 16.4 20.2674 16.4 19.6074C16.4 16.3177 13.7325 13.6502 10.4428 13.6502ZM17.4691 14.2002H14.9305C16.51 15.4961 17.5 17.4349 17.5 19.6074C17.5 20.0474 17.3694 20.453 17.1562 20.8002H21.9C22.5084 20.8002 23 20.3052 23 19.6693C23 16.6614 20.5387 14.2002 17.4691 14.2002ZM15.85 12.0002C17.9778 12.0002 19.7 10.278 19.7 8.1502C19.7 6.02238 17.9778 4.3002 15.85 4.3002C14.9868 4.3002 14.1986 4.59427 13.5565 5.07398C13.9525 5.83435 14.2 6.68582 14.2 7.6002C14.2 8.8212 13.7899 9.94251 13.1141 10.8559C13.8116 11.5602 14.7775 12.0002 15.85 12.0002Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 966 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -0,0 +1,3 @@
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.875 16.8755H2.08633C1.36574 16.8755 0.75 17.4899 0.75 18.2118C0.75 18.9337 1.36574 19.6255 2.08633 19.6255H15.875C16.6355 19.6255 17.25 19.011 17.25 18.2505C17.25 17.4899 16.6355 16.8755 15.875 16.8755ZM3.15625 8.61687H6.25V14.1212C6.25 14.8813 6.86574 15.4979 7.625 15.4979H10.375C11.1343 15.4979 11.75 14.8813 11.75 14.1212V8.61687H14.8438C15.2553 8.61687 15.6279 8.3716 15.7912 7.99339C15.9537 7.61514 15.8765 7.1757 15.5938 6.8762L9.75006 0.684407C9.36068 0.271864 8.63975 0.271864 8.25002 0.684407L2.40627 6.8762C2.12363 7.17587 2.04641 7.61527 2.20887 7.99339C2.37207 8.37195 2.74461 8.61687 3.15625 8.61687Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 747 B

@ -1 +1 @@
Subproject commit be2e6bca47bb5d6e0aef1bf2ace2dd4fa7bc8038
Subproject commit f677dec0b34d3f9fe8fce2bc8ff5c508c3f3bb9a

@ -1 +1 @@
Subproject commit 1f8fde935bbb23585477b0cb884bee9be6d12a87
Subproject commit 9cd241b5ea142e21c01dd7639b42603281c43287

View file

@ -13,6 +13,7 @@ import 'package:flutter_native_splash/cli_commands.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
import 'package:stackwallet/models/isar/models/block_explorer.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/isar/ordinal.dart';
@ -278,6 +279,14 @@ class MainDB {
QueryBuilder<UTXO, UTXO, QAfterWhereClause> getUTXOs(String walletId) =>
isar.utxos.where().walletIdEqualTo(walletId);
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> getUTXOsByAddress(
String walletId, String address) =>
isar.utxos
.where()
.walletIdEqualTo(walletId)
.filter()
.addressEqualTo(address);
Future<void> putUTXO(UTXO utxo) => isar.writeTxn(() async {
await isar.utxos.put(utxo);
});
@ -413,6 +422,8 @@ class MainDB {
//
Future<void> deleteWalletBlockchainData(String walletId) async {
final transactionCount = await getTransactions(walletId).count();
final transactionCountV2 =
await isar.transactionV2s.where().walletIdEqualTo(walletId).count();
final addressCount = await getAddresses(walletId).count();
final utxoCount = await getUTXOs(walletId).count();
final lelantusCoinCount =
@ -431,6 +442,18 @@ class MainDB {
await isar.transactions.deleteAll(txnIds);
}
// transactions V2
for (int i = 0; i < transactionCountV2; i += paginateLimit) {
final txnIds = await isar.transactionV2s
.where()
.walletIdEqualTo(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.transactionV2s.deleteAll(txnIds);
}
// addresses
for (int i = 0; i < addressCount; i += paginateLimit) {
final addressIds = await getAddresses(walletId)
@ -535,6 +558,35 @@ class MainDB {
}
}
Future<List<int>> updateOrPutTransactionV2s(
List<TransactionV2> transactions,
) async {
try {
List<int> ids = [];
await isar.writeTxn(() async {
for (final tx in transactions) {
final storedTx = await isar.transactionV2s
.where()
.txidWalletIdEqualTo(tx.txid, tx.walletId)
.findFirst();
Id id;
if (storedTx == null) {
id = await isar.transactionV2s.put(tx);
} else {
tx.id = storedTx.id;
await isar.transactionV2s.delete(storedTx.id);
id = await isar.transactionV2s.put(tx);
}
ids.add(id);
}
});
return ids;
} catch (e) {
throw MainDBException("failed updateOrPutAddresses: $transactions", e);
}
}
// ========== Ethereum =======================================================
// eth contracts

View file

@ -26,7 +26,6 @@ class EthTokenTxDto {
required this.topics,
required this.data,
required this.articulatedLog,
required this.compressedLog,
required this.transactionHash,
required this.transactionIndex,
});
@ -44,7 +43,6 @@ class EthTokenTxDto {
map['articulatedLog'] as Map,
),
),
compressedLog = map['compressedLog'] as String,
transactionHash = map['transactionHash'] as String,
transactionIndex = map['transactionIndex'] as int;
@ -54,7 +52,6 @@ class EthTokenTxDto {
final List<String> topics;
final String data;
final ArticulatedLog? articulatedLog;
final String compressedLog;
final String transactionHash;
final int transactionIndex;
@ -76,7 +73,6 @@ class EthTokenTxDto {
topics: topics ?? this.topics,
data: data ?? this.data,
articulatedLog: articulatedLog ?? this.articulatedLog,
compressedLog: compressedLog ?? this.compressedLog,
transactionHash: transactionHash ?? this.transactionHash,
transactionIndex: transactionIndex ?? this.transactionIndex,
);
@ -89,7 +85,6 @@ class EthTokenTxDto {
map['topics'] = topics;
map['data'] = data;
map['articulatedLog'] = articulatedLog?.toMap();
map['compressedLog'] = compressedLog;
map['transactionHash'] = transactionHash;
map['transactionIndex'] = transactionIndex;
return map;

View file

@ -29,7 +29,6 @@ class EthTxDTO {
required this.maxPriorityFeePerGas,
required this.isError,
required this.hasToken,
required this.compressedTx,
required this.gasCost,
required this.gasUsed,
});
@ -42,16 +41,15 @@ class EthTxDTO {
timestamp: map['timestamp'] as int,
from: map['from'] as String,
to: map['to'] as String,
value: _amountFromJsonNum(map['value']),
gas: _amountFromJsonNum(map['gas']),
gasPrice: _amountFromJsonNum(map['gasPrice']),
value: _amountFromJsonNum(map['value'])!,
gas: _amountFromJsonNum(map['gas'])!,
gasPrice: _amountFromJsonNum(map['gasPrice'])!,
maxFeePerGas: _amountFromJsonNum(map['maxFeePerGas']),
maxPriorityFeePerGas: _amountFromJsonNum(map['maxPriorityFeePerGas']),
isError: map['isError'] as int,
hasToken: map['hasToken'] as int,
compressedTx: map['compressedTx'] as String,
gasCost: _amountFromJsonNum(map['gasCost']),
gasUsed: _amountFromJsonNum(map['gasUsed']),
isError: map['isError'] as bool? ?? false,
hasToken: map['hasToken'] as bool? ?? false,
gasCost: _amountFromJsonNum(map['gasCost'])!,
gasUsed: _amountFromJsonNum(map['gasUsed'])!,
);
final String hash;
@ -64,17 +62,19 @@ class EthTxDTO {
final Amount value;
final Amount gas;
final Amount gasPrice;
final Amount maxFeePerGas;
final Amount maxPriorityFeePerGas;
final int isError;
final int hasToken;
final String compressedTx;
final Amount? maxFeePerGas;
final Amount? maxPriorityFeePerGas;
final bool isError;
final bool hasToken;
final Amount gasCost;
final Amount gasUsed;
static Amount _amountFromJsonNum(dynamic json) {
static Amount? _amountFromJsonNum(dynamic json) {
if (json == null) {
return null;
}
return Amount(
rawValue: BigInt.from(json as num),
rawValue: BigInt.parse(json.toString()),
fractionDigits: Coin.ethereum.decimals,
);
}
@ -92,8 +92,8 @@ class EthTxDTO {
Amount? gasPrice,
Amount? maxFeePerGas,
Amount? maxPriorityFeePerGas,
int? isError,
int? hasToken,
bool? isError,
bool? hasToken,
String? compressedTx,
Amount? gasCost,
Amount? gasUsed,
@ -113,7 +113,6 @@ class EthTxDTO {
maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas,
isError: isError ?? this.isError,
hasToken: hasToken ?? this.hasToken,
compressedTx: compressedTx ?? this.compressedTx,
gasCost: gasCost ?? this.gasCost,
gasUsed: gasUsed ?? this.gasUsed,
);
@ -134,7 +133,6 @@ class EthTxDTO {
map['maxPriorityFeePerGas'] = maxPriorityFeePerGas.toString();
map['isError'] = isError;
map['hasToken'] = hasToken;
map['compressedTx'] = compressedTx;
map['gasCost'] = gasCost.toString();
map['gasUsed'] = gasUsed.toString();
return map;

View file

@ -141,10 +141,10 @@ class CachedElectrumX {
await box.put(txHash, result);
}
Logging.instance.log("using fetched result", level: LogLevel.Info);
// Logging.instance.log("using fetched result", level: LogLevel.Info);
return result;
} else {
Logging.instance.log("using cached result", level: LogLevel.Info);
// Logging.instance.log("using cached result", level: LogLevel.Info);
return Map<String, dynamic>.from(cachedTx);
}
} catch (e, s) {

View file

@ -387,7 +387,14 @@ class ElectrumX {
throw jsonRpcResponse.exception!;
}
final response = jsonRpcResponse.data as List;
final List<dynamic> response;
try {
response = jsonRpcResponse.data as List;
} catch (_) {
throw Exception(
"Expected json list but got a map: ${jsonRpcResponse.data}",
);
}
// check for errors, format and throw if there are any
final List<String> errors = [];
@ -726,6 +733,14 @@ class ElectrumX {
return {"rawtx": response["result"] as String};
}
if (response["result"] == null) {
Logging.instance.log(
"getTransaction($txHash) returned null response",
level: LogLevel.Error,
);
throw 'getTransaction($txHash) returned null response';
}
return Map<String, dynamic>.from(response["result"] as Map);
} catch (e) {
Logging.instance.log(

View file

@ -0,0 +1,187 @@
import 'package:flutter/cupertino.dart';
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart';
class FusionProgressUIState extends ChangeNotifier {
/// Whether we are able to connect to the server.
bool _ableToConnect = false;
// _ableToConnect setter.
set ableToConnect(bool ableToConnect) {
_ableToConnect = ableToConnect;
notifyListeners();
}
bool get done {
if (!_ableToConnect) {
return false;
}
bool _done = (_connecting.status == CashFusionStatus.success) ||
(_connecting.status == CashFusionStatus.failed);
_done &= (_outputs.status == CashFusionStatus.success) ||
(_outputs.status == CashFusionStatus.failed);
_done &= (_peers.status == CashFusionStatus.success) ||
(_peers.status == CashFusionStatus.failed);
_done &= (_fusing.status == CashFusionStatus.success) ||
(_fusing.status == CashFusionStatus.failed);
_done &= (_complete.status == CashFusionStatus.success) ||
(_complete.status == CashFusionStatus.failed);
_done &= (fusionState.status == CashFusionStatus.success) ||
(fusionState.status == CashFusionStatus.failed);
return _done;
}
bool get succeeded {
if (!_ableToConnect) {
return false;
}
bool _succeeded = _connecting.status == CashFusionStatus.success;
_succeeded &= _outputs.status == CashFusionStatus.success;
_succeeded &= _peers.status == CashFusionStatus.success;
_succeeded &= _fusing.status == CashFusionStatus.success;
_succeeded &= _complete.status == CashFusionStatus.success;
_succeeded &= fusionState.status == CashFusionStatus.success;
return _succeeded;
}
CashFusionState _connecting =
CashFusionState(status: CashFusionStatus.waiting, info: null);
CashFusionState get connecting => _connecting;
void setConnecting(CashFusionState state, {bool shouldNotify = true}) {
_connecting = state;
_running = true;
if (shouldNotify) {
notifyListeners();
}
}
CashFusionState _outputs =
CashFusionState(status: CashFusionStatus.waiting, info: null);
CashFusionState get outputs => _outputs;
void setOutputs(CashFusionState state, {bool shouldNotify = true}) {
_outputs = state;
_running = true;
if (shouldNotify) {
notifyListeners();
}
}
CashFusionState _peers =
CashFusionState(status: CashFusionStatus.waiting, info: null);
CashFusionState get peers => _peers;
void setPeers(CashFusionState state, {bool shouldNotify = true}) {
_peers = state;
_running = true;
if (shouldNotify) {
notifyListeners();
}
}
CashFusionState _fusing =
CashFusionState(status: CashFusionStatus.waiting, info: null);
CashFusionState get fusing => _fusing;
void setFusing(CashFusionState state, {bool shouldNotify = true}) {
_fusing = state;
_running = true;
if (shouldNotify) {
notifyListeners();
}
}
CashFusionState _complete =
CashFusionState(status: CashFusionStatus.waiting, info: null);
CashFusionState get complete => _complete;
void setComplete(CashFusionState state, {bool shouldNotify = true}) {
_complete = state;
_running = true;
if (shouldNotify) {
notifyListeners();
}
}
CashFusionState _fusionStatus =
CashFusionState(status: CashFusionStatus.waiting, info: null);
CashFusionState get fusionState => _fusionStatus;
void setFusionState(CashFusionState state, {bool shouldNotify = true}) {
_fusionStatus = state;
_updateRunningState(state.status);
if (shouldNotify) {
notifyListeners();
}
}
/// An int storing the number of successfully completed fusion rounds.
int _fusionRoundsCompleted = 0;
int get fusionRoundsCompleted => _fusionRoundsCompleted;
set fusionRoundsCompleted(int fusionRoundsCompleted) {
_fusionRoundsCompleted = fusionRoundsCompleted;
notifyListeners();
}
/// A helper for incrementing the number of successfully completed fusion rounds.
void incrementFusionRoundsCompleted() {
_fusionRoundsCompleted++;
_fusionRoundsFailed = 0; // Reset failed round count on success.
_failed = false; // Reset failed flag on success.
notifyListeners();
}
/// An int storing the number of failed fusion rounds.
int _fusionRoundsFailed = 0;
int get fusionRoundsFailed => _fusionRoundsFailed;
set fusionRoundsFailed(int fusionRoundsFailed) {
_fusionRoundsFailed = fusionRoundsFailed;
notifyListeners();
}
/// A helper for incrementing the number of failed fusion rounds.
void incrementFusionRoundsFailed() {
_fusionRoundsFailed++;
notifyListeners();
}
/// A flag indicating that fusion has stopped because the maximum number of
/// consecutive failed fusion rounds has been reached.
///
/// Set from the interface. I didn't want to have to configure
///
/// Used to be named maxConsecutiveFusionRoundsFailed.
bool _failed = false;
bool get failed => _failed;
void setFailed(bool failed, {bool shouldNotify = true}) {
_failed = failed;
if (shouldNotify) {
notifyListeners();
}
}
/// A flag indicating that fusion is running.
bool _running = false;
bool get running => _running;
void setRunning(bool running, {bool shouldNotify = true}) {
_running = running;
if (shouldNotify) {
notifyListeners();
}
}
/// A helper method for setting the running flag.
///
/// Sets the running flag to true if the status is running. Sets the flag to
/// false if succeeded or failed or the global failed flag is set.
void _updateRunningState(CashFusionStatus status) {
if (status == CashFusionStatus.running) {
_running = true;
} else if (((status == CashFusionStatus.success ||
status == CashFusionStatus.failed) &&
(done || succeeded)) ||
_failed) {
_running = false;
}
}
}

View file

@ -70,6 +70,30 @@ class Address extends CryptoCurrencyAddress {
subType == AddressSubType.paynymSend ||
subType == AddressSubType.paynymReceive;
/// If called on an [Address] already stored in the DB be sure to update the
/// [transactions] Isar Links if required
Address copyWith({
String? walletId,
String? value,
List<byte>? publicKey,
int? derivationIndex,
AddressType? type,
AddressSubType? subType,
DerivationPath? derivationPath,
String? otherData,
}) {
return Address(
walletId: walletId ?? this.walletId,
value: value ?? this.value,
publicKey: publicKey ?? this.publicKey,
derivationIndex: derivationIndex ?? this.derivationIndex,
type: type ?? this.type,
subType: subType ?? this.subType,
derivationPath: derivationPath ?? this.derivationPath,
otherData: otherData ?? this.otherData,
);
}
@override
String toString() => "{ "
"id: $id, "

View file

@ -22,7 +22,6 @@ part 'transaction.g.dart';
@Collection()
class Transaction {
Transaction({
required this.walletId,
required this.txid,
@ -252,5 +251,6 @@ enum TransactionSubType {
bip47Notification, // bip47 payment code notification transaction flag
mint, // firo specific
join, // firo specific
ethToken; // eth token
ethToken, // eth token
cashFusion;
}

View file

@ -364,6 +364,7 @@ const _TransactionsubTypeEnumValueMap = {
'mint': 2,
'join': 3,
'ethToken': 4,
'cashFusion': 5,
};
const _TransactionsubTypeValueEnumMap = {
0: TransactionSubType.none,
@ -371,6 +372,7 @@ const _TransactionsubTypeValueEnumMap = {
2: TransactionSubType.mint,
3: TransactionSubType.join,
4: TransactionSubType.ethToken,
5: TransactionSubType.cashFusion,
};
const _TransactiontypeEnumValueMap = {
'outgoing': 0,

View file

@ -0,0 +1,124 @@
import 'package:isar/isar.dart';
part 'input_v2.g.dart';
@Embedded()
class OutpointV2 {
late final String txid;
late final int vout;
OutpointV2();
static OutpointV2 isarCantDoRequiredInDefaultConstructor({
required String txid,
required int vout,
}) =>
OutpointV2()
..vout = vout
..txid = txid;
@override
String toString() {
return 'OutpointV2(\n'
' txid: $txid,\n'
' vout: $vout,\n'
')';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is OutpointV2 && other.txid == txid && other.vout == vout;
}
@override
int get hashCode {
return Object.hash(
txid.hashCode,
vout.hashCode,
);
}
}
@Embedded()
class InputV2 {
late final String? scriptSigHex;
late final int? sequence;
late final OutpointV2? outpoint;
late final List<String> addresses;
late final String valueStringSats;
late final String? coinbase;
late final String? witness;
late final String? innerRedeemScriptAsm;
late final bool walletOwns;
@ignore
BigInt get value => BigInt.parse(valueStringSats);
InputV2();
static InputV2 isarCantDoRequiredInDefaultConstructor({
required String? scriptSigHex,
required int? sequence,
required OutpointV2? outpoint,
required List<String> addresses,
required String valueStringSats,
required String? witness,
required String? innerRedeemScriptAsm,
required String? coinbase,
required bool walletOwns,
}) =>
InputV2()
..scriptSigHex = scriptSigHex
..sequence = sequence
..outpoint = outpoint
..addresses = List.unmodifiable(addresses)
..valueStringSats = valueStringSats
..witness = witness
..innerRedeemScriptAsm = innerRedeemScriptAsm
..coinbase = coinbase
..walletOwns = walletOwns;
InputV2 copyWith({
String? scriptSigHex,
int? sequence,
OutpointV2? outpoint,
List<String>? addresses,
String? valueStringSats,
String? coinbase,
String? witness,
String? innerRedeemScriptAsm,
bool? walletOwns,
}) {
return InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: scriptSigHex ?? this.scriptSigHex,
sequence: sequence ?? this.sequence,
outpoint: outpoint ?? this.outpoint,
addresses: addresses ?? this.addresses,
valueStringSats: valueStringSats ?? this.valueStringSats,
coinbase: coinbase ?? this.coinbase,
witness: witness ?? this.witness,
innerRedeemScriptAsm: innerRedeemScriptAsm ?? this.innerRedeemScriptAsm,
walletOwns: walletOwns ?? this.walletOwns,
);
}
@override
String toString() {
return 'InputV2(\n'
' scriptSigHex: $scriptSigHex,\n'
' sequence: $sequence,\n'
' outpoint: $outpoint,\n'
' addresses: $addresses,\n'
' valueStringSats: $valueStringSats,\n'
' coinbase: $coinbase,\n'
' witness: $witness,\n'
' innerRedeemScriptAsm: $innerRedeemScriptAsm,\n'
' walletOwns: $walletOwns,\n'
')';
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,121 @@
import 'package:decimal/decimal.dart';
import 'package:isar/isar.dart';
part 'output_v2.g.dart';
@Embedded()
class OutputV2 {
late final String scriptPubKeyHex;
late final String valueStringSats;
late final List<String> addresses;
late final bool walletOwns;
@ignore
BigInt get value => BigInt.parse(valueStringSats);
OutputV2();
static OutputV2 isarCantDoRequiredInDefaultConstructor({
required String scriptPubKeyHex,
required String valueStringSats,
required List<String> addresses,
required bool walletOwns,
}) =>
OutputV2()
..scriptPubKeyHex = scriptPubKeyHex
..valueStringSats = valueStringSats
..walletOwns = walletOwns
..addresses = List.unmodifiable(addresses);
OutputV2 copyWith({
String? scriptPubKeyHex,
String? valueStringSats,
List<String>? addresses,
bool? walletOwns,
}) {
return OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: scriptPubKeyHex ?? this.scriptPubKeyHex,
valueStringSats: valueStringSats ?? this.valueStringSats,
addresses: addresses ?? this.addresses,
walletOwns: walletOwns ?? this.walletOwns,
);
}
static OutputV2 fromElectrumXJson(
Map<String, dynamic> json, {
required bool walletOwns,
required int decimalPlaces,
}) {
try {
List<String> addresses = [];
if (json["scriptPubKey"]?["addresses"] is List) {
for (final e in json["scriptPubKey"]["addresses"] as List) {
addresses.add(e as String);
}
} else if (json["scriptPubKey"]?["address"] is String) {
addresses.add(json["scriptPubKey"]?["address"] as String);
}
return OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: json["scriptPubKey"]["hex"] as String,
valueStringSats: parseOutputAmountString(
json["value"].toString(),
decimalPlaces: decimalPlaces,
),
addresses: addresses,
walletOwns: walletOwns,
);
} catch (e) {
throw Exception("Failed to parse OutputV2 from $json");
}
}
static String parseOutputAmountString(
String amount, {
required int decimalPlaces,
}) {
final temp = Decimal.parse(amount);
if (temp < Decimal.zero) {
throw Exception("Negative value found");
}
final String valueStringSats;
if (temp.isInteger) {
valueStringSats = temp.toString();
} else {
valueStringSats = temp.shift(decimalPlaces).toBigInt().toString();
}
return valueStringSats;
}
@override
String toString() {
return 'OutputV2(\n'
' scriptPubKeyHex: $scriptPubKeyHex,\n'
' value: $value,\n'
' walletOwns: $walletOwns,\n'
' addresses: $addresses,\n'
')';
}
}
bool _listEquals<T, U>(List<T> a, List<U> b) {
if (T != U) {
return false;
}
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}

View file

@ -0,0 +1,617 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'output_v2.dart';
// **************************************************************************
// IsarEmbeddedGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
const OutputV2Schema = Schema(
name: r'OutputV2',
id: -6134367361914065515,
properties: {
r'addresses': PropertySchema(
id: 0,
name: r'addresses',
type: IsarType.stringList,
),
r'scriptPubKeyHex': PropertySchema(
id: 1,
name: r'scriptPubKeyHex',
type: IsarType.string,
),
r'valueStringSats': PropertySchema(
id: 2,
name: r'valueStringSats',
type: IsarType.string,
),
r'walletOwns': PropertySchema(
id: 3,
name: r'walletOwns',
type: IsarType.bool,
)
},
estimateSize: _outputV2EstimateSize,
serialize: _outputV2Serialize,
deserialize: _outputV2Deserialize,
deserializeProp: _outputV2DeserializeProp,
);
int _outputV2EstimateSize(
OutputV2 object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.addresses.length * 3;
{
for (var i = 0; i < object.addresses.length; i++) {
final value = object.addresses[i];
bytesCount += value.length * 3;
}
}
bytesCount += 3 + object.scriptPubKeyHex.length * 3;
bytesCount += 3 + object.valueStringSats.length * 3;
return bytesCount;
}
void _outputV2Serialize(
OutputV2 object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeStringList(offsets[0], object.addresses);
writer.writeString(offsets[1], object.scriptPubKeyHex);
writer.writeString(offsets[2], object.valueStringSats);
writer.writeBool(offsets[3], object.walletOwns);
}
OutputV2 _outputV2Deserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = OutputV2();
object.addresses = reader.readStringList(offsets[0]) ?? [];
object.scriptPubKeyHex = reader.readString(offsets[1]);
object.valueStringSats = reader.readString(offsets[2]);
object.walletOwns = reader.readBool(offsets[3]);
return object;
}
P _outputV2DeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readStringList(offset) ?? []) as P;
case 1:
return (reader.readString(offset)) as P;
case 2:
return (reader.readString(offset)) as P;
case 3:
return (reader.readBool(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
extension OutputV2QueryFilter
on QueryBuilder<OutputV2, OutputV2, QFilterCondition> {
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'addresses',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'addresses',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'addresses',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'addresses',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'addresses',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'addresses',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'addresses',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'addresses',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'addresses',
value: '',
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesElementIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'addresses',
value: '',
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesLengthEqualTo(int length) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'addresses',
length,
true,
length,
true,
);
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition> addressesIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'addresses',
0,
true,
0,
true,
);
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'addresses',
0,
false,
999999,
true,
);
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesLengthLessThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'addresses',
0,
true,
length,
include,
);
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesLengthGreaterThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'addresses',
length,
include,
999999,
true,
);
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
addressesLengthBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'addresses',
lower,
includeLower,
upper,
includeUpper,
);
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'scriptPubKeyHex',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'scriptPubKeyHex',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'scriptPubKeyHex',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'scriptPubKeyHex',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'scriptPubKeyHex',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'scriptPubKeyHex',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'scriptPubKeyHex',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'scriptPubKeyHex',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'scriptPubKeyHex',
value: '',
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
scriptPubKeyHexIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'scriptPubKeyHex',
value: '',
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'valueStringSats',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'valueStringSats',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'valueStringSats',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'valueStringSats',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'valueStringSats',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'valueStringSats',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'valueStringSats',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'valueStringSats',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'valueStringSats',
value: '',
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
valueStringSatsIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'valueStringSats',
value: '',
));
});
}
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition> walletOwnsEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'walletOwns',
value: value,
));
});
}
}
extension OutputV2QueryObject
on QueryBuilder<OutputV2, OutputV2, QFilterCondition> {}

View file

@ -0,0 +1,120 @@
import 'dart:math';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
part 'transaction_v2.g.dart';
@Collection()
class TransactionV2 {
Id id = Isar.autoIncrement;
@Index()
final String walletId;
@Index(unique: true, composite: [CompositeIndex("walletId")])
final String txid;
final String hash;
@Index()
late final int timestamp;
final int? height;
final String? blockHash;
final int version;
final List<InputV2> inputs;
final List<OutputV2> outputs;
@enumerated
final TransactionType type;
@enumerated
final TransactionSubType subType;
TransactionV2({
required this.walletId,
required this.blockHash,
required this.hash,
required this.txid,
required this.timestamp,
required this.height,
required this.inputs,
required this.outputs,
required this.version,
required this.type,
required this.subType,
});
int getConfirmations(int currentChainHeight) {
if (height == null || height! <= 0) return 0;
return max(0, currentChainHeight - (height! - 1));
}
bool isConfirmed(int currentChainHeight, int minimumConfirms) {
final confirmations = getConfirmations(currentChainHeight);
return confirmations >= minimumConfirms;
}
Amount getFee({required Coin coin}) {
final inSum =
inputs.map((e) => e.value).reduce((value, element) => value += element);
final outSum = outputs
.map((e) => e.value)
.reduce((value, element) => value += element);
return Amount(rawValue: inSum - outSum, fractionDigits: coin.decimals);
}
Amount getAmountReceivedThisWallet({required Coin coin}) {
final outSum = outputs
.where((e) => e.walletOwns)
.fold(BigInt.zero, (p, e) => p + e.value);
return Amount(rawValue: outSum, fractionDigits: coin.decimals);
}
Amount getAmountSentFromThisWallet({required Coin coin}) {
final inSum = inputs
.where((e) => e.walletOwns)
.fold(BigInt.zero, (p, e) => p + e.value);
return Amount(rawValue: inSum, fractionDigits: coin.decimals);
}
Set<String> associatedAddresses() => {
...inputs.map((e) => e.addresses).expand((e) => e),
...outputs.map((e) => e.addresses).expand((e) => e),
};
@override
String toString() {
return 'TransactionV2(\n'
' walletId: $walletId,\n'
' hash: $hash,\n'
' txid: $txid,\n'
' type: $type,\n'
' subType: $subType,\n'
' timestamp: $timestamp,\n'
' height: $height,\n'
' blockHash: $blockHash,\n'
' version: $version,\n'
' inputs: $inputs,\n'
' outputs: $outputs,\n'
')';
}
}
enum TxDirection {
outgoing,
incoming;
}
enum TxType {
normal,
}

File diff suppressed because it is too large Load diff

View file

@ -28,4 +28,15 @@ class SigningData {
Uint8List? output;
ECPair? keyPair;
Uint8List? redeemScript;
@override
String toString() {
return "SigningData{\n"
" derivePathType: $derivePathType,\n"
" utxo: $utxo,\n"
" output: $output,\n"
" keyPair: $keyPair,\n"
" redeemScript: $redeemScript,\n"
"}";
}
}

View file

@ -0,0 +1,446 @@
/*
* 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 sneurlax on 2023-07-26
*
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_native_splash/cli_commands.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/cashfusion/fusion_progress_view.dart';
import 'package:stackwallet/pages/cashfusion/fusion_rounds_selection_sheet.dart';
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
class CashFusionView extends ConsumerStatefulWidget {
const CashFusionView({
super.key,
required this.walletId,
});
static const routeName = "/cashFusionView";
final String walletId;
@override
ConsumerState<CashFusionView> createState() => _CashFusionViewState();
}
class _CashFusionViewState extends ConsumerState<CashFusionView> {
late final TextEditingController serverController;
late final FocusNode serverFocusNode;
late final TextEditingController portController;
late final FocusNode portFocusNode;
late final TextEditingController fusionRoundController;
late final FocusNode fusionRoundFocusNode;
bool _enableSSLCheckbox = false;
bool _enableStartButton = false;
FusionOption _option = FusionOption.continuous;
Future<void> _startFusion() async {
final fusionWallet = ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.wallet as FusionWalletInterface;
try {
fusionWallet.uiState = ref.read(
fusionProgressUIStateProvider(widget.walletId),
);
} catch (e) {
if (!e.toString().contains(
"FusionProgressUIState was already set for ${widget.walletId}")) {
rethrow;
}
}
final int rounds = _option == FusionOption.continuous
? 0
: int.parse(fusionRoundController.text);
final newInfo = FusionInfo(
host: serverController.text,
port: int.parse(portController.text),
ssl: _enableSSLCheckbox,
rounds: rounds,
);
// update user prefs (persistent)
ref.read(prefsChangeNotifierProvider).fusionServerInfo = newInfo;
unawaited(
fusionWallet.fuse(
fusionInfo: newInfo,
),
);
await Navigator.of(context).pushNamed(
FusionProgressView.routeName,
arguments: widget.walletId,
);
}
@override
void initState() {
serverController = TextEditingController();
portController = TextEditingController();
fusionRoundController = TextEditingController();
serverFocusNode = FocusNode();
portFocusNode = FocusNode();
fusionRoundFocusNode = FocusNode();
final info = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
serverController.text = info.host;
portController.text = info.port.toString();
_enableSSLCheckbox = info.ssl;
_option = info.rounds == 0 ? FusionOption.continuous : FusionOption.custom;
fusionRoundController.text = info.rounds.toString();
_enableStartButton =
serverController.text.isNotEmpty && portController.text.isNotEmpty;
super.initState();
}
@override
void dispose() {
serverController.dispose();
portController.dispose();
fusionRoundController.dispose();
serverFocusNode.dispose();
portFocusNode.dispose();
fusionRoundFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Background(
child: SafeArea(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
automaticallyImplyLeading: false,
leading: const AppBarBackButton(),
title: Text(
"CashFusion",
style: STextStyles.navBarTitle(context),
),
titleSpacing: 0,
actions: [
AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
size: 36,
icon: SvgPicture.asset(
Assets.svg.circleQuestion,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
onPressed: () async {
//' TODO show about?
},
),
),
],
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RoundedWhiteContainer(
child: Text(
"CashFusion allows you to anonymize your BCH coins.",
style: STextStyles.w500_12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
),
const SizedBox(
height: 16,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Server settings",
style: STextStyles.w500_14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
),
),
CustomTextButton(
text: "Default",
onTap: () {
const def = FusionInfo.DEFAULTS;
serverController.text = def.host;
portController.text = def.port.toString();
fusionRoundController.text =
def.rounds.toString();
_option = FusionOption.continuous;
setState(() {
_enableSSLCheckbox = def.ssl;
});
},
),
],
),
const SizedBox(
height: 12,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: false,
enableSuggestions: false,
controller: serverController,
focusNode: serverFocusNode,
onChanged: (value) {
setState(() {
_enableStartButton = value.isNotEmpty &&
portController.text.isNotEmpty &&
fusionRoundController.text.isNotEmpty;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Server",
serverFocusNode,
context,
desktopMed: true,
),
),
),
const SizedBox(
height: 10,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: false,
enableSuggestions: false,
controller: portController,
focusNode: portFocusNode,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
_enableStartButton = value.isNotEmpty &&
serverController.text.isNotEmpty &&
fusionRoundController.text.isNotEmpty;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Port",
portFocusNode,
context,
),
),
),
const SizedBox(
height: 10,
),
GestureDetector(
onTap: () {
setState(() {
_enableSSLCheckbox = !_enableSSLCheckbox;
});
},
child: Container(
color: Colors.transparent,
child: Row(
children: [
SizedBox(
width: 20,
height: 20,
child: Checkbox(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
value: _enableSSLCheckbox,
onChanged: (newValue) {
setState(
() {
_enableSSLCheckbox =
!_enableSSLCheckbox;
},
);
},
),
),
const SizedBox(
width: 12,
),
Text(
"Use SSL",
style: STextStyles.itemSubtitle12(context),
),
],
),
),
),
const SizedBox(
height: 16,
),
Text(
"Rounds of fusion",
style: STextStyles.w500_14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
),
),
const SizedBox(
height: 12,
),
RoundedContainer(
onPressed: () async {
final option =
await showModalBottomSheet<FusionOption?>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) {
return FusionRoundCountSelectSheet(
currentOption: _option,
);
},
);
if (option != null) {
setState(() {
_option = option;
});
}
},
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
_option.name.capitalize(),
style: STextStyles.w500_12(context),
),
SvgPicture.asset(
Assets.svg.chevronDown,
width: 12,
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
],
),
),
),
if (_option == FusionOption.custom)
const SizedBox(
height: 10,
),
if (_option == FusionOption.custom)
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: false,
enableSuggestions: false,
controller: fusionRoundController,
focusNode: fusionRoundFocusNode,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
_enableStartButton = value.isNotEmpty &&
serverController.text.isNotEmpty &&
portController.text.isNotEmpty;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Number of fusions",
fusionRoundFocusNode,
context,
).copyWith(
labelText: "Enter number of fusions.."),
),
),
const SizedBox(
height: 16,
),
const Spacer(),
PrimaryButton(
label: "Start",
enabled: _enableStartButton,
onPressed: _startFusion,
),
],
),
),
),
),
);
},
),
),
),
);
}
}

View file

@ -0,0 +1,248 @@
/*
* 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 julian on 2023-10-16
*
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_progress.dart';
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class FusionProgressView extends ConsumerStatefulWidget {
const FusionProgressView({
super.key,
required this.walletId,
});
static const routeName = "/cashFusionProgressView";
final String walletId;
@override
ConsumerState<FusionProgressView> createState() => _FusionProgressViewState();
}
class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
Future<bool> _requestAndProcessCancel() async {
final shouldCancel = await showDialog<bool?>(
context: context,
barrierDismissible: false,
builder: (_) => StackDialog(
title: "Cancel fusion?",
leftButton: SecondaryButton(
label: "No",
buttonHeight: null,
onPressed: () {
Navigator.of(context).pop(false);
},
),
rightButton: PrimaryButton(
label: "Yes",
buttonHeight: null,
onPressed: () {
Navigator.of(context).pop(true);
},
),
),
);
if (shouldCancel == true && mounted) {
final fusionWallet = ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.wallet as FusionWalletInterface;
await showLoading(
whileFuture: Future.wait([
fusionWallet.stop(),
Future<void>.delayed(const Duration(seconds: 2)),
]),
context: context,
isDesktop: Util.isDesktop,
message: "Stopping fusion",
);
return true;
} else {
return false;
}
}
@override
Widget build(BuildContext context) {
final bool _succeeded =
ref.watch(fusionProgressUIStateProvider(widget.walletId)).succeeded;
final bool _failed =
ref.watch(fusionProgressUIStateProvider(widget.walletId)).failed;
final int _fusionRoundsCompleted = ref
.watch(fusionProgressUIStateProvider(widget.walletId))
.fusionRoundsCompleted;
return WillPopScope(
onWillPop: () async {
return await _requestAndProcessCancel();
},
child: Background(
child: SafeArea(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
automaticallyImplyLeading: false,
leading: AppBarBackButton(
onPressed: () async {
if (await _requestAndProcessCancel()) {
if (mounted) {
Navigator.of(context).pop();
}
}
},
),
title: Text(
"Fusion progress",
style: STextStyles.navBarTitle(context),
),
titleSpacing: 0,
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (_fusionRoundsCompleted == 0)
RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarBackError,
child: Text(
"Do not close this window. If you exit, "
"the process will be canceled.",
style:
STextStyles.smallMed14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarTextError,
),
textAlign: TextAlign.center,
),
),
if (_fusionRoundsCompleted > 0)
RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarBackInfo,
child: Text(
"Fusion rounds completed: $_fusionRoundsCompleted",
style: STextStyles.w500_14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarTextInfo,
),
textAlign: TextAlign.center,
),
),
const SizedBox(
height: 20,
),
FusionProgress(
walletId: widget.walletId,
),
const Spacer(),
const SizedBox(
height: 16,
),
if (_succeeded)
PrimaryButton(
label: "Fuse again",
onPressed: _fuseAgain,
),
if (_succeeded)
const SizedBox(
height: 16,
),
if (_failed)
PrimaryButton(
label: "Try again",
onPressed: _fuseAgain,
),
if (_failed)
const SizedBox(
height: 16,
),
SecondaryButton(
label: "Cancel",
onPressed: () async {
if (await _requestAndProcessCancel()) {
if (mounted) {
Navigator.of(context).pop();
}
}
},
),
],
),
),
),
),
);
},
),
),
),
),
);
}
/// Fuse again.
void _fuseAgain() async {
final fusionWallet = ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.wallet as FusionWalletInterface;
final fusionInfo = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
try {
fusionWallet.uiState = ref.read(
fusionProgressUIStateProvider(widget.walletId),
);
} catch (e) {
if (!e.toString().contains(
"FusionProgressUIState was already set for ${widget.walletId}")) {
rethrow;
}
}
unawaited(fusionWallet.fuse(fusionInfo: fusionInfo));
}
}

View file

@ -0,0 +1,168 @@
/*
* 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 julian on 2023-10-16
*
*/
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_native_splash/cli_commands.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
enum FusionOption {
continuous,
custom;
}
class FusionRoundCountSelectSheet extends HookWidget {
const FusionRoundCountSelectSheet({
Key? key,
required this.currentOption,
}) : super(key: key);
final FusionOption currentOption;
@override
Widget build(BuildContext context) {
final option = useState(currentOption);
return WillPopScope(
onWillPop: () async {
Navigator.of(context).pop(option.value);
return false;
},
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(20),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 24,
right: 24,
top: 10,
bottom: 0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
width: 60,
height: 4,
),
),
const SizedBox(
height: 36,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Rounds of fusion",
style: STextStyles.pageTitleH2(context),
textAlign: TextAlign.left,
),
const SizedBox(
height: 20,
),
for (int i = 0; i < FusionOption.values.length; i++)
Column(
children: [
GestureDetector(
onTap: () {
option.value = FusionOption.values[i];
Navigator.of(context).pop(option.value);
},
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Column(
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: FusionOption.values[i],
groupValue: option.value,
onChanged: (_) {
option.value = FusionOption.values[i];
Navigator.of(context).pop(option.value);
},
),
),
// ],
// ),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
FusionOption.values[i].name.capitalize(),
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
const SizedBox(
height: 2,
),
Text(
FusionOption.values[i] ==
FusionOption.continuous
? "Keep fusing until manually stopped"
: "Stop after a set number of fusions",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
),
textAlign: TextAlign.left,
),
],
),
],
),
),
),
const SizedBox(
height: 16,
),
],
),
const SizedBox(
height: 16,
),
],
),
],
),
),
),
);
}
}

View file

@ -13,13 +13,19 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/receive_view/addresses/address_tag.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
@ -145,6 +151,8 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
@override
Widget build(BuildContext context) {
final coin = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId).coin));
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(
@ -258,10 +266,16 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
borderColor: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
child: _AddressDetailsTxList(
walletId: widget.walletId,
address: address,
),
child: coin == Coin.bitcoincash ||
coin == Coin.bitcoincashTestnet
? _AddressDetailsTxV2List(
walletId: widget.walletId,
address: address,
)
: _AddressDetailsTxList(
walletId: widget.walletId,
address: address,
),
),
],
),
@ -377,10 +391,15 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
height: 12,
),
if (!isDesktop)
_AddressDetailsTxList(
walletId: widget.walletId,
address: address,
),
coin == Coin.bitcoincash || coin == Coin.bitcoincashTestnet
? _AddressDetailsTxV2List(
walletId: widget.walletId,
address: address,
)
: _AddressDetailsTxList(
walletId: widget.walletId,
address: address,
),
],
),
);
@ -445,6 +464,66 @@ class _AddressDetailsTxList extends StatelessWidget {
}
}
class _AddressDetailsTxV2List extends ConsumerWidget {
const _AddressDetailsTxV2List({
Key? key,
required this.walletId,
required this.address,
}) : super(key: key);
final String walletId;
final Address address;
@override
Widget build(BuildContext context, WidgetRef ref) {
final query = ref
.watch(mainDBProvider)
.isar
.transactionV2s
.where()
.walletIdEqualTo(walletId)
.filter()
.inputsElement((q) => q.addressesElementContains(address.value))
.or()
.outputsElement((q) => q.addressesElementContains(address.value))
.sortByTimestampDesc();
final count = query.countSync();
if (count > 0) {
if (Util.isDesktop) {
final txns = query.findAllSync();
return ListView.separated(
shrinkWrap: true,
primary: false,
itemBuilder: (_, index) => TransactionCardV2(
transaction: txns[index],
),
separatorBuilder: (_, __) => const _Div(height: 1),
itemCount: count,
);
} else {
return RoundedWhiteContainer(
padding: EdgeInsets.zero,
child: Column(
mainAxisSize: MainAxisSize.min,
children: query
.findAllSync()
.map(
(e) => TransactionCardV2(
transaction: e,
),
)
.toList(),
),
);
}
} else {
return const NoTransActionsFound();
}
}
}
class _Div extends StatelessWidget {
const _Div({
Key? key,

View file

@ -9,18 +9,27 @@
*/
import 'dart:async';
import 'dart:typed_data';
import 'package:bitbox/bitbox.dart' as bb;
import 'package:bitcoindart/bitcoindart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/debug_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
@ -343,6 +352,181 @@ class HiddenSettings extends StatelessWidget {
}
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
try {
final p = TT();
final n = ref
.read(nodeServiceChangeNotifierProvider)
.getPrimaryNodeFor(
coin: Coin.bitcoincash)!;
final e = ElectrumX.from(
node: ElectrumXNode(
address: n.host,
port: n.port,
name: n.name,
id: n.id,
useSSL: n.useSSL,
),
prefs:
ref.read(prefsChangeNotifierProvider),
failovers: [],
);
final ce =
CachedElectrumX(electrumXClient: e);
final txids = [
"", // cashTokenTxid
"6a0444358bc41913c5b04a8dc06896053184b3641bc62502d18f954865b6ce1e", // normalTxid
"67f13c375f9be897036cac77b7900dc74312c4ba6fe22f419f5cb21d4151678c", // fusionTxid
"c0ac3f88b238a023d2a87226dc90c3b0f9abc3eeb227e2730087b0b95ee5b3f9", // slpTokenSendTxid
"7a427a156fe70f83d3ccdd17e75804cc0df8c95c64ce04d256b3851385002a0b", // slpTokenGenesisTxid
];
// final json =
// await e.getTransaction(txHash: txids[1]);
// await p.parseBchTx(json, "NORMAL TXID:");
//
// final json2 =
// await e.getTransaction(txHash: txids[2]);
// await p.parseBchTx(json2, "FUSION TXID:");
//
// // print("CASH TOKEN TXID:");
// // final json3 =
// // await e.getTransaction(txHash: txids[2]);
// // await p.parseBchTx(json3);
//
await p.getTransaction(
txids[3],
Coin.bitcoincash,
"lol",
ce,
"SLP TOKEN SEND TXID:");
await p.getTransaction(
"009d31380d2dbfb5c91500c861d55b531a8b762b0abb19353db884548dbac8b6",
Coin.bitcoincash,
"lol",
ce,
"COINBASE TXID:");
// final json5 =
// await e.getTransaction(txHash: txids[4]);
// await p.parseBchTx(
// json5, "SLP TOKEN GENESIS TXID:");
} catch (e, s) {
print("$e\n$s");
}
},
child: RoundedWhiteContainer(
child: Text(
"Parse BCH tx test",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
try {
final p = TT();
final n = ref
.read(nodeServiceChangeNotifierProvider)
.getPrimaryNodeFor(
coin: Coin.bitcoincash)!;
final e = ElectrumX.from(
node: ElectrumXNode(
address: n.host,
port: n.port,
name: n.name,
id: n.id,
useSSL: n.useSSL,
),
prefs:
ref.read(prefsChangeNotifierProvider),
failovers: [],
);
final address =
"qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwyztz2p80d";
List<int> _base32Decode(String string) {
final data = Uint8List(string.length);
for (int i = 0; i < string.length; i++) {
final value = string[i];
if (!_CHARSET_INVERSE_INDEX
.containsKey(value))
throw FormatException(
"Invalid character '$value'");
data[i] =
_CHARSET_INVERSE_INDEX[string[i]]!;
}
return data.sublist(1);
}
final dec = _base32Decode(address);
final pd = PaymentData(
pubkey: Uint8List.fromList(dec));
final p2pkh =
P2PKH(data: pd, network: bitcoincash);
// final addr = p2pkh.data.address!;
final addr = bb.Address.toLegacyAddress(
"bitcoincash:qp352c2skpdxwzzd090mec3v37au5dmfwgwfw686sz",
);
final scripthash =
AddressUtils.convertToScriptHash(
addr, bitcoincash);
final utxos =
await e.getUTXOs(scripthash: scripthash);
Util.printJson(utxos, "UTXOS for $address");
final hist = await e.getTransaction(
txHash: utxos.first["tx_hash"] as String,
);
Util.printJson(hist, "HISTORY for $address");
} catch (e, s) {
print("$e\n$s");
}
},
child: RoundedWhiteContainer(
child: Text(
"UTXOs",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
},
),
// const SizedBox(
// height: 12,
// ),
@ -384,3 +568,38 @@ class HiddenSettings extends StatelessWidget {
);
}
}
const _CHARSET_INVERSE_INDEX = {
'q': 0,
'p': 1,
'z': 2,
'r': 3,
'y': 4,
'9': 5,
'x': 6,
'8': 7,
'g': 8,
'f': 9,
'2': 10,
't': 11,
'v': 12,
'd': 13,
'w': 14,
'0': 15,
's': 16,
'3': 17,
'j': 18,
'n': 19,
'5': 20,
'4': 21,
'k': 22,
'h': 23,
'c': 24,
'e': 25,
'6': 26,
'm': 27,
'u': 28,
'a': 29,
'7': 30,
'l': 31,
};

View file

@ -225,8 +225,8 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
_hasLoaded = true;
}
if (!_hasLoaded) {
return Column(
children: const [
return const Column(
children: [
Spacer(),
Center(
child: LoadingIndicator(

View file

@ -37,12 +37,14 @@ class TokenView extends ConsumerStatefulWidget {
const TokenView({
Key? key,
required this.walletId,
this.popPrevious = false,
this.eventBus,
}) : super(key: key);
static const String routeName = "/token";
final String walletId;
final bool popPrevious;
final EventBus? eventBus;
@override
@ -69,157 +71,175 @@ class _TokenViewState extends ConsumerState<TokenView> {
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
centerTitle: true,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
EthTokenIcon(
contractAddress: ref.watch(
tokenServiceProvider.select(
(value) => value!.tokenContract.address,
return WillPopScope(
onWillPop: () async {
final nav = Navigator.of(context);
if (widget.popPrevious) {
nav.pop();
}
nav.pop();
return false;
},
child: Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
final nav = Navigator.of(context);
if (widget.popPrevious) {
nav.pop();
}
nav.pop();
},
),
centerTitle: true,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
EthTokenIcon(
contractAddress: ref.watch(
tokenServiceProvider.select(
(value) => value!.tokenContract.address,
),
),
size: 24,
),
const SizedBox(
width: 10,
),
Flexible(
child: Text(
ref.watch(tokenServiceProvider
.select((value) => value!.tokenContract.name)),
style: STextStyles.navBarTitle(context),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
size: 24,
],
),
),
],
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 2),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
icon: SvgPicture.asset(
Assets.svg.verticalEllipsis,
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
const SizedBox(
width: 10,
),
Flexible(
child: Text(
ref.watch(tokenServiceProvider
.select((value) => value!.tokenContract.name)),
style: STextStyles.navBarTitle(context),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
],
onPressed: () {
// todo: context menu
Navigator.of(context).pushNamed(
TokenContractDetailsView.routeName,
arguments: Tuple2(
ref.watch(tokenServiceProvider
.select((value) => value!.tokenContract.address)),
widget.walletId,
),
);
},
),
),
),
],
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 2),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
icon: SvgPicture.asset(
Assets.svg.verticalEllipsis,
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
onPressed: () {
// todo: context menu
Navigator.of(context).pushNamed(
TokenContractDetailsView.routeName,
arguments: Tuple2(
ref.watch(tokenServiceProvider
.select((value) => value!.tokenContract.address)),
widget.walletId,
),
);
},
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Column(
children: [
const SizedBox(
height: 10,
),
),
),
],
),
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Column(
children: [
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TokenSummary(
walletId: widget.walletId,
initialSyncStatus: initialSyncStatus,
),
),
const SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Transactions",
style: STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
),
),
CustomTextButton(
text: "See all",
onTap: () {
Navigator.of(context).pushNamed(
AllTransactionsView.routeName,
arguments: widget.walletId,
);
},
),
],
),
),
const SizedBox(
height: 12,
),
Expanded(
child: Padding(
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(
Constants.size.circularBorderRadius,
child: TokenSummary(
walletId: widget.walletId,
initialSyncStatus: initialSyncStatus,
),
),
const SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Transactions",
style: STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
),
),
bottom: Radius.circular(
// TokenView.navBarHeight / 2.0,
Constants.size.circularBorderRadius,
CustomTextButton(
text: "See all",
onTap: () {
Navigator.of(context).pushNamed(
AllTransactionsView.routeName,
arguments: (
walletId: widget.walletId,
isTokens: true,
),
);
},
),
),
child: Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(
],
),
),
const SizedBox(
height: 12,
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(
Constants.size.circularBorderRadius,
),
bottom: Radius.circular(
// TokenView.navBarHeight / 2.0,
Constants.size.circularBorderRadius,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: TokenTransactionsList(
walletId: widget.walletId,
),
child: Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: TokenTransactionsList(
walletId: widget.walletId,
),
),
],
),
),
),
),
),
),
],
],
),
),
),
),

View file

@ -13,6 +13,7 @@ import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/isar/stack_theme.dart';
import 'package:stackwallet/themes/theme_providers.dart';
@ -27,15 +28,24 @@ class TxIcon extends ConsumerWidget {
required this.coin,
}) : super(key: key);
final Transaction transaction;
final Object transaction;
final int currentHeight;
final Coin coin;
static const Size size = Size(32, 32);
String _getAssetName(
bool isCancelled, bool isReceived, bool isPending, IThemeAssets assets) {
if (!isReceived && transaction.subType == TransactionSubType.mint) {
bool isCancelled,
bool isReceived,
bool isPending,
TransactionSubType subType,
IThemeAssets assets,
) {
if (subType == TransactionSubType.cashFusion) {
return Assets.svg.txCashFusion;
}
if (!isReceived && subType == TransactionSubType.mint) {
if (isCancelled) {
return Assets.svg.anonymizeFailed;
}
@ -66,37 +76,61 @@ class TxIcon extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final txIsReceived = transaction.type == TransactionType.incoming;
final bool txIsReceived;
final String assetName;
final assetName = _getAssetName(
transaction.isCancelled,
txIsReceived,
!transaction.isConfirmed(
currentHeight,
coin.requiredConfirmations,
),
ref.watch(themeAssetsProvider),
);
if (transaction is Transaction) {
final tx = transaction as Transaction;
txIsReceived = tx.type == TransactionType.incoming;
assetName = _getAssetName(
tx.isCancelled,
txIsReceived,
!tx.isConfirmed(
currentHeight,
coin.requiredConfirmations,
),
tx.subType,
ref.watch(themeAssetsProvider),
);
} else if (transaction is TransactionV2) {
final tx = transaction as TransactionV2;
txIsReceived = tx.type == TransactionType.incoming;
assetName = _getAssetName(
false,
txIsReceived,
!tx.isConfirmed(
currentHeight,
coin.requiredConfirmations,
),
tx.subType,
ref.watch(themeAssetsProvider),
);
} else {
throw ArgumentError(
"Unknown transaction type ${transaction.runtimeType}",
);
}
return SizedBox(
width: size.width,
height: size.height,
child: Center(
// if it starts with "assets" we assume its local
// TODO: a more thorough check
child: assetName.startsWith("assets")
? SvgPicture.asset(
// if it starts with "assets" we assume its local
// TODO: a more thorough check
child: assetName.startsWith("assets")
? SvgPicture.asset(
assetName,
width: size.width,
height: size.height,
)
: SvgPicture.file(
File(
assetName,
width: size.width,
height: size.height,
)
: SvgPicture.file(
File(
assetName,
),
width: size.width,
height: size.height,
)),
),
width: size.width,
height: size.height,
),
),
);
}
}

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/transaction_filter.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
@ -46,15 +47,23 @@ import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/widgets/transaction_card.dart';
import 'package:tuple/tuple.dart';
typedef _GroupedTransactions = ({
String label,
DateTime startDate,
List<Transaction> transactions
});
class AllTransactionsView extends ConsumerStatefulWidget {
const AllTransactionsView({
Key? key,
required this.walletId,
this.isTokens = false,
}) : super(key: key);
static const String routeName = "/allTransactions";
final String walletId;
final bool isTokens;
@override
ConsumerState<AllTransactionsView> createState() =>
@ -189,25 +198,24 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
.toList();
}
List<Tuple2<String, List<Transaction>>> groupTransactionsByMonth(
List<Transaction> transactions) {
Map<String, List<Transaction>> map = {};
List<_GroupedTransactions> groupTransactionsByMonth(
List<Transaction> transactions,
) {
Map<String, _GroupedTransactions> map = {};
for (var tx in transactions) {
final date = DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000);
final monthYear = "${Constants.monthMap[date.month]} ${date.year}";
if (map[monthYear] == null) {
map[monthYear] = [];
map[monthYear] =
(label: monthYear, startDate: date, transactions: [tx]);
} else {
map[monthYear]!.transactions.add(tx);
}
map[monthYear]!.add(tx);
}
List<Tuple2<String, List<Transaction>>> result = [];
map.forEach((key, value) {
result.add(Tuple2(key, value));
});
return result;
return map.values.toList()
..sort((a, b) => b.startDate.compareTo(a.startDate));
}
@override
@ -445,12 +453,12 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
),
if (isDesktop &&
ref.watch(transactionFilterProvider.state).state != null)
Padding(
padding: const EdgeInsets.symmetric(
const Padding(
padding: EdgeInsets.symmetric(
vertical: 8,
),
child: Row(
children: const [
children: [
TransactionFilterOptionBar(),
],
),
@ -472,8 +480,11 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
// debugPrint("Consumer build called");
return FutureBuilder(
future: ref.watch(
managerProvider.select((value) => value.transactions)),
future: widget.isTokens
? ref.watch(tokenServiceProvider
.select((value) => value!.transactions))
: ref.watch(managerProvider
.select((value) => value.transactions)),
builder: (_, AsyncSnapshot<List<Transaction>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
@ -498,7 +509,7 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
height: 12,
),
Text(
month.item1,
month.label,
style: STextStyles.smallMed12(context),
),
const SizedBox(
@ -517,14 +528,15 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
.extension<StackColors>()!
.background,
),
itemCount: month.item2.length,
itemCount: month.transactions.length,
itemBuilder: (context, index) =>
Padding(
padding: const EdgeInsets.all(4),
child: DesktopTransactionCardRow(
key: Key(
"transactionCard_key_${month.item2[index].txid}"),
transaction: month.item2[index],
"transactionCard_key_${month.transactions[index].txid}"),
transaction:
month.transactions[index],
walletId: walletId,
),
),
@ -535,7 +547,7 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
padding: const EdgeInsets.all(0),
child: Column(
children: [
...month.item2.map(
...month.transactions.map(
(tx) => TransactionCard(
key: Key(
"transactionCard_key_${tx.txid}"),

View file

@ -831,7 +831,7 @@ class _TransactionDetailsViewState
),
if (isDesktop)
IconCopyButton(
data: _transaction.address.value!.value,
data: _transaction.otherData ?? "",
),
],
),

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,196 @@
/*
* 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 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class FusionGroupDetailsView extends ConsumerStatefulWidget {
const FusionGroupDetailsView({
Key? key,
required this.transactions,
required this.walletId,
required this.coin,
}) : super(key: key);
static const String routeName = "/fusionGroupDetailsView";
final List<TransactionV2> transactions;
final String walletId;
final Coin coin;
@override
ConsumerState<FusionGroupDetailsView> createState() =>
_FusionGroupDetailsViewState();
}
class _FusionGroupDetailsViewState
extends ConsumerState<FusionGroupDetailsView> {
late final bool isDesktop;
late final String walletId;
BorderRadius get _borderRadiusFirst {
return BorderRadius.only(
topLeft: Radius.circular(
Constants.size.circularBorderRadius,
),
topRight: Radius.circular(
Constants.size.circularBorderRadius,
),
);
}
BorderRadius get _borderRadiusLast {
return BorderRadius.only(
bottomLeft: Radius.circular(
Constants.size.circularBorderRadius,
),
bottomRight: Radius.circular(
Constants.size.circularBorderRadius,
),
);
}
@override
void initState() {
isDesktop = Util.isDesktop;
walletId = widget.walletId;
super.initState();
}
@override
Widget build(BuildContext context) {
if (isDesktop) {
return Material(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.only(left: 32),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Fusion transactions",
style: STextStyles.desktopH3(context),
),
const DesktopDialogCloseButton(),
],
),
Flexible(
child: Padding(
padding: const EdgeInsets.only(
right: 32,
bottom: 32,
),
child: RoundedWhiteContainer(
borderColor: isDesktop
? Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar
: null,
padding: const EdgeInsets.all(0),
child: ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) {
BorderRadius? radius;
if (widget.transactions.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == widget.transactions.length - 1) {
radius = _borderRadiusLast;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = widget.transactions[index];
return TxListItem(
tx: tx,
coin: widget.coin,
radius: radius,
);
},
separatorBuilder: (context, index) {
return Container(
width: double.infinity,
height: 1.2,
color: Theme.of(context)
.extension<StackColors>()!
.background,
);
},
itemCount: widget.transactions.length,
),
),
),
),
],
),
),
);
} else {
return 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(
"Fusion transactions",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView.builder(
itemCount: widget.transactions.length,
itemBuilder: (context, index) {
BorderRadius? radius;
if (widget.transactions.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == widget.transactions.length - 1) {
radius = _borderRadiusLast;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = widget.transactions[index];
return TxListItem(
tx: tx,
coin: widget.coin,
radius: radius,
);
},
),
),
),
);
}
}
}

View file

@ -0,0 +1,179 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_group_details_view.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
class FusionTxGroup {
final List<TransactionV2> transactions;
FusionTxGroup(this.transactions);
}
class FusionTxGroupCard extends ConsumerWidget {
const FusionTxGroupCard({super.key, required this.group});
final FusionTxGroup group;
@override
Widget build(BuildContext context, WidgetRef ref) {
final walletId = group.transactions.first.walletId;
final coin = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).coin));
final currentHeight = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).currentHeight));
return Material(
color: Theme.of(context).extension<StackColors>()!.popupBG,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(Constants.size.circularBorderRadius),
),
child: Padding(
padding: const EdgeInsets.all(6),
child: RawMaterialButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () async {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => DesktopDialog(
maxWidth: 580,
child: FusionGroupDetailsView(
transactions: group.transactions,
coin: coin,
walletId: walletId,
),
),
);
} else {
unawaited(
Navigator.of(context).pushNamed(
FusionGroupDetailsView.routeName,
arguments: (
transactions: group.transactions,
coin: coin,
walletId: walletId,
),
),
);
}
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
TxIcon(
transaction: group.transactions.first,
coin: coin,
currentHeight: currentHeight,
),
const SizedBox(
width: 14,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"Fusions",
style: STextStyles.itemSubtitle12(context),
),
),
),
const SizedBox(
width: 10,
),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Builder(
builder: (_) {
return Text(
"${group.transactions.length} fusion transactions",
style: STextStyles.itemSubtitle12(context),
);
},
),
),
),
],
),
const SizedBox(
height: 4,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
Format.extractDateFrom(
group.transactions.last.timestamp,
),
style: STextStyles.label(context),
),
),
),
// if (ref.watch(prefsChangeNotifierProvider
// .select((value) => value.externalCalls)))
// const SizedBox(
// width: 10,
// ),
// if (ref.watch(prefsChangeNotifierProvider
// .select((value) => value.externalCalls)))
// Flexible(
// child: FittedBox(
// fit: BoxFit.scaleDown,
// child: Builder(
// builder: (_) {
// return Text(
// "$prefix${Amount.fromDecimal(
// amount.decimal * price,
// fractionDigits: 2,
// ).fiatString(
// locale: locale,
// )} $baseCurrency",
// style: STextStyles.label(context),
// );
// },
// ),
// ),
// ),
],
),
],
),
),
],
),
),
),
),
);
}
}

View file

@ -0,0 +1,287 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
import 'package:stackwallet/providers/global/locale_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/price_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
class TransactionCardV2 extends ConsumerStatefulWidget {
const TransactionCardV2({
Key? key,
required this.transaction,
}) : super(key: key);
final TransactionV2 transaction;
@override
ConsumerState<TransactionCardV2> createState() => _TransactionCardStateV2();
}
class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
late final TransactionV2 _transaction;
late final String walletId;
late final String prefix;
late final String unit;
late final Coin coin;
late final TransactionType txType;
String whatIsIt(
Coin coin,
int currentHeight,
) {
final confirmedStatus = _transaction.isConfirmed(
currentHeight,
coin.requiredConfirmations,
);
if (_transaction.subType == TransactionSubType.cashFusion) {
if (confirmedStatus) {
return "Anonymized";
} else {
return "Anonymizing";
}
}
if (_transaction.type == TransactionType.incoming) {
// if (_transaction.isMinting) {
// return "Minting";
// } else
if (confirmedStatus) {
return "Received";
} else {
return "Receiving";
}
} else if (_transaction.type == TransactionType.outgoing) {
if (confirmedStatus) {
return "Sent";
} else {
return "Sending";
}
} else if (_transaction.type == TransactionType.sentToSelf) {
return "Sent to self";
} else {
return _transaction.type.name;
}
}
@override
void initState() {
_transaction = widget.transaction;
walletId = _transaction.walletId;
if (Util.isDesktop) {
if (_transaction.type == TransactionType.outgoing &&
_transaction.subType != TransactionSubType.cashFusion) {
prefix = "-";
} else if (_transaction.type == TransactionType.incoming) {
prefix = "+";
} else {
prefix = "";
}
} else {
prefix = "";
}
coin = ref.read(walletsChangeNotifierProvider).getManager(walletId).coin;
unit = coin.ticker;
super.initState();
}
@override
Widget build(BuildContext context) {
final locale = ref.watch(
localeServiceChangeNotifierProvider.select((value) => value.locale));
final baseCurrency = ref
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
final price = ref
.watch(priceAnd24hChangeNotifierProvider
.select((value) => value.getPrice(coin)))
.item1;
final currentHeight = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).currentHeight));
final Amount amount;
if (_transaction.subType == TransactionSubType.cashFusion) {
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
} else {
switch (_transaction.type) {
case TransactionType.outgoing:
amount = _transaction.getAmountSentFromThisWallet(coin: coin);
break;
case TransactionType.incoming:
case TransactionType.sentToSelf:
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
break;
case TransactionType.unknown:
amount = _transaction.getAmountSentFromThisWallet(coin: coin);
break;
}
}
return Material(
color: Theme.of(context).extension<StackColors>()!.popupBG,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(Constants.size.circularBorderRadius),
),
child: Padding(
padding: const EdgeInsets.all(6),
child: RawMaterialButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () async {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => DesktopDialog(
maxHeight: MediaQuery.of(context).size.height - 64,
maxWidth: 580,
child: TransactionV2DetailsView(
transaction: _transaction,
coin: coin,
walletId: walletId,
),
),
);
} else {
unawaited(
Navigator.of(context).pushNamed(
TransactionV2DetailsView.routeName,
arguments: (
tx: _transaction,
coin: coin,
walletId: walletId,
),
),
);
}
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
TxIcon(
transaction: _transaction,
coin: coin,
currentHeight: currentHeight,
),
const SizedBox(
width: 14,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
whatIsIt(
coin,
currentHeight,
),
style: STextStyles.itemSubtitle12(context),
),
),
),
const SizedBox(
width: 10,
),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Builder(
builder: (_) {
return Text(
"$prefix${ref.watch(pAmountFormatter(coin)).format(amount)}",
style: STextStyles.itemSubtitle12(context),
);
},
),
),
),
],
),
const SizedBox(
height: 4,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
Format.extractDateFrom(_transaction.timestamp),
style: STextStyles.label(context),
),
),
),
if (ref.watch(prefsChangeNotifierProvider
.select((value) => value.externalCalls)))
const SizedBox(
width: 10,
),
if (ref.watch(prefsChangeNotifierProvider
.select((value) => value.externalCalls)))
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Builder(
builder: (_) {
return Text(
"$prefix${Amount.fromDecimal(
amount.decimal * price,
fractionDigits: 2,
).fiatString(
locale: locale,
)} $baseCurrency",
style: STextStyles.label(context),
);
},
),
),
),
],
),
],
),
),
],
),
),
),
),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,230 @@
/*
* 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-10-19
*
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_tx_group_card.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
class TransactionsV2List extends ConsumerStatefulWidget {
const TransactionsV2List({
Key? key,
required this.walletId,
}) : super(key: key);
final String walletId;
@override
ConsumerState<TransactionsV2List> createState() => _TransactionsV2ListState();
}
class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
bool _hasLoaded = false;
List<TransactionV2> _transactions = [];
BorderRadius get _borderRadiusFirst {
return BorderRadius.only(
topLeft: Radius.circular(
Constants.size.circularBorderRadius,
),
topRight: Radius.circular(
Constants.size.circularBorderRadius,
),
);
}
BorderRadius get _borderRadiusLast {
return BorderRadius.only(
bottomLeft: Radius.circular(
Constants.size.circularBorderRadius,
),
bottomRight: Radius.circular(
Constants.size.circularBorderRadius,
),
);
}
@override
Widget build(BuildContext context) {
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId)));
return FutureBuilder(
future: ref
.watch(mainDBProvider)
.isar
.transactionV2s
.where()
.walletIdEqualTo(widget.walletId)
.sortByTimestampDesc()
.findAll(),
builder: (fbContext, AsyncSnapshot<List<TransactionV2>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
_transactions = snapshot.data!;
_hasLoaded = true;
}
if (!_hasLoaded) {
return const Column(
children: [
Spacer(),
Center(
child: LoadingIndicator(
height: 50,
width: 50,
),
),
Spacer(
flex: 4,
),
],
);
}
if (_transactions.isEmpty) {
return const NoTransActionsFound();
} else {
_transactions.sort((a, b) => b.timestamp - a.timestamp);
final List<Object> _txns = [];
List<TransactionV2> fusions = [];
for (int i = 0; i < _transactions.length; i++) {
final tx = _transactions[i];
if (tx.subType == TransactionSubType.cashFusion) {
if (fusions.isNotEmpty) {
final prevTime = DateTime.fromMillisecondsSinceEpoch(
fusions.last.timestamp * 1000);
final thisTime =
DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000);
if (prevTime.difference(thisTime).inMinutes > 30) {
_txns.add(FusionTxGroup(fusions));
fusions = [tx];
continue;
}
}
fusions.add(tx);
}
if (i + 1 < _transactions.length) {
final nextTx = _transactions[i + 1];
if (nextTx.subType != TransactionSubType.cashFusion &&
fusions.isNotEmpty) {
_txns.add(FusionTxGroup(fusions));
fusions = [];
}
}
if (tx.subType != TransactionSubType.cashFusion) {
_txns.add(tx);
}
}
return RefreshIndicator(
onRefresh: () async {
final managerProvider = ref
.read(walletsChangeNotifierProvider)
.getManagerProvider(widget.walletId);
if (!ref.read(managerProvider).isRefreshing) {
unawaited(ref.read(managerProvider).refresh());
}
},
child: Util.isDesktop
? ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) {
BorderRadius? radius;
if (_txns.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == _txns.length - 1) {
radius = _borderRadiusLast;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = _txns[index];
return TxListItem(
tx: tx,
coin: manager.coin,
radius: radius,
);
},
separatorBuilder: (context, index) {
return Container(
width: double.infinity,
height: 2,
color: Theme.of(context)
.extension<StackColors>()!
.background,
);
},
itemCount: _txns.length,
)
: ListView.builder(
itemCount: _txns.length,
itemBuilder: (context, index) {
BorderRadius? radius;
bool shouldWrap = false;
if (_txns.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == _txns.length - 1) {
radius = _borderRadiusLast;
shouldWrap = true;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = _txns[index];
if (shouldWrap) {
return Column(
children: [
TxListItem(
tx: tx,
coin: manager.coin,
radius: radius,
),
const SizedBox(
height: WalletView.navBarHeight + 14,
),
],
);
} else {
return TxListItem(
tx: tx,
coin: manager.coin,
radius: radius,
);
}
},
),
);
}
},
);
}
}

View file

@ -0,0 +1,179 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_tx_group_card.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/trade_card.dart';
import 'package:tuple/tuple.dart';
class TxListItem extends ConsumerWidget {
const TxListItem({
super.key,
required this.tx,
this.radius,
required this.coin,
}) : assert(tx is TransactionV2 || tx is FusionTxGroup);
final Object tx;
final BorderRadius? radius;
final Coin coin;
@override
Widget build(BuildContext context, WidgetRef ref) {
if (tx is TransactionV2) {
final _tx = tx as TransactionV2;
final matchingTrades = ref
.read(tradesServiceProvider)
.trades
.where((e) => e.payInTxid == _tx.txid || e.payOutTxid == _tx.txid);
if (_tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
final trade = matchingTrades.first;
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TransactionCardV2(
key: UniqueKey(),
transaction: _tx,
),
TradeCard(
key: Key(_tx.txid +
_tx.type.name +
_tx.hashCode.toString() +
trade.uuid), //
trade: trade,
onTap: () async {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: TradeDetailsView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
DesktopDialog(
maxHeight: null,
maxWidth: 580,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
bottom: 16,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade details",
style:
STextStyles.desktopH3(context),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
),
),
Flexible(
child: TradeDetailsView(
tradeId: trade.tradeId,
// TODO
// transactionIfSentFromStack: tx,
transactionIfSentFromStack: null,
walletName: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value
.getManager(_tx.walletId)
.walletName,
),
),
walletId: _tx.walletId,
),
),
],
),
),
const RouteSettings(
name: TradeDetailsView.routeName,
),
),
];
},
),
);
} else {
unawaited(
Navigator.of(context).pushNamed(
TradeDetailsView.routeName,
arguments: Tuple4(
trade.tradeId,
_tx,
_tx.walletId,
ref
.read(walletsChangeNotifierProvider)
.getManager(_tx.walletId)
.walletName,
),
),
);
}
},
)
],
),
);
} else {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: TransactionCardV2(
// this may mess with combined firo transactions
key: UniqueKey(),
transaction: _tx,
),
);
}
}
final group = tx as FusionTxGroup;
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: FusionTxGroupCard(
key: UniqueKey(),
group: group,
),
);
}
}

View file

@ -19,6 +19,7 @@ import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
import 'package:stackwallet/pages/cashfusion/cashfusion_view.dart';
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
@ -36,6 +37,8 @@ import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart';
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
import 'package:stackwallet/providers/providers.dart';
@ -84,6 +87,8 @@ import 'package:stackwallet/widgets/wallet_navigation_bar/components/wallet_navi
import 'package:stackwallet/widgets/wallet_navigation_bar/wallet_navigation_bar.dart';
import 'package:tuple/tuple.dart';
import '../../widgets/wallet_navigation_bar/components/icons/fusion_nav_icon.dart';
/// [eventBus] should only be set during testing
class WalletView extends ConsumerStatefulWidget {
const WalletView({
@ -839,7 +844,10 @@ class _WalletViewState extends ConsumerState<WalletView> {
text: "See all",
onTap: () {
Navigator.of(context).pushNamed(
AllTransactionsView.routeName,
coin == Coin.bitcoincash ||
coin == Coin.bitcoincashTestnet
? AllTransactionsV2View.routeName
: AllTransactionsView.routeName,
arguments: walletId,
);
},
@ -878,7 +886,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
0.0,
0.8,
1.0,
], // 10% purple, 80% transparent, 10% purple
],
).createShader(bounds);
},
child: Container(
@ -893,10 +901,16 @@ class _WalletViewState extends ConsumerState<WalletView> {
CrossAxisAlignment.stretch,
children: [
Expanded(
child: TransactionsList(
managerProvider: managerProvider,
walletId: walletId,
),
child: coin == Coin.bitcoincash ||
coin == Coin.bitcoincashTestnet
? TransactionsV2List(
walletId: widget.walletId,
)
: TransactionsList(
managerProvider:
managerProvider,
walletId: walletId,
),
),
],
),
@ -1101,6 +1115,22 @@ class _WalletViewState extends ConsumerState<WalletView> {
);
},
),
if (ref.watch(
walletsChangeNotifierProvider.select(
(value) =>
value.getManager(widget.walletId).hasFusionSupport,
),
))
WalletNavigationBarItemData(
label: "Fusion",
icon: const FusionNavIcon(),
onTap: () {
Navigator.of(context).pushNamed(
CashFusionView.routeName,
arguments: walletId,
);
},
),
],
),
],

View file

@ -210,30 +210,27 @@ class _DesktopAddressListState extends ConsumerState<DesktopAddressList> {
height: 20,
),
Expanded(
child: SingleChildScrollView(
child: RoundedWhiteContainer(
padding: EdgeInsets.zero,
child: ListView.separated(
shrinkWrap: true,
itemCount: ids.length,
separatorBuilder: (_, __) => Container(
height: 1,
color: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
),
itemBuilder: (_, index) => Padding(
padding: const EdgeInsets.all(4),
child: AddressCard(
key: Key("addressCardDesktop_key_${ids[index]}"),
walletId: widget.walletId,
addressId: ids[index],
coin: coin,
onPressed: () {
ref.read(desktopSelectedAddressId.state).state =
ids[index];
},
),
child: RoundedWhiteContainer(
padding: EdgeInsets.zero,
child: ListView.separated(
shrinkWrap: true,
itemCount: ids.length,
separatorBuilder: (_, __) => Container(
height: 1,
color: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
),
itemBuilder: (_, index) => Padding(
padding: const EdgeInsets.all(4),
child: AddressCard(
key: Key("addressCardDesktop_key_${ids[index]}"),
walletId: widget.walletId,
addressId: ids[index],
coin: coin,
onPressed: () {
ref.read(desktopSelectedAddressId.state).state = ids[index];
},
),
),
),

View file

@ -0,0 +1,586 @@
/*
* 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:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_native_splash/cli_commands.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/cashfusion/fusion_rounds_selection_sheet.dart';
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart';
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
class DesktopCashFusionView extends ConsumerStatefulWidget {
const DesktopCashFusionView({
super.key,
required this.walletId,
});
static const String routeName = "/desktopCashFusionView";
final String walletId;
@override
ConsumerState<DesktopCashFusionView> createState() => _DesktopCashFusion();
}
class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
late final TextEditingController serverController;
late final FocusNode serverFocusNode;
late final TextEditingController portController;
late final FocusNode portFocusNode;
late final TextEditingController fusionRoundController;
late final FocusNode fusionRoundFocusNode;
bool _enableStartButton = false;
bool _enableSSLCheckbox = false;
FusionOption _roundType = FusionOption.continuous;
Future<void> _startFusion() async {
final fusionWallet = ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.wallet as FusionWalletInterface;
try {
fusionWallet.uiState = ref.read(
fusionProgressUIStateProvider(widget.walletId),
);
} catch (e) {
if (!e.toString().contains(
"FusionProgressUIState was already set for ${widget.walletId}")) {
rethrow;
}
}
final int rounds = _roundType == FusionOption.continuous
? 0
: int.parse(fusionRoundController.text);
final newInfo = FusionInfo(
host: serverController.text,
port: int.parse(portController.text),
ssl: _enableSSLCheckbox,
rounds: rounds,
);
// update user prefs (persistent)
ref.read(prefsChangeNotifierProvider).fusionServerInfo = newInfo;
unawaited(
fusionWallet.fuse(
fusionInfo: newInfo,
),
);
await showDialog<void>(
context: context,
barrierDismissible: false,
builder: (context) {
return FusionDialogView(
walletId: widget.walletId,
);
},
);
}
@override
void initState() {
serverController = TextEditingController();
portController = TextEditingController();
fusionRoundController = TextEditingController();
serverFocusNode = FocusNode();
portFocusNode = FocusNode();
fusionRoundFocusNode = FocusNode();
final info = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
serverController.text = info.host;
portController.text = info.port.toString();
_enableSSLCheckbox = info.ssl;
_roundType =
info.rounds == 0 ? FusionOption.continuous : FusionOption.custom;
fusionRoundController.text = info.rounds.toString();
_enableStartButton =
serverController.text.isNotEmpty && portController.text.isNotEmpty;
super.initState();
}
@override
void dispose() {
serverController.dispose();
portController.dispose();
fusionRoundController.dispose();
serverFocusNode.dispose();
portFocusNode.dispose();
fusionRoundFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return DesktopScaffold(
appBar: DesktopAppBar(
background: Theme.of(context).extension<StackColors>()!.popupBG,
isCompactHeight: true,
useSpacers: false,
leading: Expanded(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
// const SizedBox(
// width: 32,
// ),
AppBarIconButton(
size: 32,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
shadows: const [],
icon: SvgPicture.asset(
Assets.svg.arrowLeft,
width: 18,
height: 18,
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
onPressed: Navigator.of(context).pop,
),
const SizedBox(
width: 15,
),
SvgPicture.asset(
Assets.svg.cashFusion,
width: 32,
height: 32,
),
const SizedBox(
width: 12,
),
Text(
"CashFusion",
style: STextStyles.desktopH3(context),
),
],
),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {},
child: Row(
children: [
SvgPicture.asset(
Assets.svg.circleQuestion,
color: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconBorder,
),
const SizedBox(
width: 8,
),
RichText(
text: TextSpan(
text: "What is CashFusion?",
style: STextStyles.richLink(context).copyWith(
fontSize: 16,
),
recognizer: TapGestureRecognizer()
..onTap = () {
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return DesktopDialog(
maxWidth: 580,
maxHeight: double.infinity,
child: Padding(
padding: const EdgeInsets.only(
top: 10,
left: 20,
bottom: 20,
right: 10,
),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(
"What is CashFusion?",
style: STextStyles.desktopH2(
context),
),
DesktopDialogCloseButton(
onPressedOverride: () =>
Navigator.of(context)
.pop(true),
),
],
),
const SizedBox(
height: 16,
),
Text(
"A fully decentralized privacy protocol that allows "
"anyone to create multi-party transactions with other "
"network participants. This process obscures your real "
"spending and makes it difficult for chain-analysis "
"companies to track your coins.",
style:
STextStyles.desktopTextMedium(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
),
),
],
),
),
);
},
);
},
),
),
],
),
),
),
],
),
),
),
),
body: Row(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 460,
child: RoundedWhiteContainer(
child: Row(
children: [
Text(
"CashFusion allows you to anonymize your BCH coins.",
style:
STextStyles.desktopTextExtraExtraSmall(context),
),
],
),
),
),
const SizedBox(
height: 24,
),
SizedBox(
width: 460,
child: RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Server settings",
style: STextStyles.desktopTextExtraExtraSmall(
context),
),
CustomTextButton(
text: "Default",
onTap: () {
const def = FusionInfo.DEFAULTS;
serverController.text = def.host;
portController.text = def.port.toString();
fusionRoundController.text =
def.rounds.toString();
_roundType = FusionOption.continuous;
setState(() {
_enableSSLCheckbox = def.ssl;
});
},
),
],
),
const SizedBox(
height: 12,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: false,
enableSuggestions: false,
controller: serverController,
focusNode: serverFocusNode,
onChanged: (value) {
setState(() {
_enableStartButton = value.isNotEmpty &&
portController.text.isNotEmpty &&
fusionRoundController.text.isNotEmpty;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Server",
serverFocusNode,
context,
desktopMed: true,
),
),
),
const SizedBox(
height: 12,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: false,
enableSuggestions: false,
controller: portController,
focusNode: portFocusNode,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
],
onChanged: (value) {
setState(() {
_enableStartButton = value.isNotEmpty &&
serverController.text.isNotEmpty &&
fusionRoundController.text.isNotEmpty;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Port",
portFocusNode,
context,
desktopMed: true,
),
),
),
const SizedBox(
height: 12,
),
GestureDetector(
onTap: () {
setState(() {
_enableSSLCheckbox = !_enableSSLCheckbox;
});
},
child: Container(
color: Colors.transparent,
child: Row(
children: [
SizedBox(
width: 20,
height: 20,
child: Checkbox(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
value: _enableSSLCheckbox,
onChanged: (newValue) {
setState(
() {
_enableSSLCheckbox =
!_enableSSLCheckbox;
},
);
},
),
),
const SizedBox(
width: 12,
),
Text(
"Use SSL",
style: STextStyles.itemSubtitle12(context),
),
],
),
),
),
const SizedBox(
height: 20,
),
Text(
"Rounds of fusion",
style:
STextStyles.desktopTextExtraExtraSmall(context),
),
const SizedBox(
height: 10,
),
DropdownButtonHideUnderline(
child: DropdownButton2<FusionOption>(
value: _roundType,
items: [
...FusionOption.values.map(
(e) => DropdownMenuItem(
value: e,
child: Text(
e.name.capitalize(),
style: STextStyles.smallMed14(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
),
),
],
onChanged: (value) {
if (value is FusionOption) {
setState(() {
_roundType = value;
});
}
},
isExpanded: true,
iconStyleData: IconStyleData(
icon: SvgPicture.asset(
Assets.svg.chevronDown,
width: 12,
height: 6,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
),
dropdownStyleData: DropdownStyleData(
offset: const Offset(0, -10),
elevation: 0,
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
menuItemStyleData: const MenuItemStyleData(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
),
),
),
if (_roundType == FusionOption.custom)
const SizedBox(
height: 10,
),
if (_roundType == FusionOption.custom)
SizedBox(
width: 460,
child: RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: false,
enableSuggestions: false,
controller: fusionRoundController,
focusNode: fusionRoundFocusNode,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
],
onChanged: (value) {
setState(() {
_enableStartButton = value
.isNotEmpty &&
serverController
.text.isNotEmpty &&
portController.text.isNotEmpty;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Number of fusions",
fusionRoundFocusNode,
context,
desktopMed: true,
).copyWith(
labelText:
"Enter number of fusions.."),
),
),
],
),
),
),
const SizedBox(
height: 20,
),
PrimaryButton(
label: "Start",
enabled: _enableStartButton,
onPressed: _startFusion,
),
],
),
),
),
],
),
),
],
),
);
}
}

View file

@ -0,0 +1,306 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_progress.dart';
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
enum CashFusionStatus { waiting, running, success, failed }
class CashFusionState {
final CashFusionStatus status;
final String? info;
CashFusionState({required this.status, this.info});
}
class FusionDialogView extends ConsumerStatefulWidget {
const FusionDialogView({
Key? key,
required this.walletId,
}) : super(key: key);
final String walletId;
@override
ConsumerState<FusionDialogView> createState() => _FusionDialogViewState();
}
class _FusionDialogViewState extends ConsumerState<FusionDialogView> {
Future<bool> _requestAndProcessCancel() async {
if (!ref.read(fusionProgressUIStateProvider(widget.walletId)).running) {
return true;
} else {
bool? shouldCancel = await showDialog<bool?>(
context: context,
barrierDismissible: false,
builder: (_) => DesktopDialog(
maxWidth: 580,
maxHeight: double.infinity,
child: Padding(
padding: const EdgeInsets.only(
left: 32,
right: 0,
top: 0,
bottom: 32,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Cancel fusion?",
style: STextStyles.desktopH3(context),
),
DesktopDialogCloseButton(
onPressedOverride: () => Navigator.of(context).pop(false),
),
],
),
Padding(
padding: const EdgeInsets.only(
left: 0,
right: 32,
top: 0,
bottom: 0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Do you really want to cancel the fusion process?",
style: STextStyles.smallMed14(context),
textAlign: TextAlign.left,
),
const SizedBox(height: 40),
Row(
children: [
Expanded(
child: SecondaryButton(
label: "No",
buttonHeight: ButtonHeight.l,
onPressed: () {
Navigator.of(context).pop(false);
},
),
),
const SizedBox(width: 16),
Expanded(
child: PrimaryButton(
label: "Yes",
buttonHeight: ButtonHeight.l,
onPressed: () {
Navigator.of(context).pop(true);
},
),
),
],
),
],
),
),
],
),
),
),
);
if (shouldCancel == true && mounted) {
final fusionWallet = ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.wallet as FusionWalletInterface;
await showLoading(
whileFuture: Future.wait([
fusionWallet.stop(),
Future<void>.delayed(const Duration(seconds: 2)),
]),
context: context,
isDesktop: true,
message: "Stopping fusion",
);
return true;
} else {
return false;
}
}
}
@override
Widget build(BuildContext context) {
final bool _succeeded =
ref.watch(fusionProgressUIStateProvider(widget.walletId)).succeeded;
final bool _failed =
ref.watch(fusionProgressUIStateProvider(widget.walletId)).failed;
final int _fusionRoundsCompleted = ref
.watch(fusionProgressUIStateProvider(widget.walletId))
.fusionRoundsCompleted;
return DesktopDialog(
maxHeight: 600,
child: SingleChildScrollView(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 32),
child: Text(
"Fusion progress",
style: STextStyles.desktopH2(context),
),
),
DesktopDialogCloseButton(
onPressedOverride: () async {
if (await _requestAndProcessCancel()) {
if (mounted) {
Navigator.of(context).pop();
}
}
},
),
],
),
Padding(
padding: const EdgeInsets.only(
top: 20,
left: 32,
right: 32,
bottom: 32,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_fusionRoundsCompleted > 0
? RoundedWhiteContainer(
child: Text(
"Fusion rounds completed: $_fusionRoundsCompleted",
style: STextStyles.w500_14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
textAlign: TextAlign.center,
),
)
: RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarBackError,
child: Text(
"Do not close this window. If you exit, "
"the process will be canceled.",
style: STextStyles.smallMed14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.snackBarTextError,
),
textAlign: TextAlign.center,
),
),
const SizedBox(
height: 20,
),
FusionProgress(
walletId: widget.walletId,
),
const SizedBox(
height: 12,
),
Row(
children: [
if (_succeeded)
Expanded(
child: PrimaryButton(
buttonHeight: ButtonHeight.m,
label: "Fuse again",
onPressed: _fuseAgain,
),
),
if (_succeeded)
const SizedBox(
width: 16,
),
if (_failed)
Expanded(
child: PrimaryButton(
buttonHeight: ButtonHeight.m,
label: "Try again",
onPressed: _fuseAgain,
),
),
if (_failed)
const SizedBox(
width: 16,
),
if (!_succeeded && !_failed) const Spacer(),
if (!_succeeded && !_failed)
const SizedBox(
width: 16,
),
Expanded(
child: SecondaryButton(
buttonHeight: ButtonHeight.m,
enabled: true,
label: "Cancel",
onPressed: () async {
if (await _requestAndProcessCancel()) {
if (mounted) {
Navigator.of(context).pop();
}
}
},
),
),
],
),
],
),
),
],
),
),
);
}
/// Fuse again.
void _fuseAgain() async {
final fusionWallet = ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.wallet as FusionWalletInterface;
final fusionInfo = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
try {
fusionWallet.uiState = ref.read(
fusionProgressUIStateProvider(widget.walletId),
);
} catch (e) {
if (!e.toString().contains(
"FusionProgressUIState was already set for ${widget.walletId}")) {
rethrow;
}
}
unawaited(fusionWallet.fuse(fusionInfo: fusionInfo));
}
}

View file

@ -0,0 +1,149 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_item_card.dart';
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart';
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
class FusionProgress extends ConsumerWidget {
const FusionProgress({super.key, required this.walletId});
final String walletId;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_ProgressItem(
iconAsset: Assets.svg.node,
label: "Connecting to server",
state: ref.watch(fusionProgressUIStateProvider(walletId)
.select((value) => value.connecting))),
const SizedBox(
height: 12,
),
_ProgressItem(
iconAsset: Assets.svg.upFromLine,
label: "Allocating outputs",
state: ref.watch(fusionProgressUIStateProvider(walletId)
.select((value) => value.outputs))),
const SizedBox(
height: 12,
),
_ProgressItem(
iconAsset: Assets.svg.peers,
label: "Waiting for peers",
state: ref.watch(fusionProgressUIStateProvider(walletId)
.select((value) => value.peers))),
const SizedBox(
height: 12,
),
_ProgressItem(
iconAsset: Assets.svg.fusing,
label: "Fusing",
state: ref.watch(fusionProgressUIStateProvider(walletId)
.select((value) => value.fusing))),
const SizedBox(
height: 12,
),
_ProgressItem(
iconAsset: Assets.svg.checkCircle,
label: "Complete",
state: ref.watch(fusionProgressUIStateProvider(walletId)
.select((value) => value.complete))),
],
);
}
}
class _ProgressItem extends StatelessWidget {
const _ProgressItem({
super.key,
required this.iconAsset,
required this.label,
required this.state,
});
final String iconAsset;
final String label;
final CashFusionState state;
Widget _getIconForState(CashFusionStatus state, BuildContext context) {
switch (state) {
case CashFusionStatus.waiting:
return SvgPicture.asset(
Assets.svg.loader,
color:
Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
);
case CashFusionStatus.running:
return SvgPicture.asset(
Assets.svg.loader,
color: Theme.of(context).extension<StackColors>()!.accentColorGreen,
);
case CashFusionStatus.success:
return SvgPicture.asset(
Assets.svg.checkCircle,
color: Theme.of(context).extension<StackColors>()!.accentColorGreen,
);
case CashFusionStatus.failed:
return SvgPicture.asset(
Assets.svg.circleAlert,
color: Theme.of(context).extension<StackColors>()!.textError,
);
}
}
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: Util.isDesktop,
builder: (child) => RoundedContainer(
padding: EdgeInsets.zero,
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderColor: Theme.of(context).extension<StackColors>()!.background,
child: child,
),
child: RestoringItemCard(
left: SizedBox(
width: 32,
height: 32,
child: RoundedContainer(
padding: const EdgeInsets.all(0),
color:
Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
child: Center(
child: SvgPicture.asset(
iconAsset,
width: 18,
height: 18,
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
),
),
right: SizedBox(
width: 20,
height: 20,
child: _getIconForState(state.status, context),
),
title: label,
subTitle: state.info != null && state.info!.isNotEmpty
? Text(
state.info!,
style: STextStyles.w500_12(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textError,
),
)
: null,
),
);
}
}

View file

@ -218,7 +218,10 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
onTap: () {
Navigator.of(context).pushNamed(
AllTransactionsView.routeName,
arguments: widget.walletId,
arguments: (
walletId: widget.walletId,
isTokens: true,
),
);
},
),

View file

@ -21,6 +21,8 @@ import 'package:stackwallet/pages/special/firo_rescan_recovery_error_dialog.dart
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart';
@ -445,7 +447,7 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
.getManager(widget.walletId)
.hasTokenSupport))
? "Tokens"
: "Recent transactions",
: "Recent activity",
style: STextStyles.desktopTextExtraSmall(context)
.copyWith(
color: Theme.of(context)
@ -460,30 +462,33 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
.hasTokenSupport))
? "Edit"
: "See all",
onTap: ref.watch(walletsChangeNotifierProvider.select(
(value) => value
.getManager(widget.walletId)
.hasTokenSupport))
? () async {
final result = await showDialog<int?>(
context: context,
builder: (context) => EditWalletTokensView(
walletId: widget.walletId,
isDesktopPopup: true,
),
);
onTap: () async {
if (ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.hasTokenSupport) {
final result = await showDialog<int?>(
context: context,
builder: (context) => EditWalletTokensView(
walletId: widget.walletId,
isDesktopPopup: true,
),
);
if (result == 42) {
// wallet tokens were edited so update ui
setState(() {});
}
}
: () {
Navigator.of(context).pushNamed(
AllTransactionsView.routeName,
arguments: widget.walletId,
);
},
if (result == 42) {
// wallet tokens were edited so update ui
setState(() {});
}
} else {
await Navigator.of(context).pushNamed(
coin == Coin.bitcoincash ||
coin == Coin.bitcoincashTestnet
? AllTransactionsV2View.routeName
: AllTransactionsView.routeName,
arguments: widget.walletId,
);
}
},
),
],
),
@ -514,13 +519,18 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
? MyTokensView(
walletId: widget.walletId,
)
: TransactionsList(
managerProvider: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManagerProvider(
widget.walletId))),
walletId: widget.walletId,
),
: coin == Coin.bitcoincash ||
coin == Coin.bitcoincashTestnet
? TransactionsV2List(
walletId: widget.walletId,
)
: TransactionsList(
managerProvider: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManagerProvider(
widget.walletId))),
walletId: widget.walletId,
),
),
],
),

View file

@ -19,6 +19,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/monkey/monkey_view.dart';
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
import 'package:stackwallet/pages_desktop_specific/cashfusion/desktop_cashfusion_view.dart';
import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
@ -84,6 +85,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
onWhirlpoolPressed: _onWhirlpoolPressed,
onOrdinalsPressed: _onOrdinalsPressed,
onMonkeyPressed: _onMonkeyPressed,
onFusionPressed: _onFusionPressed,
),
);
}
@ -335,6 +337,15 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
);
}
void _onFusionPressed() {
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context).pushNamed(
DesktopCashFusionView.routeName,
arguments: widget.walletId,
);
}
@override
Widget build(BuildContext context) {
final manager = ref.watch(
@ -354,7 +365,9 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
manager.coin == Coin.firoTestNet ||
manager.hasWhirlpoolSupport ||
manager.coin == Coin.banano ||
manager.hasOrdinalsSupport;
manager.hasOrdinalsSupport ||
manager.hasFusionSupport;
return Row(
children: [
if (Constants.enableExchange)

View file

@ -31,6 +31,7 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget {
required this.onWhirlpoolPressed,
required this.onOrdinalsPressed,
required this.onMonkeyPressed,
required this.onFusionPressed,
}) : super(key: key);
final String walletId;
@ -40,6 +41,7 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget {
final VoidCallback? onWhirlpoolPressed;
final VoidCallback? onOrdinalsPressed;
final VoidCallback? onMonkeyPressed;
final VoidCallback? onFusionPressed;
@override
ConsumerState<MoreFeaturesDialog> createState() => _MoreFeaturesDialogState();
@ -121,6 +123,13 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
iconAsset: Assets.svg.monkey,
onPressed: () => widget.onMonkeyPressed?.call(),
),
if (manager.hasFusionSupport)
_MoreFeaturesItem(
label: "CashFusion",
detail: "Decentralized Bitcoin Cash mixing protocol",
iconAsset: Assets.svg.cashFusion,
onPressed: () => widget.onFusionPressed?.call(),
),
const SizedBox(
height: 28,
),

View file

@ -215,40 +215,37 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
return DesktopDialog(
maxWidth: 580,
maxHeight: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Padding(
padding:
const EdgeInsets.only(
left: 32,
),
child: Text(
child: Padding(
padding: const EdgeInsets.only(
top: 10,
left: 20,
bottom: 20,
right: 10,
),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(
"What is Tor?",
style:
STextStyles.desktopH2(
context),
),
),
DesktopDialogCloseButton(
onPressedOverride: () =>
Navigator.of(context)
.pop(true),
),
],
),
Padding(
padding: const EdgeInsets.only(
top: 12,
left: 32,
bottom: 32,
right: 32,
DesktopDialogCloseButton(
onPressedOverride: () =>
Navigator.of(context)
.pop(true),
),
],
),
child: Text(
const SizedBox(
height: 16,
),
Text(
"Short for \"The Onion Router\", is an open-source software that enables internet communication"
" to remain anonymous by routing internet traffic through a series of layered nodes,"
" to obscure the origin and destination of data.",
@ -261,8 +258,8 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
.textDark3,
),
),
),
],
],
),
),
);
},
@ -287,43 +284,49 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Tor killswitch",
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark),
),
const SizedBox(
height: 8,
),
RichText(
textAlign: TextAlign.start,
text: TextSpan(
text: "What is Tor killswitch?",
style: STextStyles.richLink(context).copyWith(
fontSize: 14,
),
recognizer: TapGestureRecognizer()
..onTap = () {
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return DesktopDialog(
maxWidth: 580,
maxHeight: double.infinity,
child: Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Tor killswitch",
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark),
),
const SizedBox(
height: 8,
),
RichText(
textAlign: TextAlign.start,
text: TextSpan(
text: "What is Tor killswitch?",
style: STextStyles.richLink(context).copyWith(
fontSize: 14,
),
recognizer: TapGestureRecognizer()
..onTap = () {
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return DesktopDialog(
maxWidth: 580,
maxHeight: double.infinity,
child: Padding(
padding: const EdgeInsets.only(
top: 10,
left: 20,
bottom: 20,
right: 10,
),
child: Column(
children: [
Row(
@ -331,16 +334,11 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
MainAxisAlignment
.spaceBetween,
children: [
Padding(
padding:
const EdgeInsets.only(
left: 32,
),
child: Text(
"What is Tor killswitch?",
style: STextStyles
.desktopH2(context),
),
Text(
"What is Tor killswitch?",
style:
STextStyles.desktopH2(
context),
),
DesktopDialogCloseButton(
onPressedOverride: () =>
@ -349,44 +347,36 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
),
],
),
Padding(
padding:
const EdgeInsets.only(
top: 12,
left: 32,
bottom: 32,
right: 32,
),
child: Text(
"A security feature that protects your information from accidental exposure by"
" disconnecting your device from the Tor network if the"
" connection is disrupted or compromised.",
style: STextStyles
.desktopTextMedium(
context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.textDark3,
),
const SizedBox(
height: 16,
),
Text(
"A security feature that protects your information from accidental exposure by"
" disconnecting your device from the Tor network if the"
" connection is disrupted or compromised.",
style: STextStyles
.desktopTextMedium(
context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.textDark3,
),
),
],
),
);
},
);
},
),
),
);
},
);
},
),
],
),
],
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: SizedBox(
),
],
),
const Spacer(),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
@ -401,13 +391,13 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
},
),
),
),
],
const SizedBox(
height: 10,
),
],
),
),
),
const SizedBox(
height: 10,
),
],
),
),

View file

@ -0,0 +1,19 @@
/*
* 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 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/fusion_progress_ui_state.dart';
final fusionProgressUIStateProvider =
ChangeNotifierProvider.family<FusionProgressUIState, String>(
(ref, walletId) {
return FusionProgressUIState();
},
);

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_
import 'package:stackwallet/models/buy/response_objects/quote.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/isar/ordinal.dart';
@ -44,6 +45,8 @@ import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart';
import 'package:stackwallet/pages/buy_view/buy_view.dart';
import 'package:stackwallet/pages/cashfusion/cashfusion_view.dart';
import 'package:stackwallet/pages/cashfusion/fusion_progress_view.dart';
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
import 'package:stackwallet/pages/coin_control/utxo_details_view.dart';
import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
@ -129,11 +132,15 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_group_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/pages/wallets_view/wallets_overview.dart';
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
import 'package:stackwallet/pages_desktop_specific/address_book_view/desktop_address_book.dart';
import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart';
import 'package:stackwallet/pages_desktop_specific/cashfusion/desktop_cashfusion_view.dart';
import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_view.dart';
// import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_buys_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.dart';
@ -579,6 +586,48 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case CashFusionView.routeName:
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => CashFusionView(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case FusionProgressView.routeName:
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => FusionProgressView(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case DesktopCashFusionView.routeName:
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => DesktopCashFusionView(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case GlobalSettingsView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
@ -1214,7 +1263,55 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case TransactionV2DetailsView.routeName:
if (args is ({TransactionV2 tx, Coin coin, String walletId})) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => TransactionV2DetailsView(
transaction: args.tx,
coin: args.coin,
walletId: args.walletId,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case FusionGroupDetailsView.routeName:
if (args is ({
List<TransactionV2> transactions,
Coin coin,
String walletId
})) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => FusionGroupDetailsView(
transactions: args.transactions,
coin: args.coin,
walletId: args.walletId,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case AllTransactionsView.routeName:
if (args is ({String walletId, bool isTokens})) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => AllTransactionsView(
walletId: args.walletId,
isTokens: args.isTokens,
),
settings: RouteSettings(
name: settings.name,
),
);
}
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
@ -1228,6 +1325,20 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case AllTransactionsV2View.routeName:
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => AllTransactionsV2View(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case TransactionSearchFilterView.routeName:
if (args is Coin) {
return getRoute(
@ -1992,6 +2103,17 @@ class RouteGenerator {
name: settings.name,
),
);
} else if (args is ({String walletId, bool popPrevious})) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => TokenView(
walletId: args.walletId,
popPrevious: args.popPrevious,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");

View file

@ -0,0 +1,61 @@
import 'dart:typed_data';
import 'package:bitcoindart/src/utils/constants/op.dart' as op;
import 'package:bitcoindart/src/utils/script.dart' as bscript;
import 'package:stackwallet/utilities/extensions/impl/string.dart';
abstract final class BchUtils {
static const FUSE_ID = 'FUZ\x00';
static bool isSLP(Uint8List scriptPubKey) {
const id = [83, 76, 80, 0]; // 'SLP\x00'
final decompiled = bscript.decompile(scriptPubKey);
if (decompiled != null &&
decompiled.length > 1 &&
decompiled.first == op.OPS["OP_RETURN"]) {
final _id = decompiled[1];
if (_id is List<int> && _id.length == id.length) {
for (int i = 0; i < id.length; i++) {
if (_id[i] != id[i]) {
return false;
}
}
// lists match!
return true;
}
}
return false;
}
static bool isFUZE(Uint8List scriptPubKey) {
final id = FUSE_ID.toUint8ListFromUtf8;
final decompiled = bscript.decompile(scriptPubKey);
if (decompiled != null &&
decompiled.length > 2 &&
decompiled.first == op.OPS["OP_RETURN"]) {
// check session hash length. Should be 32 bytes
final sessionHash = decompiled[2];
if (!(sessionHash is List<int> && sessionHash.length == 32)) {
return false;
}
final _id = decompiled[1];
if (_id is List<int> && _id.length == id.length) {
for (int i = 0; i < id.length; i++) {
if (_id[i] != id[i]) {
return false;
}
}
// lists match!
return true;
}
}
return false;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,317 @@
import 'dart:typed_data';
// The Structure enum
enum Structure {
HasAmount,
HasNFT,
HasCommitmentLength,
}
// The Capability enum
enum Capability {
NoCapability,
Mutable,
Minting,
}
// Used as a "custom tuple" for the supporting functions of readCompactSize to return
// a convenient data structure.
class CompactSizeResult {
final int amount;
final int bytesRead;
CompactSizeResult({required this.amount, required this.bytesRead});
}
// This class is a data structure representing the entire output, comprised of both the
// normal Script pub key and the token data. We get this after we parse/unwrap the raw
// output.
class ParsedOutput {
List<int>? script_pub_key;
TokenOutputData? token_data;
ParsedOutput({this.script_pub_key, this.token_data});
}
// This is equivalent to the Electron Cash python's "OutputData" in token.py.
// Named here specifically as "TokenOutputData" to reflect the fact that
// it is specifically for tokens, whereas the other class ParsedOutput represents
// the entire output, comprised of both the normal Script pub key and the token data.
class TokenOutputData {
Uint8List? id;
int? amount;
Uint8List? commitment;
Uint8List? bitfield; // A byte (Uint8List of length 1)
// Constructor
TokenOutputData({
this.id,
this.amount,
this.commitment,
this.bitfield,
});
// Get the "capability", see Capability enum.
int getCapability() {
if (bitfield != null) {
return bitfield![0] & 0x0f;
}
return 0;
}
// functions to return attributes of the token bitfield.
bool hasCommitmentLength() {
if (bitfield != null) {
return (bitfield![0] & 0x40) != 0;
}
return false;
}
bool hasAmount() {
if (bitfield != null) {
return (bitfield![0] & 0x10) != 0;
}
return false;
}
bool hasNFT() {
if (bitfield != null) {
return (bitfield![0] & 0x20) != 0;
}
return false;
}
// Functions to return specific attributes based on the Capability.
bool isMintingNFT() {
return hasNFT() && getCapability() == Capability.Minting.index;
}
bool isMutableNFT() {
return hasNFT() && getCapability() == Capability.Mutable.index;
}
bool isImmutableNFT() {
return hasNFT() && getCapability() == Capability.NoCapability.index;
}
// This function validates if the bitfield makes sense or violates known rules/logic.
bool isValidBitfield() {
if (bitfield == null) {
return false;
}
int s = bitfield![0] & 0xf0;
if (s >= 0x80 || s == 0x00) {
return false;
}
if (bitfield![0] & 0x0f > 2) {
return false;
}
if (!hasNFT() && !hasAmount()) {
return false;
}
if (!hasNFT() && (bitfield![0] & 0x0f) != 0) {
return false;
}
if (!hasNFT() && hasCommitmentLength()) {
return false;
}
return true;
}
// The serialze and deserialize functions are the nuts and bolts of how we unpack
// and pack outputs. These are called by the wrap and unwrap functions.
int deserialize(Uint8List buffer, {int cursor = 0, bool strict = false}) {
try {
this.id = buffer.sublist(cursor, cursor + 32);
cursor += 32;
this.bitfield = Uint8List.fromList([buffer[cursor]]);
cursor += 1;
if (this.hasCommitmentLength()) {
// Read the first byte to determine the length of the commitment data
int commitmentLength = buffer[cursor];
// Move cursor to the next byte
cursor += 1;
// Read 'commitmentLength' bytes for the commitment data
this.commitment = buffer.sublist(cursor, cursor + commitmentLength);
// Adjust the cursor by the length of the commitment data
cursor += commitmentLength;
} else {
this.commitment = null;
}
if (this.hasAmount()) {
// Use readCompactSize that returns CompactSizeResult
CompactSizeResult result =
readCompactSize(buffer, cursor, strict: strict);
this.amount = result.amount;
cursor += result.bytesRead;
} else {
this.amount = 0;
}
if (!this.isValidBitfield() ||
(this.hasAmount() && this.amount == 0) ||
(this.amount! < 0 || this.amount! > (1 << 63) - 1) ||
(this.hasCommitmentLength() && this.commitment!.isEmpty) ||
(this.amount! == 0 && !this.hasNFT())) {
throw Exception('Unable to parse token data or token data is invalid');
}
return cursor; // Return the number of bytes read
} catch (e) {
throw Exception('Deserialization failed: $e');
}
}
// Serialize method
Uint8List serialize() {
var buffer = BytesBuilder();
// write ID and bitfield
buffer.add(this.id!);
buffer.addByte(this.bitfield![0]);
// Write optional fields
if (this.hasCommitmentLength()) {
buffer.add(this.commitment!);
}
if (this.hasAmount()) {
List<int> compactSizeBytes = writeCompactSize(this.amount!);
buffer.add(compactSizeBytes);
}
return buffer.toBytes();
}
} //END OF OUTPUTDATA CLASS
// The prefix byte is specified by the CashTokens spec.
final List<int> PREFIX_BYTE = [0xef];
// This function wraps a "normal" output together with token data.
ParsedOutput wrap_spk(TokenOutputData? token_data, Uint8List script_pub_key) {
ParsedOutput parsedOutput = ParsedOutput();
if (token_data == null) {
parsedOutput.script_pub_key = script_pub_key;
return parsedOutput;
}
final buf = BytesBuilder();
buf.add(PREFIX_BYTE);
buf.add(token_data.serialize());
buf.add(script_pub_key);
parsedOutput.script_pub_key = buf.toBytes();
parsedOutput.token_data = token_data;
return parsedOutput;
}
// This function unwraps any output, either "normal" (containing no token data)
// or an output with token data. If no token data, just the output is returned,
// and if token data exists, both the output and token data are returned.
// Note that the data returend in both cases in of ParsedOutput type, which
// holds both the script pub key and token data.
ParsedOutput unwrap_spk(Uint8List wrapped_spk) {
ParsedOutput parsedOutput = ParsedOutput();
if (wrapped_spk.isEmpty || wrapped_spk[0] != PREFIX_BYTE[0]) {
parsedOutput.script_pub_key = wrapped_spk;
return parsedOutput;
}
int read_cursor = 1; // Start after the PREFIX_BYTE
TokenOutputData token_data = TokenOutputData();
Uint8List wrapped_spk_without_prefix_byte;
try {
// Deserialize updates read_cursor by the number of bytes read
wrapped_spk_without_prefix_byte = wrapped_spk.sublist(read_cursor);
int bytesRead = token_data.deserialize(wrapped_spk_without_prefix_byte);
read_cursor += bytesRead;
parsedOutput.token_data = token_data;
parsedOutput.script_pub_key = wrapped_spk.sublist(read_cursor);
} catch (e) {
// If unable to deserialize, return all bytes as the full scriptPubKey
parsedOutput.script_pub_key = wrapped_spk;
}
return parsedOutput;
}
// HELPER FUNCTIONS
//These are part of a "length value " scheme where the length (and endianness) are given first
// and inform the program of how many bytes to grab next. These are in turn used by the serialize
// and deserialize functions.-
CompactSizeResult readCompactSize(
Uint8List buffer,
int cursor, {
bool strict = false,
}) {
int bytesRead = 0; // Variable to count bytes read
int val;
try {
val = buffer[cursor];
cursor += 1;
bytesRead += 1;
int minVal;
if (val == 253) {
val = buffer.buffer.asByteData().getUint16(cursor, Endian.little);
cursor += 2;
bytesRead += 2;
minVal = 253;
} else if (val == 254) {
val = buffer.buffer.asByteData().getUint32(cursor, Endian.little);
cursor += 4;
bytesRead += 4;
minVal = 1 << 16;
} else if (val == 255) {
val = buffer.buffer.asByteData().getInt64(cursor, Endian.little);
cursor += 8;
bytesRead += 8;
minVal = 1 << 32;
} else {
minVal = 0;
}
if (strict && val < minVal) {
throw Exception("CompactSize is not minimally encoded");
}
return CompactSizeResult(amount: val, bytesRead: bytesRead);
} catch (e) {
throw Exception("attempt to read past end of buffer");
}
}
Uint8List writeCompactSize(int size) {
var buffer = ByteData(9); // Maximum needed size for compact size is 9 bytes
if (size < 0) {
throw Exception("attempt to write size < 0");
} else if (size < 253) {
return Uint8List.fromList([size]);
} else if (size < (1 << 16)) {
buffer.setUint8(0, 253);
buffer.setUint16(1, size, Endian.little);
return buffer.buffer.asUint8List(0, 3);
} else if (size < (1 << 32)) {
buffer.setUint8(0, 254);
buffer.setUint32(1, size, Endian.little);
return buffer.buffer.asUint8List(0, 5);
} else if (size < (1 << 64)) {
buffer.setUint8(0, 255);
buffer.setInt64(1, size, Endian.little);
return buffer.buffer.asUint8List(0, 9);
} else {
throw Exception("Size too large to represent as CompactSize");
}
}

View file

@ -227,7 +227,9 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
Future<void> updateBalance() async {
web3.Web3Client client = getEthClient();
web3.EtherAmount ethBalance = await client.getBalance(_credentials.address);
final address = web3.EthereumAddress.fromHex(await currentReceivingAddress);
web3.EtherAmount ethBalance = await client.getBalance(address);
_balance = Balance(
total: Amount(
rawValue: ethBalance.getInWei,

View file

@ -21,6 +21,7 @@ import 'package:stackwallet/services/event_bus/events/global/node_connection_sta
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
@ -253,6 +254,8 @@ class Manager with ChangeNotifier {
bool get hasWhirlpoolSupport => false;
bool get hasFusionSupport => _currentWallet is FusionWalletInterface;
int get rescanOnOpenVersion =>
DB.instance.get<dynamic>(
boxName: DB.boxNameDBInfo,

View file

@ -75,7 +75,7 @@ abstract class EthereumAPI {
for (final map in list!) {
final txn = EthTxDTO.fromMap(Map<String, dynamic>.from(map as Map));
if (txn.hasToken == 0 || includeTokens) {
if (!txn.hasToken || includeTokens) {
txns.add(txn);
}
}

View file

@ -468,7 +468,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache {
}
final response2 = await EthereumAPI.getEthTokenTransactionsByTxids(
response.value!.map((e) => e.transactionHash).toList(),
response.value!.map((e) => e.transactionHash).toSet().toList(),
);
if (response2.value == null) {
@ -477,14 +477,25 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache {
}
final List<Tuple2<EthTokenTxDto, EthTokenTxExtraDTO>> data = [];
for (final tokenDto in response.value!) {
data.add(
Tuple2(
tokenDto,
response2.value!.firstWhere(
(e) => e.hash == tokenDto.transactionHash,
try {
final txExtra = response2.value!.firstWhere(
(e) => e.hash == tokenDto.transactionHash,
);
data.add(
Tuple2(
tokenDto,
txExtra,
),
),
);
);
} catch (_) {
// Server indexing failed for some reason. Instead of hard crashing or
// showing no transactions we just skip it here. Not ideal but better
// than nothing showing up
Logging.instance.log(
"Server error: Transaction ${tokenDto.transactionHash} not found.",
level: LogLevel.Error,
);
}
}
final List<Tuple2<Transaction, Address?>> txnsData = [];

View file

@ -0,0 +1,71 @@
import 'dart:io';
import 'package:stackwallet/utilities/logger.dart';
import 'package:tor_ffi_plugin/tor_ffi_plugin.dart';
class FusionTorService {
Tor? _tor;
String? _torDataDirPath;
TorStatus get status => _tor!.status;
/// Singleton instance of the TorService.
///
/// Use this to access the TorService and its properties.
static final sharedInstance = FusionTorService._();
// private constructor for singleton
FusionTorService._();
/// Getter for the proxyInfo.
///
/// Throws if Tor is not connected.
({
InternetAddress host,
int port,
}) getProxyInfo() {
try {
return (
host: InternetAddress.loopbackIPv4,
port: _tor!.port,
);
} catch (_) {
throw Exception("Tor proxy info fetched while not connected!");
}
}
/// Initialize the tor ffi lib instance if it hasn't already been set. Nothing
/// changes if _tor is already been set.
void init({
required String torDataDirPath,
Tor? mockableOverride,
}) {
_tor ??= mockableOverride ?? Tor.instance;
_torDataDirPath ??= torDataDirPath;
}
/// Start the Tor service.
///
/// This will start the Tor service and establish a Tor circuit.
///
/// Throws an exception if the Tor library was not inited or if the Tor
/// service fails to start.
///
/// Returns a Future that completes when the Tor service has started.
Future<void> start() async {
if (_tor == null || _torDataDirPath == null) {
throw Exception("FusionTorService.init has not been called!");
}
// Start the Tor service.
try {
await _tor!.start(torDataDirPath: _torDataDirPath!);
} catch (e, s) {
Logging.instance.log(
"FusionTorService.start failed: $e\n$s",
level: LogLevel.Warning,
);
rethrow;
}
}
}

View file

@ -12,13 +12,114 @@ import 'dart:convert';
import 'package:bip47/src/util.dart';
import 'package:decimal/decimal.dart';
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/util.dart' as util;
import 'package:tuple/tuple.dart';
class TT with ElectrumXParsing {
//
}
mixin ElectrumXParsing {
Future<TransactionV2> getTransaction(
String txHash,
Coin coin,
String walletId,
CachedElectrumX cachedElectrumX, [
String? debugTitle,
]) async {
final jsonTx = await cachedElectrumX.getTransaction(
txHash: txHash,
coin: coin,
);
print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
util.Util.printJson(jsonTx, debugTitle);
print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
// parse inputs
final List<InputV2> inputs = [];
for (final jsonInput in jsonTx["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) {
final txid = map["txid"] as String;
final vout = map["vout"] as int;
final inputTx =
await cachedElectrumX.getTransaction(txHash: txid, coin: coin);
final prevOutJson = Map<String, dynamic>.from(
(inputTx["vout"] as List).firstWhere((e) => e["n"] == vout) as Map);
final prevOut = OutputV2.fromElectrumXJson(
prevOutJson,
decimalPlaces: coin.decimals,
walletOwns: false,
);
outpoint = OutpointV2.isarCantDoRequiredInDefaultConstructor(
txid: txid,
vout: vout,
);
valueStringSats = prevOut.valueStringSats;
addresses.addAll(prevOut.addresses);
}
final input = InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: map["scriptSig"]?["hex"] 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?,
walletOwns: false,
);
inputs.add(input);
}
// parse outputs
final List<OutputV2> outputs = [];
for (final outputJson in jsonTx["vout"] as List) {
final output = OutputV2.fromElectrumXJson(
Map<String, dynamic>.from(outputJson as Map),
decimalPlaces: coin.decimals,
walletOwns: false,
);
outputs.add(output);
}
return TransactionV2(
walletId: walletId,
blockHash: jsonTx["blockhash"] as String?,
hash: jsonTx["hash"] as String,
txid: jsonTx["txid"] as String,
height: jsonTx["height"] as int?,
version: jsonTx["version"] as int,
timestamp: jsonTx["blocktime"] as int? ??
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(inputs),
outputs: List.unmodifiable(outputs),
subType: TransactionSubType.none,
type: TransactionType.unknown,
);
}
Future<Tuple2<Transaction, Address>> parseTransaction(
Map<String, dynamic> txData,
dynamic electrumxClient,

View file

@ -0,0 +1,744 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:bitcoindart/bitcoindart.dart' as btcdart;
import 'package:flutter/foundation.dart';
import 'package:fusiondart/fusiondart.dart' as fusion;
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
import 'package:stackwallet/models/fusion_progress_ui_state.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart';
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
import 'package:stackwallet/services/fusion_tor_service.dart';
import 'package:stackwallet/utilities/bip32_utils.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/stack_file_system.dart';
const String kReservedFusionAddress = "reserved_fusion_address";
class FusionInfo {
final String host;
final int port;
final bool ssl;
/// set to 0 for continuous
final int rounds;
const FusionInfo({
required this.host,
required this.port,
required this.ssl,
required this.rounds,
}) : assert(rounds >= 0);
static const DEFAULTS = FusionInfo(
host: "fusion.servo.cash",
port: 8789,
ssl: true,
// host: "cashfusion.stackwallet.com",
// port: 8787,
// ssl: false,
rounds: 0, // 0 is continuous
);
factory FusionInfo.fromJsonString(String jsonString) {
final json = jsonDecode(jsonString);
return FusionInfo(
host: json['host'] as String,
port: json['port'] as int,
ssl: json['ssl'] as bool,
rounds: json['rounds'] as int,
);
}
String toJsonString() {
return jsonEncode({
'host': host,
'port': port,
'ssl': ssl,
'rounds': rounds,
});
}
@override
String toString() {
return toJsonString();
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is FusionInfo &&
other.host == host &&
other.port == port &&
other.ssl == ssl &&
other.rounds == rounds;
}
@override
int get hashCode {
return Object.hash(
host.hashCode,
port.hashCode,
ssl.hashCode,
rounds.hashCode,
);
}
}
/// A mixin for the BitcoinCashWallet class that adds CashFusion functionality.
mixin FusionWalletInterface {
// Passed in wallet data.
late final String _walletId;
late final Coin _coin;
late final MainDB _db;
late final FusionTorService _torService;
late final Future<String?> _mnemonic;
late final Future<String?> _mnemonicPassphrase;
late final btcdart.NetworkType _network;
final _prefs = Prefs.instance;
// setting values on this should notify any listeners (the GUI)
FusionProgressUIState? _uiState;
FusionProgressUIState get uiState {
if (_uiState == null) {
throw Exception("FusionProgressUIState has not been set for $_walletId");
}
return _uiState!;
}
set uiState(FusionProgressUIState state) {
if (_uiState != null) {
throw Exception("FusionProgressUIState was already set for $_walletId");
}
_uiState = state;
}
// Passed in wallet functions.
late final Future<List<Address>> Function({int numberOfAddresses})
_getNextUnusedChangeAddresses;
late final CachedElectrumX Function() _getWalletCachedElectrumX;
late final Future<int> Function() _getChainHeight;
late final Future<void> Function() _updateWalletUTXOS;
late final String Function(String bchAddress, btcdart.NetworkType network)
_convertToScriptHash;
// Fusion object.
fusion.Fusion? _mainFusionObject;
bool _stopRequested = false;
/// An int storing the number of successfully completed fusion rounds.
int _completedFuseCount = 0;
/// An int storing the number of failed fusion rounds.
int _failedFuseCount = 0;
/// The maximum number of consecutive failed fusion rounds before stopping.
int get maxFailedFuseCount => 5;
/// Initializes the FusionWalletInterface mixin.
///
/// This function must be called before any other functions in this mixin.
Future<void> initFusionInterface({
required String walletId,
required Coin coin,
required MainDB db,
required Future<List<Address>> Function({int numberOfAddresses})
getNextUnusedChangeAddress,
required CachedElectrumX Function() getWalletCachedElectrumX,
required Future<int> Function() getChainHeight,
required Future<void> Function() updateWalletUTXOS,
required Future<String?> mnemonic,
required Future<String?> mnemonicPassphrase,
required btcdart.NetworkType network,
required final String Function(
String bchAddress, btcdart.NetworkType network)
convertToScriptHash,
}) async {
// Set passed in wallet data.
_walletId = walletId;
_coin = coin;
_db = db;
_getNextUnusedChangeAddresses = getNextUnusedChangeAddress;
_torService = FusionTorService.sharedInstance;
_getWalletCachedElectrumX = getWalletCachedElectrumX;
_getChainHeight = getChainHeight;
_updateWalletUTXOS = updateWalletUTXOS;
_mnemonic = mnemonic;
_mnemonicPassphrase = mnemonicPassphrase;
_network = network;
_convertToScriptHash = convertToScriptHash;
}
// callback to update the ui state object
void _updateStatus({required fusion.FusionStatus status, String? info}) {
switch (status) {
case fusion.FusionStatus.connecting:
_uiState?.setConnecting(
CashFusionState(status: CashFusionStatus.running, info: null),
shouldNotify: false);
_uiState?.setOutputs(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: false);
_uiState?.setPeers(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: false);
_uiState?.setFusing(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: false);
_uiState?.setComplete(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: true);
break;
case fusion.FusionStatus.setup:
_uiState?.setConnecting(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setOutputs(
CashFusionState(status: CashFusionStatus.running, info: null),
shouldNotify: false);
_uiState?.setPeers(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: false);
_uiState?.setFusing(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: false);
_uiState?.setComplete(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: true);
break;
case fusion.FusionStatus.waiting:
_uiState?.setConnecting(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setOutputs(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setPeers(
CashFusionState(status: CashFusionStatus.running, info: null),
shouldNotify: false);
_uiState?.setFusing(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: false);
_uiState?.setComplete(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: true);
break;
case fusion.FusionStatus.running:
_uiState?.setConnecting(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setOutputs(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setPeers(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setFusing(
CashFusionState(status: CashFusionStatus.running, info: null),
shouldNotify: false);
_uiState?.setComplete(
CashFusionState(status: CashFusionStatus.waiting, info: null),
shouldNotify: true);
break;
case fusion.FusionStatus.complete:
_uiState?.setConnecting(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setOutputs(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setPeers(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setFusing(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: false);
_uiState?.setComplete(
CashFusionState(status: CashFusionStatus.success, info: null),
shouldNotify: true);
break;
case fusion.FusionStatus.failed:
failCurrentUiState(info);
break;
case fusion.FusionStatus.exception:
failCurrentUiState(info);
break;
case fusion.FusionStatus.reset:
_uiState?.setConnecting(
CashFusionState(status: CashFusionStatus.waiting, info: info),
shouldNotify: false);
_uiState?.setOutputs(
CashFusionState(status: CashFusionStatus.waiting, info: info),
shouldNotify: false);
_uiState?.setPeers(
CashFusionState(status: CashFusionStatus.waiting, info: info),
shouldNotify: false);
_uiState?.setFusing(
CashFusionState(status: CashFusionStatus.waiting, info: info),
shouldNotify: false);
_uiState?.setComplete(
CashFusionState(status: CashFusionStatus.waiting, info: info),
shouldNotify: false);
_uiState?.setFusionState(
CashFusionState(status: CashFusionStatus.waiting, info: info),
shouldNotify: false);
_uiState?.setFailed(false, shouldNotify: true);
break;
}
}
void failCurrentUiState(String? info) {
// Check each _uiState value to see if it is running. If so, set it to failed.
if (_uiState?.connecting.status == CashFusionStatus.running) {
_uiState?.setConnecting(
CashFusionState(status: CashFusionStatus.failed, info: info),
shouldNotify: true);
return;
}
if (_uiState?.outputs.status == CashFusionStatus.running) {
_uiState?.setOutputs(
CashFusionState(status: CashFusionStatus.failed, info: info),
shouldNotify: true);
return;
}
if (_uiState?.peers.status == CashFusionStatus.running) {
_uiState?.setPeers(
CashFusionState(status: CashFusionStatus.failed, info: info),
shouldNotify: true);
return;
}
if (_uiState?.fusing.status == CashFusionStatus.running) {
_uiState?.setFusing(
CashFusionState(status: CashFusionStatus.failed, info: info),
shouldNotify: true);
return;
}
if (_uiState?.complete.status == CashFusionStatus.running) {
_uiState?.setComplete(
CashFusionState(status: CashFusionStatus.failed, info: info),
shouldNotify: true);
return;
}
}
/// Returns a list of all transactions in the wallet for the given address.
Future<List<Map<String, dynamic>>> _getTransactionsByAddress(
String address,
) async {
final txidList =
await _db.getTransactions(_walletId).txidProperty().findAll();
final futures = txidList.map(
(e) => _getWalletCachedElectrumX().getTransaction(
txHash: e,
coin: _coin,
),
);
return await Future.wait(futures);
}
Future<Uint8List> _getPrivateKeyForPubKey(List<int> pubKey) async {
// can't directly query for equal lists in isar so we need to fetch
// all addresses then search in dart
try {
final derivationPath = (await _db
.getAddresses(_walletId)
.filter()
.typeEqualTo(AddressType.p2pkh)
.and()
.derivationPathIsNotNull()
.and()
.group((q) => q
.subTypeEqualTo(AddressSubType.receiving)
.or()
.subTypeEqualTo(AddressSubType.change))
.findAll())
.firstWhere((e) => e.publicKey.toString() == pubKey.toString())
.derivationPath!
.value;
final node = await Bip32Utils.getBip32Node(
(await _mnemonic)!,
(await _mnemonicPassphrase)!,
_network,
derivationPath,
);
return node.privateKey!;
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
throw Exception("Derivation path for pubkey=$pubKey could not be found");
}
}
/// Reserve an address for fusion.
Future<List<Address>> _reserveAddresses(Iterable<Address> addresses) async {
if (addresses.isEmpty) {
return [];
}
final updatedAddresses = addresses
.map((e) => e.copyWith(otherData: kReservedFusionAddress))
.toList();
await _db.isar.writeTxn(() async {
for (final newAddress in updatedAddresses) {
final oldAddress = await _db.getAddress(
newAddress.walletId,
newAddress.value,
);
if (oldAddress != null) {
newAddress.id = oldAddress.id;
await _db.isar.addresses.delete(oldAddress.id);
}
await _db.isar.addresses.put(newAddress);
}
});
return updatedAddresses;
}
/// un reserve a fusion reserved address.
/// If [address] is not reserved nothing happens
Future<Address> _unReserveAddress(Address address) async {
if (address.otherData != kReservedFusionAddress) {
return address;
}
final updated = address.copyWith(otherData: null);
// Make sure the address is updated in the database.
await _db.updateAddress(address, updated);
return updated;
}
/// Returns a list of unused reserved change addresses.
///
/// If there are not enough unused reserved change addresses, new ones are created.
Future<List<fusion.Address>> _getUnusedReservedChangeAddresses(
int numberOfAddresses,
) async {
final unusedChangeAddresses = await _getNextUnusedChangeAddresses(
numberOfAddresses: numberOfAddresses,
);
// Initialize a list of unused reserved change addresses.
final List<Address> unusedReservedAddresses = unusedChangeAddresses
.where((e) => e.otherData == kReservedFusionAddress)
.toList();
unusedReservedAddresses.addAll(await _reserveAddresses(
unusedChangeAddresses.where((e) => e.otherData == null)));
// Return the list of unused reserved change addresses.
return unusedReservedAddresses
.map(
(e) => fusion.Address(
address: e.value,
publicKey: e.publicKey,
fusionReserved: true,
derivationPath: fusion.DerivationPath(
e.derivationPath!.value,
),
),
)
.toList();
}
int _torStartCount = 0;
/// Returns the current Tor proxy address.
Future<({InternetAddress host, int port})> _getSocksProxyAddress() async {
if (_torStartCount > 5) {
// something is quite broken so stop trying to recursively fetch
// start up tor and fetch proxy info
throw Exception(
"Fusion interface attempted to start tor $_torStartCount times and failed!",
);
}
try {
final info = _torService.getProxyInfo();
// reset counter before return info;
_torStartCount = 0;
return info;
} catch (_) {
// tor is probably not running so lets fix that
final torDir = await StackFileSystem.applicationTorDirectory();
_torService.init(torDataDirPath: torDir.path);
// increment start attempt count
_torStartCount++;
await _torService.start();
// try again to fetch proxy info
return await _getSocksProxyAddress();
}
}
Future<bool> _checkUtxoExists(
String address,
String prevTxid,
int prevIndex,
) async {
final scriptHash = _convertToScriptHash(address, _network);
final utxos = await _getWalletCachedElectrumX()
.electrumXClient
.getUTXOs(scripthash: scriptHash);
for (final utxo in utxos) {
if (utxo["tx_hash"] == prevTxid && utxo["tx_pos"] == prevIndex) {
return true;
}
}
return false;
}
// Initial attempt for CashFusion integration goes here.
/// Fuse the wallet's UTXOs.
///
/// This function is called when the user taps the "Fuse" button in the UI.
Future<void> fuse({
required FusionInfo fusionInfo,
}) async {
// Initial attempt for CashFusion integration goes here.
try {
_updateStatus(status: fusion.FusionStatus.reset);
_updateStatus(
status: fusion.FusionStatus.connecting,
info: "Connecting to the CashFusion server.",
);
// Use server host and port which ultimately come from text fields.
fusion.FusionParams serverParams = fusion.FusionParams(
serverHost: fusionInfo.host,
serverPort: fusionInfo.port,
serverSsl: fusionInfo.ssl,
genesisHashHex:
_coin.isTestNet ? GENESIS_HASH_TESTNET : GENESIS_HASH_MAINNET,
enableDebugPrint: kDebugMode,
torForOvert: _prefs.useTor,
mode: fusion.FusionMode.normal,
);
// Instantiate a Fusion object with custom parameters.
_mainFusionObject = fusion.Fusion(serverParams);
// Pass wallet functions to the Fusion object
await _mainFusionObject!.initFusion(
getTransactionsByAddress: _getTransactionsByAddress,
getUnusedReservedChangeAddresses: _getUnusedReservedChangeAddresses,
getSocksProxyAddress: _getSocksProxyAddress,
getChainHeight: _getChainHeight,
updateStatusCallback: _updateStatus,
checkUtxoExists: _checkUtxoExists,
getTransactionJson: (String txid) async =>
await _getWalletCachedElectrumX().getTransaction(
coin: _coin,
txHash: txid,
),
getPrivateKeyForPubKey: _getPrivateKeyForPubKey,
broadcastTransaction: (String txHex) => _getWalletCachedElectrumX()
.electrumXClient
.broadcastTransaction(rawTx: txHex),
unReserveAddresses: (List<fusion.Address> addresses) async {
final List<Future<void>> futures = [];
for (final addr in addresses) {
futures.add(
_db.getAddress(_walletId, addr.address).then(
(address) async {
if (address == null) {
// matching address not found in db so cannot mark as unreserved
// just ignore I guess. Should never actually happen in practice.
// Might be useful check in debugging cases?
return;
} else {
await _unReserveAddress(address);
}
},
),
);
}
await Future.wait(futures);
},
);
// Reset internal and UI counts and flag.
_completedFuseCount = 0;
_uiState?.fusionRoundsCompleted = 0;
_failedFuseCount = 0;
_uiState?.fusionRoundsFailed = 0;
_stopRequested = false;
bool shouldFuzeAgain() {
if (fusionInfo.rounds <= 0) {
// ignore count if continuous
return !_stopRequested;
} else {
// not continuous
// check to make sure we aren't doing more fusions than requested
return !_stopRequested && _completedFuseCount < fusionInfo.rounds;
}
}
while (shouldFuzeAgain()) {
if (_completedFuseCount > 0 || _failedFuseCount > 0) {
_updateStatus(status: fusion.FusionStatus.reset);
_updateStatus(
status: fusion.FusionStatus.connecting,
info: "Connecting to the CashFusion server.",
);
}
// refresh wallet utxos
await _updateWalletUTXOS();
// Add unfrozen stack UTXOs.
final List<UTXO> walletUtxos = await _db
.getUTXOs(_walletId)
.filter()
.isBlockedEqualTo(false)
.and()
.addressIsNotNull()
.findAll();
final List<fusion.UtxoDTO> coinList = [];
// Loop through UTXOs, checking and adding valid ones.
for (final utxo in walletUtxos) {
final String addressString = utxo.address!;
final List<String> possibleAddresses = [addressString];
if (bitbox.Address.detectFormat(addressString) ==
bitbox.Address.formatCashAddr) {
possibleAddresses
.add(bitbox.Address.toLegacyAddress(addressString));
} else {
possibleAddresses.add(bitbox.Address.toCashAddress(addressString));
}
// Fetch address to get pubkey
final addr = await _db
.getAddresses(_walletId)
.filter()
.anyOf<String,
QueryBuilder<Address, Address, QAfterFilterCondition>>(
possibleAddresses, (q, e) => q.valueEqualTo(e))
.and()
.group((q) => q
.subTypeEqualTo(AddressSubType.change)
.or()
.subTypeEqualTo(AddressSubType.receiving))
.and()
.typeEqualTo(AddressType.p2pkh)
.findFirst();
// depending on the address type in the query above this can be null
if (addr == null) {
// A utxo object should always have a non null address.
// If non found then just ignore the UTXO (aka don't fuse it)
Logging.instance.log(
"Ignoring utxo=$utxo for address=\"$addressString\" while selecting UTXOs for Fusion",
level: LogLevel.Info,
);
continue;
}
final dto = fusion.UtxoDTO(
txid: utxo.txid,
vout: utxo.vout,
value: utxo.value,
address: utxo.address!,
pubKey: addr.publicKey,
);
// Add UTXO to coinList.
coinList.add(dto);
}
// Fuse UTXOs.
try {
await _mainFusionObject!.fuse(
inputsFromWallet: coinList,
network: _coin.isTestNet
? fusion.Utilities.testNet
: fusion.Utilities.mainNet,
);
// Increment the number of successfully completed fusion rounds.
_completedFuseCount++;
// Do the same for the UI state. This also resets the failed count (for
// the UI state only).
_uiState?.incrementFusionRoundsCompleted();
// Also reset the failed count here.
_failedFuseCount = 0;
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Error,
);
// just continue on attempt failure
// Increment the number of failed fusion rounds.
_failedFuseCount++;
// Do the same for the UI state.
_uiState?.incrementFusionRoundsFailed();
// If we fail too many times in a row, stop trying.
if (_failedFuseCount >= maxFailedFuseCount) {
_updateStatus(
status: fusion.FusionStatus.failed,
info: "Failed $maxFailedFuseCount times in a row, stopping.");
_stopRequested = true;
_uiState?.setFailed(true, shouldNotify: true);
}
}
}
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Error,
);
// Stop the fusion process and update the UI state.
await _mainFusionObject?.stop();
_mainFusionObject = null;
_uiState?.setRunning(false, shouldNotify: true);
}
}
/// Stop the fusion process.
///
/// This function is called when the user taps the "Cancel" button in the UI
/// or closes the fusion progress dialog.
Future<void> stop() async {
_stopRequested = true;
await _mainFusionObject?.stop();
}
}

View file

@ -12,7 +12,7 @@ class TorService {
Tor? _tor;
String? _torDataDirPath;
/// Current status. Same as that fired on the event bus
/// Current status. Same as that fired on the event bus.
TorConnectionStatus get status => _status;
TorConnectionStatus _status = TorConnectionStatus.disconnected;
@ -26,7 +26,7 @@ class TorService {
/// Getter for the proxyInfo.
///
/// Returns null if disabled on the stack wallet level.
/// Throws if Tor is not connected.
({
InternetAddress host,
int port,
@ -55,7 +55,8 @@ class TorService {
///
/// This will start the Tor service and establish a Tor circuit.
///
/// Throws an exception if the Tor service fails to start.
/// Throws an exception if the Tor library was not inited or if the Tor
/// service fails to start.
///
/// Returns a Future that completes when the Tor service has started.
Future<void> start() async {
@ -79,6 +80,9 @@ class TorService {
status: TorConnectionStatus.connected,
message: "TorService.start call success",
);
// Complete the future.
return;
} catch (e, s) {
Logging.instance.log(
"TorService.start failed: $e\n$s",
@ -110,6 +114,8 @@ class TorService {
status: TorConnectionStatus.disconnected,
message: "TorService.disable call success",
);
return;
}
void _updateStatusAndFireEvent({

View file

@ -93,6 +93,9 @@ class _SVG {
final coinControl = const _COIN_CONTROL();
String get peers => "assets/svg/peers.svg";
String get fusing => "assets/svg/fusing.svg";
String get upFromLine => "assets/svg/up-from-line.svg";
String get connectedButton => "assets/svg/connected-button.svg";
String get connectingButton => "assets/svg/connecting-button.svg";
String get disconnectedButton => "assets/svg/disconnected-button.svg";
@ -195,6 +198,7 @@ class _SVG {
String get arrowDown => "assets/svg/arrow-down.svg";
String get robotHead => "assets/svg/robot-head.svg";
String get whirlPool => "assets/svg/whirlpool.svg";
String get cashFusion => "assets/svg/cashfusion-icon.svg";
String get fingerprint => "assets/svg/fingerprint.svg";
String get faceId => "assets/svg/faceid.svg";
String get tokens => "assets/svg/tokens.svg";
@ -202,6 +206,7 @@ class _SVG {
String get creditCard => "assets/svg/cc.svg";
String get file => "assets/svg/file.svg";
String get fileUpload => "assets/svg/file-upload.svg";
String get txCashFusion => "assets/svg/tx-cashfusion.svg";
String get ellipse1 => "assets/svg/Ellipse-43.svg";
String get ellipse2 => "assets/svg/Ellipse-42.svg";

View file

@ -50,16 +50,27 @@ abstract class Format {
// }
// format date string from unix timestamp
static String extractDateFrom(int timestamp, {bool localized = true}) {
static String extractDateFrom(
int timestamp, {
bool localized = true,
bool noTime = false,
}) {
var date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
if (!localized) {
date = date.toUtc();
}
final dayAndYear =
"${date.day} ${Constants.monthMapShort[date.month]} ${date.year}";
if (noTime) {
return dayAndYear;
}
final minutes =
date.minute < 10 ? "0${date.minute}" : date.minute.toString();
return "${date.day} ${Constants.monthMapShort[date.month]} ${date.year}, ${date.hour}:$minutes";
return "$dayAndYear, ${date.hour}:$minutes";
}
// static String localizedStringAsFixed({

View file

@ -12,6 +12,7 @@ import 'package:flutter/cupertino.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/services/event_bus/events/global/tor_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
@ -64,6 +65,7 @@ class Prefs extends ChangeNotifier {
await _setAmountUnits();
await _setMaxDecimals();
_useTor = await _getUseTor();
_fusionServerInfo = await _getFusionServerInfo();
_initialized = true;
}
@ -931,4 +933,35 @@ class Prefs extends ChangeNotifier {
) as bool? ??
false;
}
// fusion server info
FusionInfo _fusionServerInfo = FusionInfo.DEFAULTS;
FusionInfo get fusionServerInfo => _fusionServerInfo;
set fusionServerInfo(FusionInfo fusionServerInfo) {
if (this.fusionServerInfo != fusionServerInfo) {
DB.instance.put<dynamic>(
boxName: DB.boxNamePrefs,
key: "fusionServerInfo",
value: fusionServerInfo.toJsonString(),
);
_fusionServerInfo = fusionServerInfo;
notifyListeners();
}
}
Future<FusionInfo> _getFusionServerInfo() async {
final saved = await DB.instance.get<dynamic>(
boxName: DB.boxNamePrefs,
key: "fusionServerInfo",
) as String?;
try {
return FusionInfo.fromJsonString(saved!);
} catch (_) {
return FusionInfo.DEFAULTS;
}
}
}

View file

@ -73,14 +73,21 @@ abstract class Util {
return MaterialColor(color.value, swatch);
}
static void printJson(dynamic json) {
static void printJson(dynamic json, [String? debugTitle]) {
final String result;
if (json is Map || json is List) {
final spaces = ' ' * 4;
final encoder = JsonEncoder.withIndent(spaces);
final pretty = encoder.convert(json);
log(pretty);
result = pretty;
} else {
log(dynamic.toString());
result = dynamic.toString();
}
if (debugTitle != null) {
log("$debugTitle\n$result");
} else {
log(result);
}
}
}

View file

@ -16,6 +16,7 @@ import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart';
import 'package:stackwallet/widgets/expandable.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -141,7 +142,7 @@ class _MasterWalletCardState extends ConsumerState<MasterWalletCard> {
child: SimpleWalletCard(
walletId: widget.walletId,
contractAddress: e,
popPrevious: true,
popPrevious: Util.isDesktop,
),
),
),

View file

@ -157,7 +157,7 @@ class SimpleWalletCard extends ConsumerWidget {
} else {
await nav.pushNamed(
TokenView.routeName,
arguments: walletId,
arguments: (walletId: walletId, popPrevious: !Util.isDesktop),
);
}
}

View file

@ -0,0 +1,28 @@
/*
* 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 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
class FusionNavIcon extends StatelessWidget {
const FusionNavIcon({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SvgPicture.asset(
Assets.svg.cashFusion,
height: 20,
width: 20,
color: Theme.of(context).extension<StackColors>()!.bottomNavIconIcon,
);
}
}

View file

@ -15,6 +15,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
coinlib_flutter
tor_ffi_plugin
)

View file

@ -98,6 +98,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
bip340:
dependency: "direct main"
description:
name: bip340
sha256: "2a92f6ed68959f75d67c9a304c17928b9c9449587d4f75ee68f34152f7f69e87"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
bip39:
dependency: "direct main"
description:
@ -255,13 +263,21 @@ packages:
source: hosted
version: "4.6.0"
coinlib:
dependency: "direct main"
dependency: transitive
description:
name: coinlib
sha256: c8018027801ddcb093837ad8f27e9ac014434b84860ecd3114ca28980614ec4a
sha256: "410993f49aef30e48b76bbad8a33fef33f6b90e103b15b6be0277683ffe15399"
url: "https://pub.dev"
source: hosted
version: "1.0.0-rc.3"
version: "1.0.0"
coinlib_flutter:
dependency: "direct main"
description:
name: coinlib_flutter
sha256: d0a6a3694fc8c851f6b912bb1c552e33998e3f453efab3d62f75c0b1773d1ab9
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection:
dependency: transitive
description:
@ -287,7 +303,7 @@ packages:
source: hosted
version: "1.2.4"
convert:
dependency: transitive
dependency: "direct main"
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
@ -605,6 +621,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0+1"
flutter_hooks:
dependency: "direct main"
description:
name: flutter_hooks
sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec"
url: "https://pub.dev"
source: hosted
version: "0.20.3"
flutter_launcher_icons:
dependency: "direct dev"
description:
@ -778,6 +802,15 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
fusiondart:
dependency: "direct main"
description:
path: "."
ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
resolved-ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
url: "https://github.com/cypherstack/fusiondart.git"
source: git
version: "1.0.0"
glob:
dependency: transitive
description:
@ -1459,6 +1492,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.3+dev.3"
socks_socket:
dependency: "direct main"
description:
path: "."
ref: master
resolved-ref: ac6d721fe655208a6d488a088a35bab0ddc25702
url: "https://github.com/cypherstack/socks_socket.git"
source: git
version: "0.1.0"
source_gen:
dependency: transitive
description:
@ -1976,5 +2018,5 @@ packages:
source: hosted
version: "1.0.0"
sdks:
dart: ">=3.0.2 <4.0.0"
dart: ">=3.0.6 <4.0.0"
flutter: ">=3.10.3"

View file

@ -11,7 +11,7 @@ description: Stack Wallet
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.8.0+191
version: 1.8.1+192
environment:
sdk: ">=3.0.2 <4.0.0"
@ -62,6 +62,11 @@ dependencies:
url: https://github.com/cypherstack/tor.git
ref: 0a6888282f4e98401051a396e9d2293bd55ac2c2
fusiondart:
git:
url: https://github.com/cypherstack/fusiondart.git
ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
# Utility plugins
http: ^0.13.0
local_auth: ^1.1.10
@ -144,9 +149,16 @@ dependencies:
nanodart: ^2.0.0
basic_utils: ^5.5.4
stellar_flutter_sdk: ^1.5.3
socks_socket:
git:
url: https://github.com/cypherstack/socks_socket.git
ref: master
bip340: ^0.2.0
tezart: ^2.0.5
socks5_proxy: ^1.0.3+dev.3
coinlib: ^1.0.0-rc.3
coinlib_flutter: ^1.0.0
convert: ^3.1.1
flutter_hooks: ^0.20.3
dev_dependencies:
flutter_test:
@ -234,132 +246,7 @@ flutter:
- assets/images/splash.png
- assets/icon/icon.png
- google_fonts/
- assets/svg/circle-check.svg
- assets/svg/clipboard.svg
- assets/images/glasses.png
- assets/images/glasses-hidden.png
- assets/svg/unclaimed.svg
- assets/svg/plus.svg
- assets/svg/gear.svg
- assets/svg/bell.svg
- assets/svg/arrow-left-fa.svg
- assets/svg/copy-fa.svg
- assets/svg/star.svg
- assets/svg/x-circle.svg
- assets/svg/check.svg
- assets/svg/alert-circle.svg
- assets/svg/alert-circle2.svg
- assets/svg/arrow-down-left.svg
- assets/svg/arrow-up-right.svg
- assets/svg/bars.svg
- assets/svg/filter.svg
- assets/svg/pending.svg
- assets/svg/signal-stream.svg
- assets/svg/Ellipse-43.svg
- assets/svg/Ellipse-42.svg
- assets/svg/arrow-rotate.svg
- assets/svg/arrow-rotate2.svg
- assets/svg/qrcode1.svg
- assets/svg/gear-3.svg
- assets/svg/swap.svg
- assets/svg/chevron-down.svg
- assets/svg/chevron-up.svg
- assets/svg/lock-keyhole.svg
- assets/svg/lock-open.svg
- assets/svg/folder-down.svg
- assets/svg/network-wired.svg
- assets/svg/network-wired-2.svg
- assets/svg/address-book.svg
- assets/svg/address-book2.svg
- assets/svg/arrow-right.svg
- assets/svg/delete.svg
- assets/svg/dollar-sign.svg
- assets/svg/sun-bright2.svg
- assets/svg/language2.svg
- assets/svg/pen-solid-fa.svg
- assets/svg/magnifying-glass.svg
- assets/svg/x.svg
- assets/svg/x-fat.svg
- assets/svg/user.svg
- assets/svg/user-plus.svg
- assets/svg/user-minus.svg
- assets/svg/trash.svg
- assets/svg/eye.svg
- assets/svg/eye-slash.svg
- assets/svg/folder.svg
- assets/svg/calendar-days.svg
- assets/svg/circle-question.svg
- assets/svg/info-circle.svg
- assets/svg/key.svg
- assets/svg/node-alt.svg
- assets/svg/signal-problem-alt.svg
- assets/svg/signal-sync-alt.svg
- assets/svg/wallet-settings.svg
- assets/svg/ellipsis-vertical1.svg
- assets/svg/dice-alt.svg
- assets/svg/circle-arrow-up-right2.svg
- assets/svg/loader.svg
- assets/svg/add-backup.svg
- assets/svg/auto-backup.svg
- assets/svg/restore-backup.svg
- assets/svg/sliders-solid.svg
- assets/svg/message-question.svg
- assets/svg/envelope.svg
- assets/svg/share-2.svg
- assets/svg/anonymize.svg
- assets/svg/tx-icon-anonymize.svg
- assets/svg/tx-icon-anonymize-pending.svg
- assets/svg/tx-icon-anonymize-failed.svg
- assets/svg/Polygon.svg
- assets/svg/Button.svg
- assets/svg/enabled-button.svg
- assets/svg/lock-circle.svg
- assets/svg/dollar-sign-circle.svg
- assets/svg/language-circle.svg
- assets/svg/rotate-circle.svg
- assets/svg/sun-circle.svg
- assets/svg/node-circle.svg
- assets/svg/address-book-desktop.svg
- assets/svg/about-desktop.svg
- assets/svg/exchange-desktop.svg
- assets/svg/wallet-desktop.svg
- assets/svg/exit-desktop.svg
- assets/svg/keys.svg
- assets/svg/arrow-down.svg
- assets/svg/plus-circle.svg
- assets/svg/circle-plus-filled.svg
- assets/svg/configuration.svg
- assets/svg/tokens.svg
- assets/svg/circle-plus.svg
- assets/svg/robot-head.svg
- assets/svg/whirlpool.svg
- assets/svg/fingerprint.svg
- assets/svg/faceid.svg
- assets/svg/chevron-right.svg
- assets/svg/minimize.svg
- assets/svg/wallet-fa.svg
- assets/svg/exchange-3.svg
- assets/svg/message-question-1.svg
- assets/svg/drd-icon.svg
- assets/svg/box-auto.svg
- assets/svg/framed-address-book.svg
- assets/svg/framed-gear.svg
- assets/svg/list-ul.svg
- assets/svg/cc.svg
- assets/svg/file.svg
- assets/svg/file-upload.svg
- assets/svg/trocador_rating_a.svg
- assets/svg/trocador_rating_b.svg
- assets/svg/trocador_rating_c.svg
- assets/svg/trocador_rating_d.svg
- assets/svg/send.svg
- assets/svg/ordinal.svg
- assets/svg/monkey.svg
- assets/svg/tor.svg
- assets/svg/tor-circle.svg
- assets/svg/connected-button.svg
- assets/svg/connecting-button.svg
- assets/svg/disconnected-button.svg
- assets/svg/
# coin control icons
- assets/svg/coin_control/

View file

@ -3,17 +3,19 @@
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i4;
import 'dart:ui' as _i10;
import 'dart:async' as _i5;
import 'dart:ui' as _i11;
import 'package:decimal/decimal.dart' as _i2;
import 'package:mockito/mockito.dart' as _i1;
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i3;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i8;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i7;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i6;
import 'package:stackwallet/utilities/prefs.dart' as _i5;
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4;
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'
as _i3;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i9;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i8;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i7;
import 'package:stackwallet/utilities/prefs.dart' as _i6;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@ -46,16 +48,26 @@ class _FakeDecimal_1 extends _i1.SmartFake implements _i2.Decimal {
);
}
class _FakeFusionInfo_2 extends _i1.SmartFake implements _i3.FusionInfo {
_FakeFusionInfo_2(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
/// A class which mocks [ElectrumX].
///
/// See the documentation for Mockito's code generation for more information.
class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
class MockElectrumX extends _i1.Mock implements _i4.ElectrumX {
MockElectrumX() {
_i1.throwOnMissingStub(this);
}
@override
set failovers(List<_i3.ElectrumXNode>? _failovers) => super.noSuchMethod(
set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod(
Invocation.setter(
#failovers,
_failovers,
@ -100,7 +112,7 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
returnValue: false,
) as bool);
@override
_i4.Future<dynamic> request({
_i5.Future<dynamic> request({
required String? command,
List<dynamic>? args = const [],
String? requestID,
@ -119,10 +131,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
#requestTimeout: requestTimeout,
},
),
returnValue: _i4.Future<dynamic>.value(),
) as _i4.Future<dynamic>);
returnValue: _i5.Future<dynamic>.value(),
) as _i5.Future<dynamic>);
@override
_i4.Future<List<Map<String, dynamic>>> batchRequest({
_i5.Future<List<Map<String, dynamic>>> batchRequest({
required String? command,
required Map<String, List<dynamic>>? args,
Duration? requestTimeout = const Duration(seconds: 60),
@ -139,11 +151,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
#retries: retries,
},
),
returnValue: _i4.Future<List<Map<String, dynamic>>>.value(
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
<Map<String, dynamic>>[]),
) as _i4.Future<List<Map<String, dynamic>>>);
) as _i5.Future<List<Map<String, dynamic>>>);
@override
_i4.Future<bool> ping({
_i5.Future<bool> ping({
String? requestID,
int? retryCount = 1,
}) =>
@ -156,10 +168,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
#retryCount: retryCount,
},
),
returnValue: _i4.Future<bool>.value(false),
) as _i4.Future<bool>);
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
@override
_i4.Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) =>
_i5.Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) =>
(super.noSuchMethod(
Invocation.method(
#getBlockHeadTip,
@ -167,10 +179,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
{#requestID: requestID},
),
returnValue:
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>);
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
@override
_i4.Future<Map<String, dynamic>> getServerFeatures({String? requestID}) =>
_i5.Future<Map<String, dynamic>> getServerFeatures({String? requestID}) =>
(super.noSuchMethod(
Invocation.method(
#getServerFeatures,
@ -178,10 +190,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
{#requestID: requestID},
),
returnValue:
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>);
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
@override
_i4.Future<String> broadcastTransaction({
_i5.Future<String> broadcastTransaction({
required String? rawTx,
String? requestID,
}) =>
@ -194,10 +206,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
#requestID: requestID,
},
),
returnValue: _i4.Future<String>.value(''),
) as _i4.Future<String>);
returnValue: _i5.Future<String>.value(''),
) as _i5.Future<String>);
@override
_i4.Future<Map<String, dynamic>> getBalance({
_i5.Future<Map<String, dynamic>> getBalance({
required String? scripthash,
String? requestID,
}) =>
@ -211,10 +223,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
},
),
returnValue:
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>);
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
@override
_i4.Future<List<Map<String, dynamic>>> getHistory({
_i5.Future<List<Map<String, dynamic>>> getHistory({
required String? scripthash,
String? requestID,
}) =>
@ -227,11 +239,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
#requestID: requestID,
},
),
returnValue: _i4.Future<List<Map<String, dynamic>>>.value(
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
<Map<String, dynamic>>[]),
) as _i4.Future<List<Map<String, dynamic>>>);
) as _i5.Future<List<Map<String, dynamic>>>);
@override
_i4.Future<Map<String, List<Map<String, dynamic>>>> getBatchHistory(
_i5.Future<Map<String, List<Map<String, dynamic>>>> getBatchHistory(
{required Map<String, List<dynamic>>? args}) =>
(super.noSuchMethod(
Invocation.method(
@ -239,11 +251,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
[],
{#args: args},
),
returnValue: _i4.Future<Map<String, List<Map<String, dynamic>>>>.value(
returnValue: _i5.Future<Map<String, List<Map<String, dynamic>>>>.value(
<String, List<Map<String, dynamic>>>{}),
) as _i4.Future<Map<String, List<Map<String, dynamic>>>>);
) as _i5.Future<Map<String, List<Map<String, dynamic>>>>);
@override
_i4.Future<List<Map<String, dynamic>>> getUTXOs({
_i5.Future<List<Map<String, dynamic>>> getUTXOs({
required String? scripthash,
String? requestID,
}) =>
@ -256,11 +268,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
#requestID: requestID,
},
),
returnValue: _i4.Future<List<Map<String, dynamic>>>.value(
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
<Map<String, dynamic>>[]),
) as _i4.Future<List<Map<String, dynamic>>>);
) as _i5.Future<List<Map<String, dynamic>>>);
@override
_i4.Future<Map<String, List<Map<String, dynamic>>>> getBatchUTXOs(
_i5.Future<Map<String, List<Map<String, dynamic>>>> getBatchUTXOs(
{required Map<String, List<dynamic>>? args}) =>
(super.noSuchMethod(
Invocation.method(
@ -268,11 +280,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
[],
{#args: args},
),
returnValue: _i4.Future<Map<String, List<Map<String, dynamic>>>>.value(
returnValue: _i5.Future<Map<String, List<Map<String, dynamic>>>>.value(
<String, List<Map<String, dynamic>>>{}),
) as _i4.Future<Map<String, List<Map<String, dynamic>>>>);
) as _i5.Future<Map<String, List<Map<String, dynamic>>>>);
@override
_i4.Future<Map<String, dynamic>> getTransaction({
_i5.Future<Map<String, dynamic>> getTransaction({
required String? txHash,
bool? verbose = true,
String? requestID,
@ -288,10 +300,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
},
),
returnValue:
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>);
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
@override
_i4.Future<Map<String, dynamic>> getAnonymitySet({
_i5.Future<Map<String, dynamic>> getAnonymitySet({
String? groupId = r'1',
String? blockhash = r'',
String? requestID,
@ -307,10 +319,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
},
),
returnValue:
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>);
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
@override
_i4.Future<dynamic> getMintData({
_i5.Future<dynamic> getMintData({
dynamic mints,
String? requestID,
}) =>
@ -323,10 +335,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
#requestID: requestID,
},
),
returnValue: _i4.Future<dynamic>.value(),
) as _i4.Future<dynamic>);
returnValue: _i5.Future<dynamic>.value(),
) as _i5.Future<dynamic>);
@override
_i4.Future<Map<String, dynamic>> getUsedCoinSerials({
_i5.Future<Map<String, dynamic>> getUsedCoinSerials({
String? requestID,
required int? startNumber,
}) =>
@ -340,19 +352,19 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
},
),
returnValue:
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>);
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
@override
_i4.Future<int> getLatestCoinId({String? requestID}) => (super.noSuchMethod(
_i5.Future<int> getLatestCoinId({String? requestID}) => (super.noSuchMethod(
Invocation.method(
#getLatestCoinId,
[],
{#requestID: requestID},
),
returnValue: _i4.Future<int>.value(0),
) as _i4.Future<int>);
returnValue: _i5.Future<int>.value(0),
) as _i5.Future<int>);
@override
_i4.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
_i5.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
(super.noSuchMethod(
Invocation.method(
#getFeeRate,
@ -360,10 +372,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
{#requestID: requestID},
),
returnValue:
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>);
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
@override
_i4.Future<_i2.Decimal> estimateFee({
_i5.Future<_i2.Decimal> estimateFee({
String? requestID,
required int? blocks,
}) =>
@ -376,7 +388,7 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
#blocks: blocks,
},
),
returnValue: _i4.Future<_i2.Decimal>.value(_FakeDecimal_1(
returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_1(
this,
Invocation.method(
#estimateFee,
@ -387,15 +399,15 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
},
),
)),
) as _i4.Future<_i2.Decimal>);
) as _i5.Future<_i2.Decimal>);
@override
_i4.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod(
_i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod(
Invocation.method(
#relayFee,
[],
{#requestID: requestID},
),
returnValue: _i4.Future<_i2.Decimal>.value(_FakeDecimal_1(
returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_1(
this,
Invocation.method(
#relayFee,
@ -403,13 +415,13 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
{#requestID: requestID},
),
)),
) as _i4.Future<_i2.Decimal>);
) as _i5.Future<_i2.Decimal>);
}
/// A class which mocks [Prefs].
///
/// See the documentation for Mockito's code generation for more information.
class MockPrefs extends _i1.Mock implements _i5.Prefs {
class MockPrefs extends _i1.Mock implements _i6.Prefs {
MockPrefs() {
_i1.throwOnMissingStub(this);
}
@ -465,12 +477,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
_i6.SyncingType get syncType => (super.noSuchMethod(
_i7.SyncingType get syncType => (super.noSuchMethod(
Invocation.getter(#syncType),
returnValue: _i6.SyncingType.currentWalletOnly,
) as _i6.SyncingType);
returnValue: _i7.SyncingType.currentWalletOnly,
) as _i7.SyncingType);
@override
set syncType(_i6.SyncingType? syncType) => super.noSuchMethod(
set syncType(_i7.SyncingType? syncType) => super.noSuchMethod(
Invocation.setter(
#syncType,
syncType,
@ -629,12 +641,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
_i7.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
_i8.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
Invocation.getter(#backupFrequencyType),
returnValue: _i7.BackupFrequencyType.everyTenMinutes,
) as _i7.BackupFrequencyType);
returnValue: _i8.BackupFrequencyType.everyTenMinutes,
) as _i8.BackupFrequencyType);
@override
set backupFrequencyType(_i7.BackupFrequencyType? backupFrequencyType) =>
set backupFrequencyType(_i8.BackupFrequencyType? backupFrequencyType) =>
super.noSuchMethod(
Invocation.setter(
#backupFrequencyType,
@ -780,66 +792,82 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
_i3.FusionInfo get fusionServerInfo => (super.noSuchMethod(
Invocation.getter(#fusionServerInfo),
returnValue: _FakeFusionInfo_2(
this,
Invocation.getter(#fusionServerInfo),
),
) as _i3.FusionInfo);
@override
set fusionServerInfo(_i3.FusionInfo? fusionServerInfo) => super.noSuchMethod(
Invocation.setter(
#fusionServerInfo,
fusionServerInfo,
),
returnValueForMissingStub: null,
);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
) as bool);
@override
_i4.Future<void> init() => (super.noSuchMethod(
_i5.Future<void> init() => (super.noSuchMethod(
Invocation.method(
#init,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i4.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
_i5.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
Invocation.method(
#incrementCurrentNotificationIndex,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i4.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
_i5.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
Invocation.method(
#isExternalCallsSet,
[],
),
returnValue: _i4.Future<bool>.value(false),
) as _i4.Future<bool>);
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
@override
_i4.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
_i5.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
Invocation.method(
#saveUserID,
[userId],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i4.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
_i5.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
Invocation.method(
#saveSignupEpoch,
[signupEpoch],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i8.AmountUnit amountUnit(_i9.Coin? coin) => (super.noSuchMethod(
_i9.AmountUnit amountUnit(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#amountUnit,
[coin],
),
returnValue: _i8.AmountUnit.normal,
) as _i8.AmountUnit);
returnValue: _i9.AmountUnit.normal,
) as _i9.AmountUnit);
@override
void updateAmountUnit({
required _i9.Coin? coin,
required _i8.AmountUnit? amountUnit,
required _i10.Coin? coin,
required _i9.AmountUnit? amountUnit,
}) =>
super.noSuchMethod(
Invocation.method(
@ -853,7 +881,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
int maxDecimals(_i9.Coin? coin) => (super.noSuchMethod(
int maxDecimals(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#maxDecimals,
[coin],
@ -862,7 +890,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
) as int);
@override
void updateMaxDecimals({
required _i9.Coin? coin,
required _i10.Coin? coin,
required int? maxDecimals,
}) =>
super.noSuchMethod(
@ -877,7 +905,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
@ -885,7 +913,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],

View file

@ -3,21 +3,23 @@
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i4;
import 'dart:io' as _i3;
import 'dart:ui' as _i10;
import 'dart:async' as _i5;
import 'dart:io' as _i4;
import 'dart:ui' as _i11;
import 'package:mockito/mockito.dart' as _i1;
import 'package:stackwallet/electrumx_rpc/rpc.dart' as _i2;
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart'
as _i12;
import 'package:stackwallet/services/tor_service.dart' as _i11;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i8;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i7;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i6;
import 'package:stackwallet/utilities/prefs.dart' as _i5;
import 'package:tor_ffi_plugin/tor_ffi_plugin.dart' as _i13;
as _i13;
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'
as _i3;
import 'package:stackwallet/services/tor_service.dart' as _i12;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i9;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i8;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i7;
import 'package:stackwallet/utilities/prefs.dart' as _i6;
import 'package:tor_ffi_plugin/tor_ffi_plugin.dart' as _i14;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@ -51,9 +53,19 @@ class _FakeJsonRPCResponse_1 extends _i1.SmartFake
);
}
class _FakeInternetAddress_2 extends _i1.SmartFake
implements _i3.InternetAddress {
_FakeInternetAddress_2(
class _FakeFusionInfo_2 extends _i1.SmartFake implements _i3.FusionInfo {
_FakeFusionInfo_2(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeInternetAddress_3 extends _i1.SmartFake
implements _i4.InternetAddress {
_FakeInternetAddress_3(
Object parent,
Invocation parentInvocation,
) : super(
@ -94,7 +106,7 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
),
) as Duration);
@override
set proxyInfo(({_i3.InternetAddress host, int port})? _proxyInfo) =>
set proxyInfo(({_i4.InternetAddress host, int port})? _proxyInfo) =>
super.noSuchMethod(
Invocation.setter(
#proxyInfo,
@ -103,7 +115,7 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
returnValueForMissingStub: null,
);
@override
_i4.Future<_i2.JsonRPCResponse> request(
_i5.Future<_i2.JsonRPCResponse> request(
String? jsonRpcRequest,
Duration? requestTimeout,
) =>
@ -116,7 +128,7 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
],
),
returnValue:
_i4.Future<_i2.JsonRPCResponse>.value(_FakeJsonRPCResponse_1(
_i5.Future<_i2.JsonRPCResponse>.value(_FakeJsonRPCResponse_1(
this,
Invocation.method(
#request,
@ -126,32 +138,32 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
],
),
)),
) as _i4.Future<_i2.JsonRPCResponse>);
) as _i5.Future<_i2.JsonRPCResponse>);
@override
_i4.Future<void> disconnect({required String? reason}) => (super.noSuchMethod(
_i5.Future<void> disconnect({required String? reason}) => (super.noSuchMethod(
Invocation.method(
#disconnect,
[],
{#reason: reason},
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i4.Future<void> connect() => (super.noSuchMethod(
_i5.Future<void> connect() => (super.noSuchMethod(
Invocation.method(
#connect,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
}
/// A class which mocks [Prefs].
///
/// See the documentation for Mockito's code generation for more information.
class MockPrefs extends _i1.Mock implements _i5.Prefs {
class MockPrefs extends _i1.Mock implements _i6.Prefs {
MockPrefs() {
_i1.throwOnMissingStub(this);
}
@ -207,12 +219,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
_i6.SyncingType get syncType => (super.noSuchMethod(
_i7.SyncingType get syncType => (super.noSuchMethod(
Invocation.getter(#syncType),
returnValue: _i6.SyncingType.currentWalletOnly,
) as _i6.SyncingType);
returnValue: _i7.SyncingType.currentWalletOnly,
) as _i7.SyncingType);
@override
set syncType(_i6.SyncingType? syncType) => super.noSuchMethod(
set syncType(_i7.SyncingType? syncType) => super.noSuchMethod(
Invocation.setter(
#syncType,
syncType,
@ -371,12 +383,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
_i7.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
_i8.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
Invocation.getter(#backupFrequencyType),
returnValue: _i7.BackupFrequencyType.everyTenMinutes,
) as _i7.BackupFrequencyType);
returnValue: _i8.BackupFrequencyType.everyTenMinutes,
) as _i8.BackupFrequencyType);
@override
set backupFrequencyType(_i7.BackupFrequencyType? backupFrequencyType) =>
set backupFrequencyType(_i8.BackupFrequencyType? backupFrequencyType) =>
super.noSuchMethod(
Invocation.setter(
#backupFrequencyType,
@ -522,66 +534,82 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
_i3.FusionInfo get fusionServerInfo => (super.noSuchMethod(
Invocation.getter(#fusionServerInfo),
returnValue: _FakeFusionInfo_2(
this,
Invocation.getter(#fusionServerInfo),
),
) as _i3.FusionInfo);
@override
set fusionServerInfo(_i3.FusionInfo? fusionServerInfo) => super.noSuchMethod(
Invocation.setter(
#fusionServerInfo,
fusionServerInfo,
),
returnValueForMissingStub: null,
);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
) as bool);
@override
_i4.Future<void> init() => (super.noSuchMethod(
_i5.Future<void> init() => (super.noSuchMethod(
Invocation.method(
#init,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i4.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
_i5.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
Invocation.method(
#incrementCurrentNotificationIndex,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i4.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
_i5.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
Invocation.method(
#isExternalCallsSet,
[],
),
returnValue: _i4.Future<bool>.value(false),
) as _i4.Future<bool>);
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
@override
_i4.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
_i5.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
Invocation.method(
#saveUserID,
[userId],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i4.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
_i5.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
Invocation.method(
#saveSignupEpoch,
[signupEpoch],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i8.AmountUnit amountUnit(_i9.Coin? coin) => (super.noSuchMethod(
_i9.AmountUnit amountUnit(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#amountUnit,
[coin],
),
returnValue: _i8.AmountUnit.normal,
) as _i8.AmountUnit);
returnValue: _i9.AmountUnit.normal,
) as _i9.AmountUnit);
@override
void updateAmountUnit({
required _i9.Coin? coin,
required _i8.AmountUnit? amountUnit,
required _i10.Coin? coin,
required _i9.AmountUnit? amountUnit,
}) =>
super.noSuchMethod(
Invocation.method(
@ -595,7 +623,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
int maxDecimals(_i9.Coin? coin) => (super.noSuchMethod(
int maxDecimals(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#maxDecimals,
[coin],
@ -604,7 +632,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
) as int);
@override
void updateMaxDecimals({
required _i9.Coin? coin,
required _i10.Coin? coin,
required int? maxDecimals,
}) =>
super.noSuchMethod(
@ -619,7 +647,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
@ -627,7 +655,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
returnValueForMissingStub: null,
);
@override
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],
@ -655,24 +683,24 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
/// A class which mocks [TorService].
///
/// See the documentation for Mockito's code generation for more information.
class MockTorService extends _i1.Mock implements _i11.TorService {
class MockTorService extends _i1.Mock implements _i12.TorService {
MockTorService() {
_i1.throwOnMissingStub(this);
}
@override
_i12.TorConnectionStatus get status => (super.noSuchMethod(
_i13.TorConnectionStatus get status => (super.noSuchMethod(
Invocation.getter(#status),
returnValue: _i12.TorConnectionStatus.disconnected,
) as _i12.TorConnectionStatus);
returnValue: _i13.TorConnectionStatus.disconnected,
) as _i13.TorConnectionStatus);
@override
({_i3.InternetAddress host, int port}) getProxyInfo() => (super.noSuchMethod(
({_i4.InternetAddress host, int port}) getProxyInfo() => (super.noSuchMethod(
Invocation.method(
#getProxyInfo,
[],
),
returnValue: (
host: _FakeInternetAddress_2(
host: _FakeInternetAddress_3(
this,
Invocation.method(
#getProxyInfo,
@ -681,11 +709,11 @@ class MockTorService extends _i1.Mock implements _i11.TorService {
),
port: 0
),
) as ({_i3.InternetAddress host, int port}));
) as ({_i4.InternetAddress host, int port}));
@override
void init({
required String? torDataDirPath,
_i13.Tor? mockableOverride,
_i14.Tor? mockableOverride,
}) =>
super.noSuchMethod(
Invocation.method(
@ -699,21 +727,21 @@ class MockTorService extends _i1.Mock implements _i11.TorService {
returnValueForMissingStub: null,
);
@override
_i4.Future<void> start() => (super.noSuchMethod(
_i5.Future<void> start() => (super.noSuchMethod(
Invocation.method(
#start,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i4.Future<void> disable() => (super.noSuchMethod(
_i5.Future<void> disable() => (super.noSuchMethod(
Invocation.method(
#disable,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
}

File diff suppressed because it is too large Load diff

View file

@ -401,6 +401,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -362,6 +362,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -360,6 +360,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -3,38 +3,40 @@
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i7;
import 'dart:ui' as _i10;
import 'dart:async' as _i8;
import 'dart:ui' as _i11;
import 'package:decimal/decimal.dart' as _i16;
import 'package:decimal/decimal.dart' as _i17;
import 'package:mockito/mockito.dart' as _i1;
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'
as _i19;
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'
as _i21;
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'
as _i22;
import 'package:stackwallet/models/exchange/response_objects/estimate.dart'
as _i18;
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'
as _i20;
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'
as _i22;
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'
as _i23;
import 'package:stackwallet/models/exchange/response_objects/estimate.dart'
as _i19;
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'
as _i21;
import 'package:stackwallet/models/exchange/response_objects/range.dart'
as _i17;
as _i18;
import 'package:stackwallet/models/exchange/response_objects/trade.dart'
as _i12;
import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i15;
import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i23;
import 'package:stackwallet/networking/http.dart' as _i2;
as _i13;
import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i16;
import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i24;
import 'package:stackwallet/networking/http.dart' as _i3;
import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'
as _i14;
import 'package:stackwallet/services/exchange/exchange_response.dart' as _i3;
import 'package:stackwallet/services/trade_notes_service.dart' as _i13;
import 'package:stackwallet/services/trade_service.dart' as _i11;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i8;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i6;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i5;
import 'package:stackwallet/utilities/prefs.dart' as _i4;
as _i15;
import 'package:stackwallet/services/exchange/exchange_response.dart' as _i4;
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'
as _i2;
import 'package:stackwallet/services/trade_notes_service.dart' as _i14;
import 'package:stackwallet/services/trade_service.dart' as _i12;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i9;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i7;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i6;
import 'package:stackwallet/utilities/prefs.dart' as _i5;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@ -47,8 +49,8 @@ import 'package:stackwallet/utilities/prefs.dart' as _i4;
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeHTTP_0 extends _i1.SmartFake implements _i2.HTTP {
_FakeHTTP_0(
class _FakeFusionInfo_0 extends _i1.SmartFake implements _i2.FusionInfo {
_FakeFusionInfo_0(
Object parent,
Invocation parentInvocation,
) : super(
@ -57,9 +59,19 @@ class _FakeHTTP_0 extends _i1.SmartFake implements _i2.HTTP {
);
}
class _FakeExchangeResponse_1<T> extends _i1.SmartFake
implements _i3.ExchangeResponse<T> {
_FakeExchangeResponse_1(
class _FakeHTTP_1 extends _i1.SmartFake implements _i3.HTTP {
_FakeHTTP_1(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeExchangeResponse_2<T> extends _i1.SmartFake
implements _i4.ExchangeResponse<T> {
_FakeExchangeResponse_2(
Object parent,
Invocation parentInvocation,
) : super(
@ -71,7 +83,7 @@ class _FakeExchangeResponse_1<T> extends _i1.SmartFake
/// A class which mocks [Prefs].
///
/// See the documentation for Mockito's code generation for more information.
class MockPrefs extends _i1.Mock implements _i4.Prefs {
class MockPrefs extends _i1.Mock implements _i5.Prefs {
MockPrefs() {
_i1.throwOnMissingStub(this);
}
@ -127,12 +139,12 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
returnValueForMissingStub: null,
);
@override
_i5.SyncingType get syncType => (super.noSuchMethod(
_i6.SyncingType get syncType => (super.noSuchMethod(
Invocation.getter(#syncType),
returnValue: _i5.SyncingType.currentWalletOnly,
) as _i5.SyncingType);
returnValue: _i6.SyncingType.currentWalletOnly,
) as _i6.SyncingType);
@override
set syncType(_i5.SyncingType? syncType) => super.noSuchMethod(
set syncType(_i6.SyncingType? syncType) => super.noSuchMethod(
Invocation.setter(
#syncType,
syncType,
@ -291,12 +303,12 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
returnValueForMissingStub: null,
);
@override
_i6.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
_i7.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
Invocation.getter(#backupFrequencyType),
returnValue: _i6.BackupFrequencyType.everyTenMinutes,
) as _i6.BackupFrequencyType);
returnValue: _i7.BackupFrequencyType.everyTenMinutes,
) as _i7.BackupFrequencyType);
@override
set backupFrequencyType(_i6.BackupFrequencyType? backupFrequencyType) =>
set backupFrequencyType(_i7.BackupFrequencyType? backupFrequencyType) =>
super.noSuchMethod(
Invocation.setter(
#backupFrequencyType,
@ -442,66 +454,82 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
returnValueForMissingStub: null,
);
@override
_i2.FusionInfo get fusionServerInfo => (super.noSuchMethod(
Invocation.getter(#fusionServerInfo),
returnValue: _FakeFusionInfo_0(
this,
Invocation.getter(#fusionServerInfo),
),
) as _i2.FusionInfo);
@override
set fusionServerInfo(_i2.FusionInfo? fusionServerInfo) => super.noSuchMethod(
Invocation.setter(
#fusionServerInfo,
fusionServerInfo,
),
returnValueForMissingStub: null,
);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
) as bool);
@override
_i7.Future<void> init() => (super.noSuchMethod(
_i8.Future<void> init() => (super.noSuchMethod(
Invocation.method(
#init,
[],
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i7.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
_i8.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
Invocation.method(
#incrementCurrentNotificationIndex,
[],
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i7.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
_i8.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
Invocation.method(
#isExternalCallsSet,
[],
),
returnValue: _i7.Future<bool>.value(false),
) as _i7.Future<bool>);
returnValue: _i8.Future<bool>.value(false),
) as _i8.Future<bool>);
@override
_i7.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
_i8.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
Invocation.method(
#saveUserID,
[userId],
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i7.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
_i8.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
Invocation.method(
#saveSignupEpoch,
[signupEpoch],
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i8.AmountUnit amountUnit(_i9.Coin? coin) => (super.noSuchMethod(
_i9.AmountUnit amountUnit(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#amountUnit,
[coin],
),
returnValue: _i8.AmountUnit.normal,
) as _i8.AmountUnit);
returnValue: _i9.AmountUnit.normal,
) as _i9.AmountUnit);
@override
void updateAmountUnit({
required _i9.Coin? coin,
required _i8.AmountUnit? amountUnit,
required _i10.Coin? coin,
required _i9.AmountUnit? amountUnit,
}) =>
super.noSuchMethod(
Invocation.method(
@ -515,7 +543,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
returnValueForMissingStub: null,
);
@override
int maxDecimals(_i9.Coin? coin) => (super.noSuchMethod(
int maxDecimals(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#maxDecimals,
[coin],
@ -524,7 +552,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
) as int);
@override
void updateMaxDecimals({
required _i9.Coin? coin,
required _i10.Coin? coin,
required int? maxDecimals,
}) =>
super.noSuchMethod(
@ -539,7 +567,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
returnValueForMissingStub: null,
);
@override
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
@ -547,7 +575,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
returnValueForMissingStub: null,
);
@override
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],
@ -575,29 +603,29 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
/// A class which mocks [TradesService].
///
/// See the documentation for Mockito's code generation for more information.
class MockTradesService extends _i1.Mock implements _i11.TradesService {
class MockTradesService extends _i1.Mock implements _i12.TradesService {
MockTradesService() {
_i1.throwOnMissingStub(this);
}
@override
List<_i12.Trade> get trades => (super.noSuchMethod(
List<_i13.Trade> get trades => (super.noSuchMethod(
Invocation.getter(#trades),
returnValue: <_i12.Trade>[],
) as List<_i12.Trade>);
returnValue: <_i13.Trade>[],
) as List<_i13.Trade>);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
) as bool);
@override
_i12.Trade? get(String? tradeId) => (super.noSuchMethod(Invocation.method(
_i13.Trade? get(String? tradeId) => (super.noSuchMethod(Invocation.method(
#get,
[tradeId],
)) as _i12.Trade?);
)) as _i13.Trade?);
@override
_i7.Future<void> add({
required _i12.Trade? trade,
_i8.Future<void> add({
required _i13.Trade? trade,
required bool? shouldNotifyListeners,
}) =>
(super.noSuchMethod(
@ -609,12 +637,12 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
#shouldNotifyListeners: shouldNotifyListeners,
},
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i7.Future<void> edit({
required _i12.Trade? trade,
_i8.Future<void> edit({
required _i13.Trade? trade,
required bool? shouldNotifyListeners,
}) =>
(super.noSuchMethod(
@ -626,12 +654,12 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
#shouldNotifyListeners: shouldNotifyListeners,
},
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i7.Future<void> delete({
required _i12.Trade? trade,
_i8.Future<void> delete({
required _i13.Trade? trade,
required bool? shouldNotifyListeners,
}) =>
(super.noSuchMethod(
@ -643,11 +671,11 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
#shouldNotifyListeners: shouldNotifyListeners,
},
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i7.Future<void> deleteByUuid({
_i8.Future<void> deleteByUuid({
required String? uuid,
required bool? shouldNotifyListeners,
}) =>
@ -660,11 +688,11 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
#shouldNotifyListeners: shouldNotifyListeners,
},
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
@ -672,7 +700,7 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
returnValueForMissingStub: null,
);
@override
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],
@ -700,7 +728,7 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
/// A class which mocks [TradeNotesService].
///
/// See the documentation for Mockito's code generation for more information.
class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
class MockTradeNotesService extends _i1.Mock implements _i14.TradeNotesService {
MockTradeNotesService() {
_i1.throwOnMissingStub(this);
}
@ -725,7 +753,7 @@ class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
returnValue: '',
) as String);
@override
_i7.Future<void> set({
_i8.Future<void> set({
required String? tradeId,
required String? note,
}) =>
@ -738,21 +766,21 @@ class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
#note: note,
},
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i7.Future<void> delete({required String? tradeId}) => (super.noSuchMethod(
_i8.Future<void> delete({required String? tradeId}) => (super.noSuchMethod(
Invocation.method(
#delete,
[],
{#tradeId: tradeId},
),
returnValue: _i7.Future<void>.value(),
returnValueForMissingStub: _i7.Future<void>.value(),
) as _i7.Future<void>);
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
@ -760,7 +788,7 @@ class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
returnValueForMissingStub: null,
);
@override
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],
@ -788,21 +816,21 @@ class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
/// A class which mocks [ChangeNowAPI].
///
/// See the documentation for Mockito's code generation for more information.
class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
class MockChangeNowAPI extends _i1.Mock implements _i15.ChangeNowAPI {
MockChangeNowAPI() {
_i1.throwOnMissingStub(this);
}
@override
_i2.HTTP get client => (super.noSuchMethod(
_i3.HTTP get client => (super.noSuchMethod(
Invocation.getter(#client),
returnValue: _FakeHTTP_0(
returnValue: _FakeHTTP_1(
this,
Invocation.getter(#client),
),
) as _i2.HTTP);
) as _i3.HTTP);
@override
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>> getAvailableCurrencies({
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>> getAvailableCurrencies({
bool? fixedRate,
bool? active,
}) =>
@ -816,8 +844,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
returnValue:
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>.value(
_FakeExchangeResponse_1<List<_i15.Currency>>(
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>.value(
_FakeExchangeResponse_2<List<_i16.Currency>>(
this,
Invocation.method(
#getAvailableCurrencies,
@ -828,26 +856,26 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>);
) as _i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>);
@override
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>> getCurrenciesV2() =>
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>> getCurrenciesV2() =>
(super.noSuchMethod(
Invocation.method(
#getCurrenciesV2,
[],
),
returnValue:
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>.value(
_FakeExchangeResponse_1<List<_i15.Currency>>(
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>.value(
_FakeExchangeResponse_2<List<_i16.Currency>>(
this,
Invocation.method(
#getCurrenciesV2,
[],
),
)),
) as _i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>);
) as _i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>);
@override
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>> getPairedCurrencies({
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>> getPairedCurrencies({
required String? ticker,
bool? fixedRate,
}) =>
@ -861,8 +889,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
returnValue:
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>.value(
_FakeExchangeResponse_1<List<_i15.Currency>>(
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>.value(
_FakeExchangeResponse_2<List<_i16.Currency>>(
this,
Invocation.method(
#getPairedCurrencies,
@ -873,9 +901,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>);
) as _i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>);
@override
_i7.Future<_i3.ExchangeResponse<_i16.Decimal>> getMinimalExchangeAmount({
_i8.Future<_i4.ExchangeResponse<_i17.Decimal>> getMinimalExchangeAmount({
required String? fromTicker,
required String? toTicker,
String? apiKey,
@ -890,8 +918,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
#apiKey: apiKey,
},
),
returnValue: _i7.Future<_i3.ExchangeResponse<_i16.Decimal>>.value(
_FakeExchangeResponse_1<_i16.Decimal>(
returnValue: _i8.Future<_i4.ExchangeResponse<_i17.Decimal>>.value(
_FakeExchangeResponse_2<_i17.Decimal>(
this,
Invocation.method(
#getMinimalExchangeAmount,
@ -903,9 +931,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<_i16.Decimal>>);
) as _i8.Future<_i4.ExchangeResponse<_i17.Decimal>>);
@override
_i7.Future<_i3.ExchangeResponse<_i17.Range>> getRange({
_i8.Future<_i4.ExchangeResponse<_i18.Range>> getRange({
required String? fromTicker,
required String? toTicker,
required bool? isFixedRate,
@ -922,8 +950,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
#apiKey: apiKey,
},
),
returnValue: _i7.Future<_i3.ExchangeResponse<_i17.Range>>.value(
_FakeExchangeResponse_1<_i17.Range>(
returnValue: _i8.Future<_i4.ExchangeResponse<_i18.Range>>.value(
_FakeExchangeResponse_2<_i18.Range>(
this,
Invocation.method(
#getRange,
@ -936,12 +964,12 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<_i17.Range>>);
) as _i8.Future<_i4.ExchangeResponse<_i18.Range>>);
@override
_i7.Future<_i3.ExchangeResponse<_i18.Estimate>> getEstimatedExchangeAmount({
_i8.Future<_i4.ExchangeResponse<_i19.Estimate>> getEstimatedExchangeAmount({
required String? fromTicker,
required String? toTicker,
required _i16.Decimal? fromAmount,
required _i17.Decimal? fromAmount,
String? apiKey,
}) =>
(super.noSuchMethod(
@ -955,8 +983,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
#apiKey: apiKey,
},
),
returnValue: _i7.Future<_i3.ExchangeResponse<_i18.Estimate>>.value(
_FakeExchangeResponse_1<_i18.Estimate>(
returnValue: _i8.Future<_i4.ExchangeResponse<_i19.Estimate>>.value(
_FakeExchangeResponse_2<_i19.Estimate>(
this,
Invocation.method(
#getEstimatedExchangeAmount,
@ -969,13 +997,13 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<_i18.Estimate>>);
) as _i8.Future<_i4.ExchangeResponse<_i19.Estimate>>);
@override
_i7.Future<_i3.ExchangeResponse<_i18.Estimate>>
_i8.Future<_i4.ExchangeResponse<_i19.Estimate>>
getEstimatedExchangeAmountFixedRate({
required String? fromTicker,
required String? toTicker,
required _i16.Decimal? fromAmount,
required _i17.Decimal? fromAmount,
required bool? reversed,
bool? useRateId = true,
String? apiKey,
@ -993,8 +1021,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
#apiKey: apiKey,
},
),
returnValue: _i7.Future<_i3.ExchangeResponse<_i18.Estimate>>.value(
_FakeExchangeResponse_1<_i18.Estimate>(
returnValue: _i8.Future<_i4.ExchangeResponse<_i19.Estimate>>.value(
_FakeExchangeResponse_2<_i19.Estimate>(
this,
Invocation.method(
#getEstimatedExchangeAmountFixedRate,
@ -1009,17 +1037,17 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<_i18.Estimate>>);
) as _i8.Future<_i4.ExchangeResponse<_i19.Estimate>>);
@override
_i7.Future<_i3.ExchangeResponse<_i19.CNExchangeEstimate>>
_i8.Future<_i4.ExchangeResponse<_i20.CNExchangeEstimate>>
getEstimatedExchangeAmountV2({
required String? fromTicker,
required String? toTicker,
required _i19.CNEstimateType? fromOrTo,
required _i16.Decimal? amount,
required _i20.CNEstimateType? fromOrTo,
required _i17.Decimal? amount,
String? fromNetwork,
String? toNetwork,
_i19.CNFlowType? flow = _i19.CNFlowType.standard,
_i20.CNFlowType? flow = _i20.CNFlowType.standard,
String? apiKey,
}) =>
(super.noSuchMethod(
@ -1038,8 +1066,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
returnValue:
_i7.Future<_i3.ExchangeResponse<_i19.CNExchangeEstimate>>.value(
_FakeExchangeResponse_1<_i19.CNExchangeEstimate>(
_i8.Future<_i4.ExchangeResponse<_i20.CNExchangeEstimate>>.value(
_FakeExchangeResponse_2<_i20.CNExchangeEstimate>(
this,
Invocation.method(
#getEstimatedExchangeAmountV2,
@ -1056,18 +1084,18 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<_i19.CNExchangeEstimate>>);
) as _i8.Future<_i4.ExchangeResponse<_i20.CNExchangeEstimate>>);
@override
_i7.Future<_i3.ExchangeResponse<List<_i20.FixedRateMarket>>>
_i8.Future<_i4.ExchangeResponse<List<_i21.FixedRateMarket>>>
getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod(
Invocation.method(
#getAvailableFixedRateMarkets,
[],
{#apiKey: apiKey},
),
returnValue: _i7
.Future<_i3.ExchangeResponse<List<_i20.FixedRateMarket>>>.value(
_FakeExchangeResponse_1<List<_i20.FixedRateMarket>>(
returnValue: _i8
.Future<_i4.ExchangeResponse<List<_i21.FixedRateMarket>>>.value(
_FakeExchangeResponse_2<List<_i21.FixedRateMarket>>(
this,
Invocation.method(
#getAvailableFixedRateMarkets,
@ -1075,14 +1103,14 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
{#apiKey: apiKey},
),
)),
) as _i7.Future<_i3.ExchangeResponse<List<_i20.FixedRateMarket>>>);
) as _i8.Future<_i4.ExchangeResponse<List<_i21.FixedRateMarket>>>);
@override
_i7.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>
_i8.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>
createStandardExchangeTransaction({
required String? fromTicker,
required String? toTicker,
required String? receivingAddress,
required _i16.Decimal? amount,
required _i17.Decimal? amount,
String? extraId = r'',
String? userId = r'',
String? contactEmail = r'',
@ -1107,9 +1135,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
#apiKey: apiKey,
},
),
returnValue: _i7
.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>.value(
_FakeExchangeResponse_1<_i21.ExchangeTransaction>(
returnValue: _i8
.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>.value(
_FakeExchangeResponse_2<_i22.ExchangeTransaction>(
this,
Invocation.method(
#createStandardExchangeTransaction,
@ -1128,14 +1156,14 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>);
) as _i8.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>);
@override
_i7.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>
_i8.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>
createFixedRateExchangeTransaction({
required String? fromTicker,
required String? toTicker,
required String? receivingAddress,
required _i16.Decimal? amount,
required _i17.Decimal? amount,
required String? rateId,
required bool? reversed,
String? extraId = r'',
@ -1164,9 +1192,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
#apiKey: apiKey,
},
),
returnValue: _i7
.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>.value(
_FakeExchangeResponse_1<_i21.ExchangeTransaction>(
returnValue: _i8
.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>.value(
_FakeExchangeResponse_2<_i22.ExchangeTransaction>(
this,
Invocation.method(
#createFixedRateExchangeTransaction,
@ -1187,11 +1215,11 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>);
) as _i8.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>);
@override
_i7.Future<
_i3
.ExchangeResponse<_i22.ExchangeTransactionStatus>> getTransactionStatus({
_i8.Future<
_i4
.ExchangeResponse<_i23.ExchangeTransactionStatus>> getTransactionStatus({
required String? id,
String? apiKey,
}) =>
@ -1204,9 +1232,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
#apiKey: apiKey,
},
),
returnValue: _i7
.Future<_i3.ExchangeResponse<_i22.ExchangeTransactionStatus>>.value(
_FakeExchangeResponse_1<_i22.ExchangeTransactionStatus>(
returnValue: _i8
.Future<_i4.ExchangeResponse<_i23.ExchangeTransactionStatus>>.value(
_FakeExchangeResponse_2<_i23.ExchangeTransactionStatus>(
this,
Invocation.method(
#getTransactionStatus,
@ -1217,9 +1245,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
},
),
)),
) as _i7.Future<_i3.ExchangeResponse<_i22.ExchangeTransactionStatus>>);
) as _i8.Future<_i4.ExchangeResponse<_i23.ExchangeTransactionStatus>>);
@override
_i7.Future<_i3.ExchangeResponse<List<_i23.Pair>>>
_i8.Future<_i4.ExchangeResponse<List<_i24.Pair>>>
getAvailableFloatingRatePairs({bool? includePartners = false}) =>
(super.noSuchMethod(
Invocation.method(
@ -1228,8 +1256,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
{#includePartners: includePartners},
),
returnValue:
_i7.Future<_i3.ExchangeResponse<List<_i23.Pair>>>.value(
_FakeExchangeResponse_1<List<_i23.Pair>>(
_i8.Future<_i4.ExchangeResponse<List<_i24.Pair>>>.value(
_FakeExchangeResponse_2<List<_i24.Pair>>(
this,
Invocation.method(
#getAvailableFloatingRatePairs,
@ -1237,5 +1265,5 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
{#includePartners: includePartners},
),
)),
) as _i7.Future<_i3.ExchangeResponse<List<_i23.Pair>>>);
) as _i8.Future<_i4.ExchangeResponse<List<_i24.Pair>>>);
}

View file

@ -680,6 +680,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -467,6 +467,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -467,6 +467,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -467,6 +467,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -465,6 +465,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -680,6 +680,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -521,6 +521,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -449,6 +449,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -449,6 +449,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -465,6 +465,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -701,6 +701,11 @@ class MockManager extends _i1.Mock implements _i15.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -465,6 +465,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

View file

@ -236,6 +236,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
returnValue: false,
) as bool);
@override
bool get hasFusionSupport => (super.noSuchMethod(
Invocation.getter(#hasFusionSupport),
returnValue: false,
) as bool);
@override
int get rescanOnOpenVersion => (super.noSuchMethod(
Invocation.getter(#rescanOnOpenVersion),
returnValue: 0,

Some files were not shown because too many files have changed in this diff Show more